Merge commit 'f2803c4a7f58237aa0dd9d39ccc6dea362527b96' into vulkan-cts
Change-Id: Icc934dee248beb972ba7e8084a6ad0c3fa2897ce
diff --git a/.appveyor.yml b/.appveyor.yml
index a50c7c2..669de03 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -42,7 +42,7 @@
- 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;%PATH%
+ - set PATH=C:\ninja;C:\Python36;%PATH%
before_build:
- git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
diff --git a/.gitignore b/.gitignore
index 059b18e..e097bab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,5 +21,5 @@
*~
# C-Lion
-.idea
-cmake-build-debug
\ No newline at end of file
+/.idea/
+/cmake-build-*/
diff --git a/Android.mk b/Android.mk
index 8597c50..7c6a076 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,10 +76,12 @@
source/opt/aggressive_dead_code_elim_pass.cpp \
source/opt/basic_block.cpp \
source/opt/block_merge_pass.cpp \
+ source/opt/block_merge_util.cpp \
source/opt/build_module.cpp \
source/opt/cfg.cpp \
source/opt/cfg_cleanup_pass.cpp \
source/opt/ccp_pass.cpp \
+ source/opt/code_sink.cpp \
source/opt/combine_access_chains.cpp \
source/opt/common_uniform_elim_pass.cpp \
source/opt/compact_ids_pass.cpp \
@@ -90,19 +92,24 @@
source/opt/dead_branch_elim_pass.cpp \
source/opt/dead_insert_elim_pass.cpp \
source/opt/dead_variable_elimination.cpp \
+ source/opt/decompose_initialized_variables_pass.cpp \
source/opt/decoration_manager.cpp \
source/opt/def_use_manager.cpp \
source/opt/dominator_analysis.cpp \
source/opt/dominator_tree.cpp \
source/opt/eliminate_dead_constant_pass.cpp \
source/opt/eliminate_dead_functions_pass.cpp \
+ source/opt/eliminate_dead_functions_util.cpp \
+ source/opt/eliminate_dead_members_pass.cpp \
source/opt/feature_manager.cpp \
+ source/opt/fix_storage_class.cpp \
source/opt/flatten_decoration_pass.cpp \
source/opt/fold.cpp \
source/opt/folding_rules.cpp \
source/opt/fold_spec_constant_op_and_composite_pass.cpp \
source/opt/freeze_spec_constant_value_pass.cpp \
source/opt/function.cpp \
+ source/opt/generate_webgpu_initializers_pass.cpp \
source/opt/if_conversion.cpp \
source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \
@@ -113,6 +120,7 @@
source/opt/instrument_pass.cpp \
source/opt/ir_context.cpp \
source/opt/ir_loader.cpp \
+ source/opt/legalize_vector_shuffle_pass.cpp \
source/opt/licm_pass.cpp \
source/opt/local_access_chain_convert_pass.cpp \
source/opt/local_redundancy_elimination.cpp \
@@ -150,6 +158,7 @@
source/opt/simplification_pass.cpp \
source/opt/ssa_rewrite_pass.cpp \
source/opt/strength_reduction_pass.cpp \
+ source/opt/strip_atomic_counter_memory_pass.cpp \
source/opt/strip_debug_info_pass.cpp \
source/opt/strip_reflect_info_pass.cpp \
source/opt/struct_cfg_analysis.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 5f0eba9..6c28118 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -305,7 +305,6 @@
static_library("spvtools") {
deps = [
- ":spvtools_core_enums_unified1",
":spvtools_core_tables_unified1",
":spvtools_generators_inc",
":spvtools_glsl_tables_glsl1-0",
@@ -376,6 +375,7 @@
]
public_deps = [
+ ":spvtools_core_enums_unified1",
":spvtools_headers",
]
@@ -452,6 +452,8 @@
"source/opt/basic_block.h",
"source/opt/block_merge_pass.cpp",
"source/opt/block_merge_pass.h",
+ "source/opt/block_merge_util.cpp",
+ "source/opt/block_merge_util.h",
"source/opt/build_module.cpp",
"source/opt/build_module.h",
"source/opt/ccp_pass.cpp",
@@ -460,6 +462,8 @@
"source/opt/cfg.h",
"source/opt/cfg_cleanup_pass.cpp",
"source/opt/cfg_cleanup_pass.h",
+ "source/opt/code_sink.cpp",
+ "source/opt/code_sink.h",
"source/opt/combine_access_chains.cpp",
"source/opt/combine_access_chains.h",
"source/opt/common_uniform_elim_pass.cpp",
@@ -480,6 +484,8 @@
"source/opt/dead_insert_elim_pass.h",
"source/opt/dead_variable_elimination.cpp",
"source/opt/dead_variable_elimination.h",
+ "source/opt/decompose_initialized_variables_pass.cpp",
+ "source/opt/decompose_initialized_variables_pass.h",
"source/opt/decoration_manager.cpp",
"source/opt/decoration_manager.h",
"source/opt/def_use_manager.cpp",
@@ -492,8 +498,14 @@
"source/opt/eliminate_dead_constant_pass.h",
"source/opt/eliminate_dead_functions_pass.cpp",
"source/opt/eliminate_dead_functions_pass.h",
+ "source/opt/eliminate_dead_functions_util.cpp",
+ "source/opt/eliminate_dead_functions_util.h",
+ "source/opt/eliminate_dead_members_pass.cpp",
+ "source/opt/eliminate_dead_members_pass.h",
"source/opt/feature_manager.cpp",
"source/opt/feature_manager.h",
+ "source/opt/fix_storage_class.cpp",
+ "source/opt/fix_storage_class.h",
"source/opt/flatten_decoration_pass.cpp",
"source/opt/flatten_decoration_pass.h",
"source/opt/fold.cpp",
@@ -506,6 +518,8 @@
"source/opt/freeze_spec_constant_value_pass.h",
"source/opt/function.cpp",
"source/opt/function.h",
+ "source/opt/generate_webgpu_initializers_pass.cpp",
+ "source/opt/generate_webgpu_initializers_pass.h",
"source/opt/if_conversion.cpp",
"source/opt/if_conversion.h",
"source/opt/inline_exhaustive_pass.cpp",
@@ -528,6 +542,8 @@
"source/opt/ir_loader.cpp",
"source/opt/ir_loader.h",
"source/opt/iterator.h",
+ "source/opt/legalize_vector_shuffle_pass.cpp",
+ "source/opt/legalize_vector_shuffle_pass.h",
"source/opt/licm_pass.cpp",
"source/opt/licm_pass.h",
"source/opt/local_access_chain_convert_pass.cpp",
@@ -604,6 +620,8 @@
"source/opt/ssa_rewrite_pass.h",
"source/opt/strength_reduction_pass.cpp",
"source/opt/strength_reduction_pass.h",
+ "source/opt/strip_atomic_counter_memory_pass.cpp",
+ "source/opt/strip_atomic_counter_memory_pass.h",
"source/opt/strip_debug_info_pass.cpp",
"source/opt/strip_debug_info_pass.h",
"source/opt/strip_reflect_info_pass.cpp",
@@ -649,132 +667,85 @@
]
}
-if (!build_with_chromium) {
- googletest_dir = spirv_tools_googletest_dir
-
- config("gtest_config") {
- include_dirs = [
- "${googletest_dir}/googletest",
- "${googletest_dir}/googletest/include",
- ]
- }
-
- static_library("gtest") {
- testonly = true
+# The tests are scoped to Chromium to avoid needing to write gtest integration.
+# See Chromium's third_party/googletest/BUILD.gn for a complete integration.
+if (build_with_chromium) {
+ test("spvtools_test") {
sources = [
- "${googletest_dir}/googletest/src/gtest-all.cc",
+ "test/assembly_context_test.cpp",
+ "test/assembly_format_test.cpp",
+ "test/binary_destroy_test.cpp",
+ "test/binary_endianness_test.cpp",
+ "test/binary_header_get_test.cpp",
+ "test/binary_parse_test.cpp",
+ "test/binary_strnlen_s_test.cpp",
+ "test/binary_to_text.literal_test.cpp",
+ "test/binary_to_text_test.cpp",
+ "test/comment_test.cpp",
+ "test/enum_set_test.cpp",
+ "test/enum_string_mapping_test.cpp",
+ "test/ext_inst.debuginfo_test.cpp",
+ "test/ext_inst.glsl_test.cpp",
+ "test/ext_inst.opencl_test.cpp",
+ "test/fix_word_test.cpp",
+ "test/generator_magic_number_test.cpp",
+ "test/hex_float_test.cpp",
+ "test/immediate_int_test.cpp",
+ "test/libspirv_macros_test.cpp",
+ "test/name_mapper_test.cpp",
+ "test/named_id_test.cpp",
+ "test/opcode_make_test.cpp",
+ "test/opcode_require_capabilities_test.cpp",
+ "test/opcode_split_test.cpp",
+ "test/opcode_table_get_test.cpp",
+ "test/operand_capabilities_test.cpp",
+ "test/operand_pattern_test.cpp",
+ "test/operand_test.cpp",
+ "test/target_env_test.cpp",
+ "test/test_fixture.h",
+ "test/text_advance_test.cpp",
+ "test/text_destroy_test.cpp",
+ "test/text_literal_test.cpp",
+ "test/text_start_new_inst_test.cpp",
+ "test/text_to_binary.annotation_test.cpp",
+ "test/text_to_binary.barrier_test.cpp",
+ "test/text_to_binary.constant_test.cpp",
+ "test/text_to_binary.control_flow_test.cpp",
+ "test/text_to_binary.debug_test.cpp",
+ "test/text_to_binary.device_side_enqueue_test.cpp",
+ "test/text_to_binary.extension_test.cpp",
+ "test/text_to_binary.function_test.cpp",
+ "test/text_to_binary.group_test.cpp",
+ "test/text_to_binary.image_test.cpp",
+ "test/text_to_binary.literal_test.cpp",
+ "test/text_to_binary.memory_test.cpp",
+ "test/text_to_binary.misc_test.cpp",
+ "test/text_to_binary.mode_setting_test.cpp",
+ "test/text_to_binary.pipe_storage_test.cpp",
+ "test/text_to_binary.reserved_sampling_test.cpp",
+ "test/text_to_binary.subgroup_dispatch_test.cpp",
+ "test/text_to_binary.type_declaration_test.cpp",
+ "test/text_to_binary_test.cpp",
+ "test/text_word_get_test.cpp",
+ "test/unit_spirv.cpp",
+ "test/unit_spirv.h",
]
- public_configs = [ ":gtest_config" ]
- }
- config("gmock_config") {
- include_dirs = [
- "${googletest_dir}/googlemock",
- "${googletest_dir}/googlemock/include",
- "${googletest_dir}/googletest/include",
- ]
- if (is_clang) {
- # TODO: Can remove this if/when the issue is fixed.
- # https://github.com/google/googletest/issues/533
- cflags = [ "-Wno-inconsistent-missing-override" ]
- }
- }
-
- static_library("gmock") {
- testonly = true
- sources = [
- "${googletest_dir}/googlemock/src/gmock-all.cc",
- ]
- public_configs = [ ":gmock_config" ]
- }
-}
-
-test("spvtools_test") {
- sources = [
- "test/assembly_context_test.cpp",
- "test/assembly_format_test.cpp",
- "test/binary_destroy_test.cpp",
- "test/binary_endianness_test.cpp",
- "test/binary_header_get_test.cpp",
- "test/binary_parse_test.cpp",
- "test/binary_strnlen_s_test.cpp",
- "test/binary_to_text.literal_test.cpp",
- "test/binary_to_text_test.cpp",
- "test/comment_test.cpp",
- "test/enum_set_test.cpp",
- "test/enum_string_mapping_test.cpp",
- "test/ext_inst.debuginfo_test.cpp",
- "test/ext_inst.glsl_test.cpp",
- "test/ext_inst.opencl_test.cpp",
- "test/fix_word_test.cpp",
- "test/generator_magic_number_test.cpp",
- "test/hex_float_test.cpp",
- "test/immediate_int_test.cpp",
- "test/libspirv_macros_test.cpp",
- "test/name_mapper_test.cpp",
- "test/named_id_test.cpp",
- "test/opcode_make_test.cpp",
- "test/opcode_require_capabilities_test.cpp",
- "test/opcode_split_test.cpp",
- "test/opcode_table_get_test.cpp",
- "test/operand_capabilities_test.cpp",
- "test/operand_pattern_test.cpp",
- "test/operand_test.cpp",
- "test/target_env_test.cpp",
- "test/test_fixture.h",
- "test/text_advance_test.cpp",
- "test/text_destroy_test.cpp",
- "test/text_literal_test.cpp",
- "test/text_start_new_inst_test.cpp",
- "test/text_to_binary.annotation_test.cpp",
- "test/text_to_binary.barrier_test.cpp",
- "test/text_to_binary.constant_test.cpp",
- "test/text_to_binary.control_flow_test.cpp",
- "test/text_to_binary.debug_test.cpp",
- "test/text_to_binary.device_side_enqueue_test.cpp",
- "test/text_to_binary.extension_test.cpp",
- "test/text_to_binary.function_test.cpp",
- "test/text_to_binary.group_test.cpp",
- "test/text_to_binary.image_test.cpp",
- "test/text_to_binary.literal_test.cpp",
- "test/text_to_binary.memory_test.cpp",
- "test/text_to_binary.misc_test.cpp",
- "test/text_to_binary.mode_setting_test.cpp",
- "test/text_to_binary.pipe_storage_test.cpp",
- "test/text_to_binary.reserved_sampling_test.cpp",
- "test/text_to_binary.subgroup_dispatch_test.cpp",
- "test/text_to_binary.type_declaration_test.cpp",
- "test/text_to_binary_test.cpp",
- "test/text_word_get_test.cpp",
- "test/unit_spirv.cpp",
- "test/unit_spirv.h",
- ]
-
- deps = [
- ":spvtools",
- ":spvtools_language_header_unified1",
- ":spvtools_val",
- ]
-
- if (build_with_chromium) {
- deps += [
+ deps = [
"//testing/gmock",
"//testing/gtest",
"//testing/gtest:gtest_main",
+ ":spvtools",
+ ":spvtools_language_header_unified1",
+ ":spvtools_val",
]
- } else {
- deps += [
- ":gmock",
- ":gtest",
- ]
- sources += [ "${googletest_dir}/googletest/src/gtest_main.cc" ]
- }
- if (is_clang) {
- cflags_cc = [ "-Wno-self-assign" ]
- }
+ if (is_clang) {
+ cflags_cc = [ "-Wno-self-assign" ]
+ }
- configs += [ ":spvtools_internal_config" ]
+ configs += [ ":spvtools_internal_config" ]
+ }
}
if (spirv_tools_standalone) {
diff --git a/CHANGES b/CHANGES
index 7f9008f..bfc70e5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,20 +1,116 @@
Revision history for SPIRV-Tools
-v2018.7-dev 2018-12-10
+v2019.3-dev 2019-04-03
+ - General:
+ - Updated Python scripts to work for both Python 2 and Python 3.
+ - Add a continuous test that does memory checks using the address sanitizer.
+ - Optimizer
+ - Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
+ - Bindless Validation: Descriptor Initialization Check (#2419)
+ - Add option to validate after each pass (#2462)
+ Fixes:
+ - #2412: Dead memeber elimination should not change input and output variables.
+ - #2405: Fix OpDot folding of half float vectors.
+ - #2391: Dead branch elim should not fold away back edges.
+ - #2441: Removing decorations when doing constant propagation.
+ - #2455: Maintain inst to block mapping in merge return.
+ - #2453: Fix merge return in the face of breaks.
+ - #2456: Handle dead infinite loops in DCE.
+ - #2458: Handle variable pointer in some optimizations.
+ - #2452: Fix dead branch elimination to handle unreachable blocks better.
+ - Validator
+ - Add validation of storage classes for WebGPU (#2446)
+ - Add validation for ExecutionMode in WebGPU (#2443)
+ - Implement WebGPU specific CFG validation (#2386)
+ - Allow NonWritable to target struct members. (#2420)
+ - Allow storage type mismatch for parameter in relaxed addressing mode.
+ - Allow non memory objects as parameter in relaxed addressing mode.
+ - Disallow nested Blocks and buffer blocks (#2410).
+ - Add validation for SPV_NV_cooperative_matrix (#2404)
+ - Add --strip-atomic-counter-memory (#2413)
+ - Check OpSampledImage is only passed into valid instructions (#2467)
+ - Handle function decls in Structured CFG analysis (#2474)
+ - Validate that OpUnreacahble is not statically reachable (#2473)
+ - Add pass to generate needed initializers for WebGPU (#2481)
+ Fixes:
+ - #2439: Add missing DepthGreater case to Fragment only check.
+ - #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan.
+ - #2408: Restrict and Aliased decorations cannot be applied to the same id.
+ - #2447: Improve function call parameter check.
+ - Reduce
+ - Add Pass to remove unreferenced blocks. (#2398)
+ - Allows passing options to the validator. (#2401)
+ - Improve reducer algorithm and other changes (#2472)
+ - Add Pass to remove selections (#2485)
+ Fixes:
+ - #2478: fix loop to selection pass for loops with combined header/continue block
+
+v2019.2 2019-02-20
+ - General:
+ - Support SPV_EXT_physical_storage_buffer
+ - A number of memory leak have been fixed.
+ - Removed use of deprecated Google test macro:
+ - Changed the BUILD.gn to only build tests in Chromium.
+ - Optimizer
+ - Upgrade memory model improvments for modf and frexp.
+ - Add a new pass to move loads closer to their uses: code sinking.
+ - Invalidating the type manager now invalidates the constnat manager.
+ - Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays.
+ - Add a new pass that removes members from structs that are not used: dead member elimination.
+ Fixes:
+ - #2292: Remove undefined behaviour when folding bit shifts.
+ - #2294: Fixes for instrumentation code.
+ - #2293: Fix overflow when folding -INT_MIN.
+ - #2374: Don't merge unreachable blocks when merging blocks.
+ - Validator
+ - Support SPV_KHR_no_integer_wrap and related decorations.
+ - Validate Vulkan rules for OpTypeRuntimeArray.
+ - Validate NonWritable decoration.
+ - Many WebGPU specific validation rules were added.
+ - Validate variable pointer related function call rules.
+ - Better error messages.
+ Fixes:
+ - #2307: Check forwards references in OpTypeArray.
+ - #2315, #2303: Fixed the layout check for relaxed layout.
+ - #1628: Emit an error when an OpSwitch target is not an OpLabel.
+ - Reduce
+ - Added more documentation for spirv-reduce.
+ - Add ability to remove OpPhi instructions.
+ - Add ability to merge two basic blocks.
+ - Add ability to remove unused functions and unused basic blocks.
+ Fixes:
+
+v2019.1 2019-01-07
- General:
- Created a new tool called spirv-reduce.
- Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103)
- New optimization pass to update the memory model from GLSL450 to VulkanKHR.
+ - Recognize OpTypeAccelerationStructureNV as a type instruction and ray tracing storage classes.
+ - Fix GCC8 build.
+ - Add --target-env flag to spirv-opt.
+ - Add --webgpu-mode flag to run optimizations for webgpu.
+ - The output disassembled line number stead of byte offset in validation errors. (#2091)
- Optimizer
- Added the instrumentation passes for bindless validation.
- Added passes to help preserve OpLine information (#2027)
- Add basic support for EXT_fragment_invocation_density (#2100)
- Fix invalid OpPhi generated by merge-return. (#2172)
+ - Constant and type manager have been turned into analysies. (#2251)
Fixes:
- #2018: Don't inline functions with a return in a structured CFG contstruct.
- #2047: Fix bug in folding when volatile stores are present.
- #2053: Fix check for when folding floating pointer values is allowed.
- #2130: Don't inline recursive functions.
+ - #2202: Handle multiple edges between two basic blocks in SSA-rewriter.
+ - #2205: Don't unswitch a latch condition during loop unswitch.
+ - #2245: Don't fold branch in loop unswitch. Run dead branch elimination to fold them.
+ - #2204: Fix eliminate common uniform to place OpPhi instructions correctly.
+ - #2247: Fix type mismatches caused by scalar replacement.
+ - #2248: Fix missing OpPhi after merge return.
+ - #2211: After merge return, fix invalid continue target.
+ - #2210: Fix loop invariant code motion to not place code between merge instruction and branch.
+ - #2258: Handle CompositeInsert with no indices in VDCE.
+ - #2261: Have replace load size handle extact with no index.
- Validator
- Changed the naming convention of outputing ids with names in diagnostic messages.
- Added validation rules for UniformConstant variables in Vulkan.
@@ -32,12 +128,14 @@
- Allow Float16/Int8 for Vulkan 1.0 (#2153)
- Check binding annotations in resource variables (#2151, #2167)
- Validate OpForwardPointer (#2156)
+ - Validate operation for OpSpecConstantOp (#2260)
Fixes:
- #2049: Allow InstanceId for NV ray tracing
- Reduce
- Initial commit wit a few passes to reduce test cases.
+ - Validation is run after each reduction step.
Fixes:
-
+
v2018.6 2018-11-07
- General:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index df0dd8d..f23f154 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,8 @@
if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
target_compile_options(${TARGET} PRIVATE
-fsanitize=${SPIRV_USE_SANITIZER})
+ set_target_properties(${TARGET} PROPERTIES
+ LINK_FLAGS -fsanitize=${SPIRV_USE_SANITIZER})
endif()
target_compile_options(${TARGET} PRIVATE
-ftemplate-depth=1024)
@@ -178,7 +180,8 @@
endmacro()
endif()
-find_host_package(PythonInterp)
+# Tests require Python3
+find_host_package(PythonInterp 3 REQUIRED)
# Check for symbol exports on Linux.
# At the moment, this check will fail on the OSX build machines for the Android NDK.
diff --git a/DEPS b/DEPS
index f7bac6d..3c5361f 100644
--- a/DEPS
+++ b/DEPS
@@ -11,7 +11,7 @@
'googletest_revision': '98a0d007d7092b72eea0e501bb9ad17908a1a036',
'testing_revision': '340252637e2e7c72c0901dcbeeacfff419e19b59',
're2_revision': '6cf8ccd82dbaab2668e9b13596c68183c9ecd13f',
- 'spirv_headers_revision': 'd5b2e1255f706ce1f88812217e9a554f299848af',
+ 'spirv_headers_revision': 'e74c389f81915d0a48d6df1af83c3862c5ad85ab',
}
deps = {
diff --git a/README.md b/README.md
index 534664f..edc5eca 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,5 @@
# SPIR-V Tools
-[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
-<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_release.svg)
-<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_release.svg)
-<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)
-
## Overview
The SPIR-V Tools project provides an API and commands for processing SPIR-V
@@ -24,6 +19,15 @@
See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification,
headers, and XML registry.
+## Downloads
+
+[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
+<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html)
+<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html)
+<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html)
+
+[More downloads](downloads.md)
+
## Versioning SPIRV-Tools
See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.
@@ -47,7 +51,7 @@
* Support for SPIR-V 1.0, 1.1, 1.2, and 1.3
* Based on SPIR-V syntax described by JSON grammar files in the
- [SPIRV-Headers](spirv-headers) repository.
+ [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository.
* Support for extended instruction sets:
* GLSL std450 version 1.0 Rev 3
* OpenCL version 1.0 Rev 2
@@ -128,6 +132,23 @@
sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
planned and in-progress work.
+
+### Reducer
+
+*Note:* The reducer is still under development.
+
+The reducer simplifies and shrinks a SPIR-V module with respect to a
+user-supplied *interestingness function*. For example, given a large
+SPIR-V module that cause some SPIR-V compiler to fail with a given
+fatal error message, the reducer could be used to look for a smaller
+version of the module that causes the compiler to fail with the same
+fatal error message.
+
+To suggest an additional capability for the reducer, [file an
+issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
+"Reducer:" as the start of its title.
+
+
### Extras
* [Utility filters](#utility-filters)
@@ -252,6 +273,31 @@
Once the build files have been generated, build using your preferred
development environment.
+### Tools you'll need
+
+For building and testing SPIRV-Tools, the following tools should be
+installed regardless of your OS:
+
+- [CMake](http://www.cmake.org/): for generating compilation targets. Version
+ 2.8.12 or later.
+- [Python 3](http://www.python.org/): for utility scripts and running the test
+suite.
+
+SPIRV-Tools is regularly tested with the the following compilers:
+
+On Linux
+- GCC version 4.8.5
+- Clang version 3.8
+
+On MacOS
+- AppleClang 10.0
+
+On Windows
+- Visual Studio 2015
+- Visual Studio 2017
+
+Other compilers or later versions may work, but they are not tested.
+
### CMake options
The following CMake options are supported:
@@ -415,6 +461,18 @@
* `spirv-val` - the standalone validator
* `<spirv-dir>/tools/val`
+### Reducer tool
+
+The reducer shrinks a SPIR-V binary module, guided by a user-supplied
+*interestingness test*.
+
+This is a work in progress, with initially only shrinks a module in a few ways.
+
+* `spirv-reduce` - the standalone reducer
+ * `<spirv-dir>/tools/reduce`
+
+Run `spirv-reduce --help` to see how to specify interestingness.
+
### Control flow dumper tool
The control flow dumper prints the control flow graph for a SPIR-V module as a
diff --git a/downloads.md b/downloads.md
new file mode 100644
index 0000000..9c7d856
--- /dev/null
+++ b/downloads.md
@@ -0,0 +1,14 @@
+# Downloads
+Download the latest builds.
+
+## Release
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | |
+
+## Debug
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | |
diff --git a/examples/cpp-interface/main.cpp b/examples/cpp-interface/main.cpp
index c5354b8..a1e22c7 100644
--- a/examples/cpp-interface/main.cpp
+++ b/examples/cpp-interface/main.cpp
@@ -28,6 +28,7 @@
int main() {
const std::string source =
+ " OpCapability Linkage "
" OpCapability Shader "
" OpMemoryModel Logical GLSL450 "
" OpSource GLSL 450 "
@@ -36,8 +37,8 @@
" %spec = OpSpecConstant %int 0 "
"%const = OpConstant %int 42";
- spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0);
- spvtools::Optimizer opt(SPV_ENV_VULKAN_1_0);
+ spvtools::SpirvTools core(SPV_ENV_UNIVERSAL_1_3);
+ spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_3);
auto print_msg_to_stderr = [](spv_message_level_t, const char*,
const spv_position_t&, const char* m) {
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 69d1ad2..9711b92 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -30,17 +30,17 @@
// Stream Output Buffer Offsets
//
-// The following values provide 32-bit word offsets into the output buffer
+// The following values provide offsets into the output buffer struct
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
// by InstBindlessCheckPass.
//
-// The first word of the debug output buffer contains the next available word
+// The first member of the debug output buffer contains the next available word
// in the data stream to be written. Shaders will atomically read and update
// this value so as not to overwrite each others records. This value must be
// initialized to zero
static const int kDebugOutputSizeOffset = 0;
-// The second word of the output buffer is the start of the stream of records
+// The second member of the output buffer is the start of the stream of records
// written by the instrumented shaders. Each record represents a validation
// error. The format of the records is documented below.
static const int kDebugOutputDataOffset = 1;
@@ -75,8 +75,8 @@
// error.
//
// Vertex Shader Output Record Offsets
-static const int kInstVertOutVertexId = kInstCommonOutCnt;
-static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
+static const int kInstVertOutVertexIndex = kInstCommonOutCnt;
+static const int kInstVertOutInstanceIndex = kInstCommonOutCnt + 1;
// Frag Shader Output Record Offsets
static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
@@ -110,6 +110,16 @@
// about the validation error.
//
// A bindless bounds error will output the index and the bound.
+static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+
+// A bindless uninitialized error will output the index.
+static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+
+// DEPRECATED
static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
@@ -121,15 +131,52 @@
//
// These are the possible validation error codes.
static const int kInstErrorBindlessBounds = 0;
+static const int kInstErrorBindlessUninit = 1;
+
+// Direct Input Buffer Offsets
+//
+// The following values provide member offsets into the input buffers
+// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
+// by InstBindlessCheckPass.
+//
+// The only object in an input buffer is a runtime array of unsigned
+// integers. Each validation will have its own formatting of this array.
+static const int kDebugInputDataOffset = 0;
// Debug Buffer Bindings
//
// These are the bindings for the different buffers which are
// read or written by the instrumentation passes.
//
-// This is the output buffer written by InstBindlessCheckPass.
+// This is the output buffer written by InstBindlessCheckPass
+// and possibly other future validations.
static const int kDebugOutputBindingStream = 0;
+// The binding for the input buffer read by InstBindlessCheckPass and
+// possibly other future validations.
+static const int kDebugInputBindingBindless = 1;
+
+// Bindless Validation Input Buffer Format
+//
+// An input buffer for bindless validation consists of a single array of
+// unsigned integers we will call Data[]. This array is formatted as follows.
+//
+// At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
+// gives an offset to the start of the bindless initialization data. More
+// specifically, if the following value is zero, we know that the descriptor at
+// (set = s, binding = b, index = i) is not initialized:
+// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
+static const int kDebugInputBindlessInitOffset = 0;
+
+// DEPRECATED
+static const int kDebugInputBindlessOffsetReserved = 0;
+
+// At offset kDebugInputBindlessOffsetLengths is some number of uints which
+// provide the bindless length data. More specifically, the number of
+// descriptors at (set=s, binding=b) is:
+// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
+static const int kDebugInputBindlessOffsetLengths = 1;
+
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index ff7eb6b..f794faf 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -427,6 +427,8 @@
SPV_ENV_UNIVERSAL_1_3, // SPIR-V 1.3 latest revision, no other restrictions.
SPV_ENV_VULKAN_1_1, // Vulkan 1.1 latest revision.
SPV_ENV_WEBGPU_0, // Work in progress WebGPU 1.0.
+ SPV_ENV_UNIVERSAL_1_4, // SPIR-V 1.4 latest revision, no other restrictions.
+ SPV_ENV_VULKAN_1_1_SPIRV_1_4, // Vulkan 1.1 with SPIR-V 1.4 binary.
} spv_target_env;
// SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -445,6 +447,10 @@
// Returns a string describing the given SPIR-V target environment.
SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
+// Parses s into *env and returns true if successful. If unparsable, returns
+// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
+SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
+
// Creates a context object. Returns null if env is invalid.
SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
@@ -498,6 +504,11 @@
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
spv_validator_options options, bool val);
+// Records whether the validator should use standard block layout rules for
+// uniform blocks.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout(
+ spv_validator_options options, bool val);
+
// Records whether the validator should use "scalar" block layout rules.
// Scalar layout rules are more permissive than relaxed block layout.
//
@@ -553,14 +564,17 @@
// Destroys the given reducer options object.
SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
-// Records the maximum number of reduction steps that should run before the
-// reducer gives up.
+// Sets the maximum number of reduction steps that should run before the reducer
+// gives up.
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
spv_reducer_options options, uint32_t step_limit);
-// Sets seed for random number generation.
-SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
- uint32_t seed);
+// Sets the fail-on-validation-error option; if true, the reducer will return
+// kStateInvalid if a reduction step yields a state that fails SPIR-V
+// validation. Otherwise, an invalid state is treated as uninteresting and the
+// reduction backtracks and continues.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+ spv_reducer_options options, bool fail_on_validation_error);
// Encodes the given SPIR-V assembly text to its binary representation. The
// length parameter specifies the number of bytes for text. Encoded binary will
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 9cb5afe..27b87e7 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -88,6 +88,12 @@
spvValidatorOptionsSetRelaxBlockLayout(options_, val);
}
+ // Enables VK_KHR_uniform_buffer_standard_layout when validating standard
+ // uniform layout. If true, disables scalar block layout rules.
+ void SetUniformBufferStandardLayout(bool val) {
+ spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
+ }
+
// Enables VK_EXT_scalar_block_layout when validating standard
// uniform/storage buffer/push-constant layout. If true, disables
// relaxed block layout rules.
@@ -151,16 +157,20 @@
~ReducerOptions() { spvReducerOptionsDestroy(options_); }
// Allow implicit conversion to the underlying object.
- operator spv_reducer_options() const { return options_; }
+ operator spv_reducer_options() const { // NOLINT(google-explicit-constructor)
+ return options_;
+ }
- // Records the maximum number of reduction steps that should
- // run before the reducer gives up.
+ // See spvReducerOptionsSetStepLimit.
void set_step_limit(uint32_t step_limit) {
spvReducerOptionsSetStepLimit(options_, step_limit);
}
- // Sets a seed to be used for random number generation.
- void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
+ // See spvReducerOptionsSetFailOnValidationError.
+ void set_fail_on_validation_error(bool fail_on_validation_error) {
+ spvReducerOptionsSetFailOnValidationError(options_,
+ fail_on_validation_error);
+ }
private:
spv_reducer_options options_;
@@ -235,6 +245,9 @@
bool Validate(const uint32_t* binary, size_t binary_size,
spv_validator_options options) const;
+ // Was this object successfully constructed.
+ bool IsValid() const;
+
private:
struct Impl; // Opaque struct for holding the data fields used by this class.
std::unique_ptr<Impl> impl_; // Unique pointer to implementation data.
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index f8769c9..af9f3e5 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -101,6 +101,16 @@
// from time to time.
Optimizer& RegisterSizePasses();
+ // Registers passes that have been prescribed for converting from Vulkan to
+ // WebGPU. This sequence of passes is subject to constant review and will
+ // change from time to time.
+ Optimizer& RegisterVulkanToWebGPUPasses();
+
+ // Registers passes that have been prescribed for converting from WebGPU to
+ // Vulkan. This sequence of passes is subject to constant review and will
+ // change from time to time.
+ Optimizer& RegisterWebGPUToVulkanPasses();
+
// Registers passes that attempt to legalize the generated code.
//
// Note: this recipe is specially designed for legalizing SPIR-V. It should be
@@ -194,6 +204,9 @@
// |out| output stream.
Optimizer& SetTimeReport(std::ostream* out);
+ // Sets the option to validate the module after each pass.
+ Optimizer& SetValidateAfterAll(bool validate);
+
private:
struct Impl; // Opaque struct for holding internal data.
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
@@ -203,6 +216,13 @@
// A null pass does nothing to the SPIR-V module to be optimized.
Optimizer::PassToken CreateNullPass();
+// Creates a strip-atomic-counter-memory pass.
+// A strip-atomic-counter-memory pass removes all usages of the
+// AtomicCounterMemory bit in Memory Semantics bitmasks. This bit is a no-op in
+// Vulkan, so isn't needed in that env. And the related capability is not
+// allowed in WebGPU, so it is not allowed in that env.
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass();
+
// Creates a strip-debug-info pass.
// A strip-debug-info pass removes all debug instructions (as documented in
// Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
@@ -221,6 +241,11 @@
// functions are not needed because they will never be called.
Optimizer::PassToken CreateEliminateDeadFunctionsPass();
+// Creates an eliminate-dead-members pass.
+// An eliminate-dead-members pass will remove all unused members of structures.
+// This will not affect the data layout of the remaining members.
+Optimizer::PassToken CreateEliminateDeadMembersPass();
+
// Creates a set-spec-constant-default-value pass from a mapping from spec-ids
// to the default values in the form of string.
// A set-spec-constant-default-value pass sets the default values for the
@@ -687,10 +712,14 @@
// Create a pass to instrument bindless descriptor checking
// This pass instruments all bindless references to check that descriptor
-// array indices are inbounds. If the reference is invalid, a record is
-// written to the debug output buffer (if space allows) and a null value is
-// returned. This pass is designed to support bindless validation in the Vulkan
-// validation layers.
+// array indices are inbounds, and if the descriptor indexing extension is
+// enabled, that the descriptor has been initialized. If the reference is
+// invalid, a record is written to the debug output buffer (if space allows)
+// and a null value is returned. This pass is designed to support bindless
+// validation in the Vulkan validation layers.
+//
+// TODO(greg-lunarg): Add support for buffer references. Currently only does
+// checking for image references.
//
// Dead code elimination should be run after this pass as the original,
// potentially invalid code is not removed and could cause undefined behavior,
@@ -706,10 +735,12 @@
// The instrumentation will read and write buffers in debug
// descriptor set |desc_set|. It will write |shader_id| in each output record
// to identify the shader module which generated the record.
-//
-// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
-Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
- uint32_t shader_id);
+// |input_length_enable| controls instrumentation of runtime descriptor array
+// references, and |input_init_enable| controls instrumentation of descriptor
+// initialization checking, both of which require input buffer support.
+Optimizer::PassToken CreateInstBindlessCheckPass(
+ uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
+ bool input_init_enable = false);
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
@@ -717,6 +748,30 @@
// conform to that model's requirements.
Optimizer::PassToken CreateUpgradeMemoryModelPass();
+// Create a pass to do code sinking. Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+Optimizer::PassToken CreateCodeSinkingPass();
+
+// Create a pass to adds initializers for OpVariable calls that require them
+// in WebGPU. Currently this pass naively initializes variables that are
+// missing an initializer with a null value. In the future it may initialize
+// variables to the first value stored in them, if that is a constant.
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass();
+
+// Create a pass to fix incorrect storage classes. In order to make code
+// generation simpler, DXC may generate code where the storage classes do not
+// match up correctly. This pass will fix the errors that it can.
+Optimizer::PassToken CreateFixStorageClassPass();
+
+// Create a pass to legalize OpVectorShuffle operands going into WebGPU. WebGPU
+// forbids using 0xFFFFFFFF, which indicates an undefined result, so this pass
+// converts those literals to 0.
+Optimizer::PassToken CreateLegalizeVectorShufflePass();
+
+// Create a pass to decompose initialized variables into a seperate variable
+// declaration and an initial store.
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
+
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/linux-clang-asan/build.sh b/kokoro/linux-clang-asan/build.sh
new file mode 100644
index 0000000..8f86e6e
--- /dev/null
+++ b/kokoro/linux-clang-asan/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# 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 ASAN clang
diff --git a/kokoro/linux-clang-asan/continuous.cfg b/kokoro/linux-clang-asan/continuous.cfg
new file mode 100644
index 0000000..3a98fc7
--- /dev/null
+++ b/kokoro/linux-clang-asan/continuous.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Continuous build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-asan/presubmit.cfg b/kokoro/linux-clang-asan/presubmit.cfg
new file mode 100644
index 0000000..ceac44b
--- /dev/null
+++ b/kokoro/linux-clang-asan/presubmit.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Presubmit build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-debug/continuous.cfg b/kokoro/linux-clang-debug/continuous.cfg
index e92f059..3350f3b 100644
--- a/kokoro/linux-clang-debug/continuous.cfg
+++ b/kokoro/linux-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/linux-clang-debug/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/linux-clang-release/continuous.cfg b/kokoro/linux-clang-release/continuous.cfg
index 687434a..8b075c6 100644
--- a/kokoro/linux-clang-release/continuous.cfg
+++ b/kokoro/linux-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/linux-clang-release/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/linux-gcc-debug/continuous.cfg b/kokoro/linux-gcc-debug/continuous.cfg
index 4f8418d..d9579d5 100644
--- a/kokoro/linux-gcc-debug/continuous.cfg
+++ b/kokoro/linux-gcc-debug/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/linux-gcc-debug/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/linux-gcc-release/continuous.cfg b/kokoro/linux-gcc-release/continuous.cfg
index 41a0024..ead07bf 100644
--- a/kokoro/linux-gcc-release/continuous.cfg
+++ b/kokoro/linux-gcc-release/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/linux-gcc-release/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/macos-clang-debug/continuous.cfg b/kokoro/macos-clang-debug/continuous.cfg
index 84aaa5c..f5f274a 100644
--- a/kokoro/macos-clang-debug/continuous.cfg
+++ b/kokoro/macos-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/macos-clang-debug/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/macos-clang-release/continuous.cfg b/kokoro/macos-clang-release/continuous.cfg
index a8e23a7..710185e 100644
--- a/kokoro/macos-clang-release/continuous.cfg
+++ b/kokoro/macos-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/macos-clang-release/build.sh"
+
+action {
+ define_artifacts {
+ regex: "install.tgz"
+ }
+}
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index d457539..e89d2ea 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -31,8 +31,7 @@
CMAKE_C_CXX_COMPILER=""
if [ $COMPILER = "clang" ]
then
- sudo ln -s /usr/bin/clang-3.8 /usr/bin/clang
- sudo ln -s /usr/bin/clang++-3.8 /usr/bin/clang++
+ PATH=/usr/lib/llvm-3.8/bin:$PATH
CMAKE_C_CXX_COMPILER="-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
fi
@@ -47,8 +46,8 @@
ADDITIONAL_CMAKE_FLAGS=""
if [ $CONFIG = "ASAN" ]
then
- ADDITIONAL_CMAKE_FLAGS="-DCMAKE_CXX_FLAGS=-fsanitize=address -DCMAKE_C_FLAGS=-fsanitize=address"
- export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
+ ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address"
+ [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
elif [ $CONFIG = "COVERAGE" ]
then
ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON"
@@ -78,7 +77,7 @@
# Invoke the build.
BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
echo $(date): Starting build...
-cmake -GNinja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
+cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -GNinja -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
echo $(date): Build everything...
ninja
@@ -98,3 +97,7 @@
fi
echo $(date): ctest completed.
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index a7f0453..98e6366 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -41,7 +41,15 @@
# Invoke the build.
BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
echo $(date): Starting build...
-cmake -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+# We need Python 3. At the moment python3.7 is the newest Python on Kokoro.
+cmake \
+ -GNinja \
+ -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install \
+ -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3.7 \
+ -DCMAKE_C_COMPILER=clang \
+ -DCMAKE_CXX_COMPILER=clang++ \
+ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+ ..
echo $(date): Build everything...
ninja
@@ -51,3 +59,8 @@
ctest -j4 --output-on-failure --timeout 300
echo $(date): ctest completed.
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
+
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index a2472fb..0532118 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -21,8 +21,8 @@
set BUILD_TYPE=%1
set VS_VERSION=%2
-:: Force usage of python 2.7 rather than 3.6
-set PATH=C:\python27;%PATH%
+:: Force usage of python 3.6
+set PATH=C:\python36;%PATH%
cd %SRC%
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
@@ -58,33 +58,45 @@
set BUILD_SHA=%KOKORO_GITHUB_COMMIT%
)
+set CMAKE_FLAGS=-DCMAKE_INSTALL_PREFIX=%KOKORO_ARTIFACTS_DIR%\install -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+
:: Skip building tests for VS2013
if %VS_VERSION% == 2013 (
- cmake -GNinja -DSPIRV_SKIP_TESTS=ON -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
-) else (
- cmake -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
+ set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_SKIP_TESTS=ON
)
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+cmake %CMAKE_FLAGS% ..
+
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
echo "Build everything... %DATE% %TIME%"
ninja
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
echo "Build Completed %DATE% %TIME%"
+:: This lets us use !ERRORLEVEL! inside an IF ... () and get the actual error at that point.
+setlocal ENABLEDELAYEDEXPANSION
+
:: ################################################
:: Run the tests (We no longer run tests on VS2013)
:: ################################################
-if NOT %VS_VERSION% == 2013 (
- echo "Running Tests... %DATE% %TIME%"
+echo "Running Tests... %DATE% %TIME%"
+if %VS_VERSION% NEQ 2013 (
ctest -C %BUILD_TYPE% --output-on-failure --timeout 300
- if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
- echo "Tests Completed %DATE% %TIME%"
+ if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
)
+echo "Tests Completed %DATE% %TIME%"
+
+:: ################################################
+:: Install and package.
+:: ################################################
+ninja install
+cd %KOKORO_ARTIFACTS_DIR%
+zip -r install.zip install
:: Clean up some directories.
rm -rf %SRC%\build
rm -rf %SRC%\external
-exit /b %ERRORLEVEL%
+exit /b 0
diff --git a/kokoro/shaderc-smoketest/build.sh b/kokoro/shaderc-smoketest/build.sh
index 638ca8c..0856c9b 100644
--- a/kokoro/shaderc-smoketest/build.sh
+++ b/kokoro/shaderc-smoketest/build.sh
@@ -37,7 +37,7 @@
# Get shaderc dependencies. Link the appropriate SPIRV-Tools.
git clone https://github.com/google/googletest.git
-git clone https://github.com/google/glslang.git
+git clone https://github.com/KhronosGroup/glslang.git
ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
git clone https://github.com/google/re2
diff --git a/kokoro/windows-msvc-2017-debug/continuous.cfg b/kokoro/windows-msvc-2017-debug/continuous.cfg
index b842c30..25c5e11 100644
--- a/kokoro/windows-msvc-2017-debug/continuous.cfg
+++ b/kokoro/windows-msvc-2017-debug/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
+
+action {
+ define_artifacts {
+ regex: "install.zip"
+ }
+}
diff --git a/kokoro/windows-msvc-2017-release/continuous.cfg b/kokoro/windows-msvc-2017-release/continuous.cfg
index 7b8c2ff..a9ac6ec 100644
--- a/kokoro/windows-msvc-2017-release/continuous.cfg
+++ b/kokoro/windows-msvc-2017-release/continuous.cfg
@@ -14,3 +14,9 @@
# Continuous build configuration.
build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release/build.bat"
+
+action {
+ define_artifacts {
+ regex: "install.zip"
+ }
+}
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index efd6330..1f96018 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -101,7 +101,7 @@
list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
endmacro(spvtools_opencl_tables)
-macro(spvtools_vendor_tables VENDOR_TABLE)
+macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME)
set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
add_custom_command(OUTPUT ${INSTS_FILE}
@@ -110,9 +110,9 @@
--vendor-insts-output=${INSTS_FILE}
DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.")
- list(APPEND EXTINST_CPP_DEPENDS ${INSTS_FILE})
- add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE})
- set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build")
+ add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE})
+ set_property(TARGET spv-tools-${SHORT_NAME} PROPERTY FOLDER "SPIRV-Tools build")
+ list(APPEND EXTINST_CPP_DEPENDS spv-tools-${SHORT_NAME})
endmacro(spvtools_vendor_tables)
macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
@@ -125,20 +125,20 @@
--extinst-output-base=${OUTBASE}
DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
COMMENT "Generate language specific header for ${NAME}.")
- list(APPEND EXTINST_CPP_DEPENDS ${OUT_H})
add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H})
set_property(TARGET spirv-tools-header-${NAME} PROPERTY FOLDER "SPIRV-Tools build")
+ list(APPEND EXTINST_CPP_DEPENDS spirv-tools-header-${NAME})
endmacro(spvtools_extinst_lang_headers)
spvtools_core_tables("unified1")
spvtools_enum_string_mapping("unified1")
spvtools_opencl_tables("unified1")
spvtools_glsl_tables("unified1")
-spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
-spvtools_vendor_tables("spv-amd-shader-trinary-minmax")
-spvtools_vendor_tables("spv-amd-gcn-shader")
-spvtools_vendor_tables("spv-amd-shader-ballot")
-spvtools_vendor_tables("debuginfo")
+spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp")
+spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm")
+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_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
spvtools_vimsyntax("unified1" "1.0")
@@ -159,36 +159,18 @@
# The following .cpp files include the above generated .inc files.
# Add those .inc files as their dependencies.
#
-# Why using such an awkward way?
-# * If we use add_custom_target() to define a target to generate all .inc files
-# and let ${SPIRV_TOOLS} depend on it, then we need to run ninja twice every
-# time the grammar is updated: the first time is for generating those .inc
-# files, and the second time is for rebuilding .cpp files, when ninja finds
-# out that .inc files are updated.
-# * If we use add_custom_command() with PRE_BUILD, then the grammar processing
-# script will always run no matter whether the grammar is updated.
-# * add_dependencies() is used to add *target* dependencies to a target.
-# * The following solution only generates .inc files when the script or the
-# grammar files is updated, and in a single ninja run.
-set_source_files_properties(
- ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
- PROPERTIES OBJECT_DEPENDS "${OPCODE_CPP_DEPENDS}")
-set_source_files_properties(
- ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
- PROPERTIES OBJECT_DEPENDS "${OPERAND_CPP_DEPENDS}")
-set_source_files_properties(
- ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
- PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
-set_source_files_properties(
- ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp
- PROPERTIES OBJECT_DEPENDS "${ENUM_STRING_MAPPING_CPP_DEPENDS}")
+# We need to wrap the .inc files with a custom target to avoid problems when
+# multiple targets depend on the same custom command.
+add_custom_target(core_tables
+ DEPENDS ${OPCODE_CPP_DEPENDS} ${OPERAND_CPP_DEPENDS})
+add_custom_target(enum_string_mapping
+ DEPENDS ${EXTENSION_H_DEPENDS} ${ENUM_STRING_MAPPING_CPP_DEPENDS})
+add_custom_target(extinst_tables
+ DEPENDS ${EXTINST_CPP_DEPENDS})
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
PROPERTIES HEADER_FILE_ONLY TRUE)
-set_source_files_properties(
- ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
- PROPERTIES OBJECT_DEPENDS "${EXTENSION_H_DEPENDS}")
set(SPIRV_TOOLS_BUILD_VERSION_INC
${spirv-tools_BINARY_DIR}/build-version.inc)
@@ -361,6 +343,7 @@
)
set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries")
spvtools_check_symbol_exports(${SPIRV_TOOLS})
+add_dependencies( ${SPIRV_TOOLS} core_tables enum_string_mapping extinst_tables )
add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES})
spvtools_default_compile_options(${SPIRV_TOOLS}-shared)
@@ -376,6 +359,15 @@
PRIVATE SPIRV_TOOLS_IMPLEMENTATION
PUBLIC SPIRV_TOOLS_SHAREDLIB
)
+add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables )
+
+if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
+ find_library(LIBRT rt)
+ if(LIBRT)
+ target_link_libraries(${SPIRV_TOOLS} ${LIBRT})
+ target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT})
+ endif()
+endif()
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 4d98e3d..79f18ee 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -154,10 +154,11 @@
CASE(InBoundsAccessChain),
CASE(PtrAccessChain),
CASE(InBoundsPtrAccessChain),
+ CASE(CooperativeMatrixLengthNV)
};
-// The 59 is determined by counting the opcodes listed in the spec.
-static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 60 is determined by counting the opcodes listed in the spec.
+static_assert(60 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
"OpSpecConstantOp opcode table is incomplete");
#undef CASE
// clang-format on
diff --git a/source/binary.cpp b/source/binary.cpp
index 6604d80..636dac8 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -123,8 +123,8 @@
// returned object will be propagated to the current parse's diagnostic
// object.
spvtools::DiagnosticStream diagnostic(spv_result_t error) {
- return spvtools::DiagnosticStream({0, 0, _.word_index}, consumer_, "",
- error);
+ return spvtools::DiagnosticStream({0, 0, _.instruction_count}, consumer_,
+ "", error);
}
// Returns a diagnostic stream object with the default parse error code.
@@ -179,6 +179,7 @@
num_words(num_words_arg),
diagnostic(diagnostic_arg),
word_index(0),
+ instruction_count(0),
endian(),
requires_endian_conversion(false) {
// Temporary storage for parser state within a single instruction.
@@ -192,6 +193,7 @@
size_t num_words; // Number of words in the module.
spv_diagnostic* diagnostic; // Where diagnostics go.
size_t word_index; // The current position in words.
+ size_t instruction_count; // The count of processed instructions
spv_endianness_t endian; // The endianness of the binary.
// Is the SPIR-V binary in a different endiannes from the host native
// endianness?
@@ -269,6 +271,8 @@
}
spv_result_t Parser::parseInstruction() {
+ _.instruction_count++;
+
// The zero values for all members except for opcode are the
// correct initial values.
spv_parsed_instruction_t inst = {};
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 08c775e..1198f76 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -30,6 +30,7 @@
#include "glsl.std.450.insts.inc"
#include "opencl.std.insts.inc"
+#include "spirv-tools/libspirv.h"
#include "spv-amd-gcn-shader.insts.inc"
#include "spv-amd-shader-ballot.insts.inc"
#include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
@@ -80,7 +81,9 @@
case SPV_ENV_OPENGL_4_5:
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_VULKAN_1_1:
+ case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
case SPV_ENV_WEBGPU_0:
+ case SPV_ENV_UNIVERSAL_1_4:
*pExtInstTable = &kTable_1_0;
return SPV_SUCCESS;
default:
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index b5fe897..a1ed11d 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -128,4 +128,6 @@
return valid;
}
+bool SpirvTools::IsValid() const { return impl_->context != nullptr; }
+
} // namespace spvtools
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 78c2386..ddf2deb 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -260,6 +260,7 @@
case SpvOpTypeMatrix:
case SpvOpTypeArray:
case SpvOpTypeStruct:
+ case SpvOpTypeCooperativeMatrixNV:
return true;
default:
return false;
@@ -325,6 +326,7 @@
case SpvOpTypePipeStorage:
case SpvOpTypeNamedBarrier:
case SpvOpTypeAccelerationStructureNV:
+ case SpvOpTypeCooperativeMatrixNV:
return true;
default:
// In particular, OpTypeForwardPointer does not generate a type,
@@ -603,3 +605,35 @@
return false;
}
}
+
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
+ switch (opcode) {
+ case SpvOpMemoryBarrier:
+ return {1};
+ case SpvOpAtomicStore:
+ case SpvOpControlBarrier:
+ case SpvOpAtomicFlagClear:
+ case SpvOpMemoryNamedBarrier:
+ return {2};
+ case SpvOpAtomicLoad:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ return {4};
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ return {4, 5};
+ default:
+ return {};
+ }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 76f9a0e..ed64f1b 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -133,4 +133,8 @@
// Returns true if the given opcode is a debug instruction.
bool spvOpcodeIsDebug(SpvOp opcode);
+// Returns a vector containing the indices of the memory semantics <id>
+// operands for |opcode|.
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
+
#endif // SOURCE_OPCODE_H_
diff --git a/source/operand.cpp b/source/operand.cpp
index 923074e..00dc53d 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -481,6 +481,9 @@
case SpvOpTypeForwardPointer:
out = [](unsigned index) { return index == 0; };
break;
+ case SpvOpTypeArray:
+ out = [](unsigned index) { return index == 1; };
+ break;
default:
out = [](unsigned) { return false; };
break;
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 96ee8b3..b02485a 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -15,10 +15,12 @@
aggressive_dead_code_elim_pass.h
basic_block.h
block_merge_pass.h
+ block_merge_util.h
build_module.h
ccp_pass.h
cfg_cleanup_pass.h
cfg.h
+ code_sink.h
combine_access_chains.h
common_uniform_elim_pass.h
compact_ids_pass.h
@@ -29,19 +31,24 @@
dead_branch_elim_pass.h
dead_insert_elim_pass.h
dead_variable_elimination.h
+ decompose_initialized_variables_pass.h
decoration_manager.h
def_use_manager.h
dominator_analysis.h
dominator_tree.h
eliminate_dead_constant_pass.h
eliminate_dead_functions_pass.h
+ eliminate_dead_functions_util.h
+ eliminate_dead_members_pass.h
feature_manager.h
+ fix_storage_class.h
flatten_decoration_pass.h
fold.h
folding_rules.h
fold_spec_constant_op_and_composite_pass.h
freeze_spec_constant_value_pass.h
function.h
+ generate_webgpu_initializers_pass.h
if_conversion.h
inline_exhaustive_pass.h
inline_opaque_pass.h
@@ -92,6 +99,7 @@
simplification_pass.h
ssa_rewrite_pass.h
strength_reduction_pass.h
+ strip_atomic_counter_memory_pass.h
strip_debug_info_pass.h
strip_reflect_info_pass.h
struct_cfg_analysis.h
@@ -107,10 +115,12 @@
aggressive_dead_code_elim_pass.cpp
basic_block.cpp
block_merge_pass.cpp
+ block_merge_util.cpp
build_module.cpp
ccp_pass.cpp
cfg_cleanup_pass.cpp
cfg.cpp
+ code_sink.cpp
combine_access_chains.cpp
common_uniform_elim_pass.cpp
compact_ids_pass.cpp
@@ -121,19 +131,24 @@
dead_branch_elim_pass.cpp
dead_insert_elim_pass.cpp
dead_variable_elimination.cpp
+ decompose_initialized_variables_pass.cpp
decoration_manager.cpp
def_use_manager.cpp
dominator_analysis.cpp
dominator_tree.cpp
eliminate_dead_constant_pass.cpp
eliminate_dead_functions_pass.cpp
+ eliminate_dead_functions_util.cpp
+ eliminate_dead_members_pass.cpp
feature_manager.cpp
+ fix_storage_class.cpp
flatten_decoration_pass.cpp
fold.cpp
folding_rules.cpp
fold_spec_constant_op_and_composite_pass.cpp
freeze_spec_constant_value_pass.cpp
function.cpp
+ generate_webgpu_initializers_pass.cpp
if_conversion.cpp
inline_exhaustive_pass.cpp
inline_opaque_pass.cpp
@@ -144,6 +159,7 @@
instrument_pass.cpp
ir_context.cpp
ir_loader.cpp
+ legalize_vector_shuffle_pass.cpp
licm_pass.cpp
local_access_chain_convert_pass.cpp
local_redundancy_elimination.cpp
@@ -181,6 +197,7 @@
simplification_pass.cpp
ssa_rewrite_pass.cpp
strength_reduction_pass.cpp
+ strip_atomic_counter_memory_pass.cpp
strip_debug_info_pass.cpp
strip_reflect_info_pass.cpp
struct_cfg_analysis.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 82d7499..4063eb5 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -24,6 +24,7 @@
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/iterator.h"
#include "source/opt/reflect.h"
+#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
@@ -513,6 +514,26 @@
AddBranch(mergeBlockId, *bi);
for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
}
+
+ auto merge_terminator = (*bi)->terminator();
+ if (merge_terminator->opcode() == SpvOpUnreachable) {
+ // The merge was unreachable. This is undefined behaviour so just
+ // return (or return an undef). Then mark the new return as live.
+ auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id());
+ if (func_ret_type_inst->opcode() == SpvOpTypeVoid) {
+ merge_terminator->SetOpcode(SpvOpReturn);
+ } else {
+ // Find an undef for the return value and make sure it gets kept by
+ // the pass.
+ auto undef_id = Type2Undef(func->type_id());
+ auto undef = get_def_use_mgr()->GetDef(undef_id);
+ live_insts_.Set(undef->unique_id());
+ merge_terminator->SetOpcode(SpvOpReturnValue);
+ merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}});
+ get_def_use_mgr()->AnalyzeInstUse(merge_terminator);
+ }
+ live_insts_.Set(merge_terminator->unique_id());
+ }
} else {
++bi;
}
@@ -528,7 +549,26 @@
}
// Keep all entry points.
for (auto& entry : get_module()->entry_points()) {
- AddToWorklist(&entry);
+ if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+ // In SPIR-V 1.4 and later, entry points must list all global variables
+ // used. DCE can still remove non-input/output variables and update the
+ // interface list. Mark the entry point as live and inputs and outputs as
+ // live, but defer decisions all other interfaces.
+ live_insts_.Set(entry.unique_id());
+ // The actual function is live always.
+ AddToWorklist(
+ get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
+ for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
+ auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+ auto storage_class = var->GetSingleWordInOperand(0u);
+ if (storage_class == SpvStorageClassInput ||
+ storage_class == SpvStorageClassOutput) {
+ AddToWorklist(var);
+ }
+ }
+ } else {
+ AddToWorklist(&entry);
+ }
}
// Keep workgroup size.
for (auto& anno : get_module()->annotations()) {
@@ -546,11 +586,19 @@
// TODO(greg-lunarg): Handle additional capabilities
if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
return Status::SuccessWithoutChange;
+
// Current functionality assumes relaxed logical addressing (see
// instruction.h)
// TODO(greg-lunarg): Handle non-logical addressing
if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
+
+ // The variable pointer extension is no longer needed to use the capability,
+ // so we have to look for the capability.
+ if (context()->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer))
+ return Status::SuccessWithoutChange;
+
// If any extensions in the module are not explicitly supported,
// return unmodified.
if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
@@ -752,6 +800,29 @@
}
}
+ if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+ // Remove the dead interface variables from the entry point interface list.
+ for (auto& entry : get_module()->entry_points()) {
+ std::vector<Operand> new_operands;
+ for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+ if (i < 3) {
+ // Execution model, function id and name are always valid.
+ new_operands.push_back(entry.GetInOperand(i));
+ } else {
+ auto* var =
+ get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+ if (!IsDead(var)) {
+ new_operands.push_back(entry.GetInOperand(i));
+ }
+ }
+ }
+ if (new_operands.size() != entry.NumInOperands()) {
+ entry.SetInOperands(std::move(new_operands));
+ get_def_use_mgr()->UpdateDefUse(&entry);
+ }
+ }
+ }
+
return modified;
}
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index b4b34b0..c043a96 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -49,7 +49,9 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp
index aafee51..3608448 100644
--- a/source/opt/basic_block.cpp
+++ b/source/opt/basic_block.cpp
@@ -108,21 +108,29 @@
void BasicBlock::ForEachSuccessorLabel(
const std::function<void(const uint32_t)>& f) const {
+ WhileEachSuccessorLabel([f](const uint32_t l) {
+ f(l);
+ return true;
+ });
+}
+
+bool BasicBlock::WhileEachSuccessorLabel(
+ const std::function<bool(const uint32_t)>& f) const {
const auto br = &insts_.back();
switch (br->opcode()) {
- case SpvOpBranch: {
- f(br->GetOperand(0).words[0]);
- } break;
+ case SpvOpBranch:
+ return f(br->GetOperand(0).words[0]);
case SpvOpBranchConditional:
case SpvOpSwitch: {
bool is_first = true;
- br->ForEachInId([&is_first, &f](const uint32_t* idp) {
- if (!is_first) f(*idp);
+ return br->WhileEachInId([&is_first, &f](const uint32_t* idp) {
+ if (!is_first) return f(*idp);
is_first = false;
+ return true;
});
- } break;
+ }
default:
- break;
+ return true;
}
}
@@ -184,6 +192,12 @@
return mbid;
}
+uint32_t BasicBlock::MergeBlockId() const {
+ uint32_t mbid = MergeBlockIdIfAny();
+ assert(mbid && "Expected block to have a corresponding merge block");
+ return mbid;
+}
+
uint32_t BasicBlock::ContinueBlockIdIfAny() const {
auto merge_ii = cend();
--merge_ii;
@@ -197,6 +211,12 @@
return cbid;
}
+uint32_t BasicBlock::ContinueBlockId() const {
+ uint32_t cbid = ContinueBlockIdIfAny();
+ assert(cbid && "Expected block to have a corresponding continue target");
+ return cbid;
+}
+
std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
str << block.PrettyPrint();
return str;
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index ff3a412..0bab337 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -160,6 +160,11 @@
void ForEachSuccessorLabel(
const std::function<void(const uint32_t)>& f) const;
+ // Runs the given function |f| on each label id of each successor block. If
+ // |f| returns false, iteration is terminated and this function returns false.
+ bool WhileEachSuccessorLabel(
+ const std::function<bool(const uint32_t)>& f) const;
+
// Runs the given function |f| on each label id of each successor block.
// Modifying the pointed value will change the branch taken by the basic
// block. It is the caller responsibility to update or invalidate the CFG.
@@ -183,10 +188,16 @@
// block, if any. If none, returns zero.
uint32_t MergeBlockIdIfAny() const;
+ // Returns MergeBlockIdIfAny() and asserts that it is non-zero.
+ uint32_t MergeBlockId() const;
+
// Returns the ID of the continue block declared by a merge instruction in
// this block, if any. If none, returns zero.
uint32_t ContinueBlockIdIfAny() const;
+ // Returns ContinueBlockIdIfAny() and asserts that it is non-zero.
+ uint32_t ContinueBlockId() const;
+
// Returns the terminator instruction. Assumes the terminator exists.
Instruction* terminator() { return &*tail(); }
const Instruction* terminator() const { return &*ctail(); }
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index 09deb21..c7315ba 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -18,6 +18,7 @@
#include <vector>
+#include "source/opt/block_merge_util.h"
#include "source/opt/ir_context.h"
#include "source/opt/iterator.h"
@@ -27,112 +28,17 @@
bool BlockMergePass::MergeBlocks(Function* func) {
bool modified = false;
for (auto bi = func->begin(); bi != func->end();) {
- // Find block with single successor which has no other predecessors.
- auto ii = bi->end();
- --ii;
- Instruction* br = &*ii;
- if (br->opcode() != SpvOpBranch) {
+ if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+ blockmergeutil::MergeWithSuccessor(context(), func, bi);
+ // Reprocess block.
+ modified = true;
+ } else {
++bi;
- continue;
}
-
- const uint32_t lab_id = br->GetSingleWordInOperand(0);
- if (cfg()->preds(lab_id).size() != 1) {
- ++bi;
- continue;
- }
-
- bool pred_is_merge = IsMerge(&*bi);
- bool succ_is_merge = IsMerge(lab_id);
- if (pred_is_merge && succ_is_merge) {
- // Cannot merge two merges together.
- ++bi;
- continue;
- }
-
- Instruction* merge_inst = bi->GetMergeInst();
- bool pred_is_header = IsHeader(&*bi);
- if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
- bool succ_is_header = IsHeader(lab_id);
- if (pred_is_header && succ_is_header) {
- // Cannot merge two headers together when the successor is not the merge
- // block of the predecessor.
- ++bi;
- continue;
- }
-
- // If this is a header block and the successor is not its merge, we must
- // be careful about which blocks we are willing to merge together.
- // OpLoopMerge must be followed by a conditional or unconditional branch.
- // The merge must be a loop merge because a selection merge cannot be
- // followed by an unconditional branch.
- BasicBlock* succ_block = context()->get_instr_block(lab_id);
- SpvOp succ_term_op = succ_block->terminator()->opcode();
- assert(merge_inst->opcode() == SpvOpLoopMerge);
- if (succ_term_op != SpvOpBranch &&
- succ_term_op != SpvOpBranchConditional) {
- ++bi;
- continue;
- }
- }
-
- // Merge blocks.
- context()->KillInst(br);
- auto sbi = bi;
- for (; sbi != func->end(); ++sbi)
- if (sbi->id() == lab_id) break;
- // If bi is sbi's only predecessor, it dominates sbi and thus
- // sbi must follow bi in func's ordering.
- assert(sbi != func->end());
-
- // Update the inst-to-block mapping for the instructions in sbi.
- for (auto& inst : *sbi) {
- context()->set_instr_block(&inst, &*bi);
- }
-
- // Now actually move the instructions.
- bi->AddInstructions(&*sbi);
-
- if (merge_inst) {
- if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
- // Merging the header and merge blocks, so remove the structured control
- // flow declaration.
- context()->KillInst(merge_inst);
- } else {
- // Move the merge instruction to just before the terminator.
- merge_inst->InsertBefore(bi->terminator());
- }
- }
- context()->ReplaceAllUsesWith(lab_id, bi->id());
- context()->KillInst(sbi->GetLabelInst());
- (void)sbi.Erase();
- // Reprocess block.
- modified = true;
}
return modified;
}
-bool BlockMergePass::IsHeader(BasicBlock* block) {
- return block->GetMergeInst() != nullptr;
-}
-
-bool BlockMergePass::IsHeader(uint32_t id) {
- return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id)));
-}
-
-bool BlockMergePass::IsMerge(uint32_t id) {
- return !get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
- uint32_t index) {
- SpvOp op = user->opcode();
- if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
- return false;
- }
- return true;
- });
-}
-
-bool BlockMergePass::IsMerge(BasicBlock* block) { return IsMerge(block->id()); }
-
Pass::Status BlockMergePass::Process() {
// Process all entry point functions.
ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
diff --git a/source/opt/block_merge_pass.h b/source/opt/block_merge_pass.h
index be14b02..aabf789 100644
--- a/source/opt/block_merge_pass.h
+++ b/source/opt/block_merge_pass.h
@@ -44,7 +44,8 @@
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
@@ -53,14 +54,6 @@
// with no other predecessors. Merge these blocks into a single block.
bool MergeBlocks(Function* func);
- // Returns true if |block| (or |id|) contains a merge instruction.
- bool IsHeader(BasicBlock* block);
- bool IsHeader(uint32_t id);
-
- // Returns true if |block| (or |id|) is the merge target of a merge
- // instruction.
- bool IsMerge(BasicBlock* block);
- bool IsMerge(uint32_t id);
};
} // namespace opt
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
new file mode 100644
index 0000000..107723d
--- /dev/null
+++ b/source/opt/block_merge_util.cpp
@@ -0,0 +1,168 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "block_merge_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace blockmergeutil {
+
+namespace {
+
+// Returns true if |block| contains a merge instruction.
+bool IsHeader(BasicBlock* block) { return block->GetMergeInst() != nullptr; }
+
+// Returns true if |id| contains a merge instruction.
+bool IsHeader(IRContext* context, uint32_t id) {
+ return IsHeader(
+ context->get_instr_block(context->get_def_use_mgr()->GetDef(id)));
+}
+
+// Returns true if |id| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, uint32_t id) {
+ return !context->get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
+ uint32_t index) {
+ SpvOp op = user->opcode();
+ if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
+ return false;
+ }
+ return true;
+ });
+}
+
+// Returns true if |block| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, BasicBlock* block) {
+ return IsMerge(context, block->id());
+}
+
+// Removes any OpPhi instructions in |block|, which should have exactly one
+// predecessor, replacing uses of OpPhi ids with the ids associated with the
+// predecessor.
+void EliminateOpPhiInstructions(IRContext* context, BasicBlock* block) {
+ block->ForEachPhiInst([context](Instruction* phi) {
+ assert(2 == phi->NumInOperands() &&
+ "A block can only have one predecessor for block merging to make "
+ "sense.");
+ context->ReplaceAllUsesWith(phi->result_id(),
+ phi->GetSingleWordInOperand(0));
+ context->KillInst(phi);
+ });
+}
+
+} // Anonymous namespace
+
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
+ // Find block with single successor which has no other predecessors.
+ auto ii = block->end();
+ --ii;
+ Instruction* br = &*ii;
+ if (br->opcode() != SpvOpBranch) {
+ return false;
+ }
+
+ const uint32_t lab_id = br->GetSingleWordInOperand(0);
+ if (context->cfg()->preds(lab_id).size() != 1) {
+ return false;
+ }
+
+ bool pred_is_merge = IsMerge(context, block);
+ bool succ_is_merge = IsMerge(context, lab_id);
+ if (pred_is_merge && succ_is_merge) {
+ // Cannot merge two merges together.
+ return false;
+ }
+
+ // Don't bother trying to merge unreachable blocks.
+ if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
+ if (!dominators->IsReachable(block)) return false;
+ }
+
+ Instruction* merge_inst = block->GetMergeInst();
+ const bool pred_is_header = IsHeader(block);
+ if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
+ bool succ_is_header = IsHeader(context, lab_id);
+ if (pred_is_header && succ_is_header) {
+ // Cannot merge two headers together when the successor is not the merge
+ // block of the predecessor.
+ return false;
+ }
+
+ // If this is a header block and the successor is not its merge, we must
+ // be careful about which blocks we are willing to merge together.
+ // OpLoopMerge must be followed by a conditional or unconditional branch.
+ // The merge must be a loop merge because a selection merge cannot be
+ // followed by an unconditional branch.
+ BasicBlock* succ_block = context->get_instr_block(lab_id);
+ SpvOp succ_term_op = succ_block->terminator()->opcode();
+ assert(merge_inst->opcode() == SpvOpLoopMerge);
+ if (succ_term_op != SpvOpBranch && succ_term_op != SpvOpBranchConditional) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void MergeWithSuccessor(IRContext* context, Function* func,
+ Function::iterator bi) {
+ assert(CanMergeWithSuccessor(context, &*bi) &&
+ "Precondition failure for MergeWithSuccessor: it must be legal to "
+ "merge the block and its successor.");
+
+ auto ii = bi->end();
+ --ii;
+ Instruction* br = &*ii;
+ const uint32_t lab_id = br->GetSingleWordInOperand(0);
+ Instruction* merge_inst = bi->GetMergeInst();
+ bool pred_is_header = IsHeader(&*bi);
+
+ // Merge blocks.
+ context->KillInst(br);
+ auto sbi = bi;
+ for (; sbi != func->end(); ++sbi)
+ if (sbi->id() == lab_id) break;
+ // If bi is sbi's only predecessor, it dominates sbi and thus
+ // sbi must follow bi in func's ordering.
+ assert(sbi != func->end());
+
+ // Update the inst-to-block mapping for the instructions in sbi.
+ for (auto& inst : *sbi) {
+ context->set_instr_block(&inst, &*bi);
+ }
+
+ EliminateOpPhiInstructions(context, &*sbi);
+
+ // Now actually move the instructions.
+ bi->AddInstructions(&*sbi);
+
+ if (merge_inst) {
+ if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
+ // Merging the header and merge blocks, so remove the structured control
+ // flow declaration.
+ context->KillInst(merge_inst);
+ } else {
+ // Move the merge instruction to just before the terminator.
+ merge_inst->InsertBefore(bi->terminator());
+ }
+ }
+ context->ReplaceAllUsesWith(lab_id, bi->id());
+ context->KillInst(sbi->GetLabelInst());
+ (void)sbi.Erase();
+}
+
+} // namespace blockmergeutil
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/block_merge_util.h b/source/opt/block_merge_util.h
new file mode 100644
index 0000000..e71e3d6
--- /dev/null
+++ b/source/opt/block_merge_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+#define SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for determining when it is safe to merge blocks, and for
+// actually merging blocks, for use by various analyses and passes.
+namespace blockmergeutil {
+
+// Returns true if and only if |block| has exactly one successor and merging
+// this successor into |block| has no impact on the semantics or validity of the
+// SPIR-V module.
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block);
+
+// Requires that |bi| has a successor that can be safely merged into |bi|, and
+// performs the merge.
+void MergeWithSuccessor(IRContext* context, Function* func,
+ Function::iterator bi);
+
+} // namespace blockmergeutil
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_BLOCK_MERGE_UTIL_H_
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8356195..2de9250 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -271,6 +271,7 @@
uint32_t id = it.first;
uint32_t cst_id = it.second;
if (!IsVaryingValue(cst_id) && id != cst_id) {
+ context()->KillNamesAndDecorates(id);
retval |= context()->ReplaceAllUsesWith(id, cst_id);
}
}
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index 178fd12..527f459 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -40,7 +40,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 7e1097e..72693cb 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -150,15 +150,25 @@
void CFG::ComputePostOrderTraversal(BasicBlock* bb,
std::vector<BasicBlock*>* order,
std::unordered_set<BasicBlock*>* seen) {
- seen->insert(bb);
- static_cast<const BasicBlock*>(bb)->ForEachSuccessorLabel(
- [&order, &seen, this](const uint32_t sbid) {
- BasicBlock* succ_bb = id2block_[sbid];
- if (!seen->count(succ_bb)) {
- ComputePostOrderTraversal(succ_bb, order, seen);
- }
- });
- order->push_back(bb);
+ std::vector<BasicBlock*> stack;
+ stack.push_back(bb);
+ while (!stack.empty()) {
+ bb = stack.back();
+ seen->insert(bb);
+ static_cast<const BasicBlock*>(bb)->WhileEachSuccessorLabel(
+ [&seen, &stack, this](const uint32_t sbid) {
+ BasicBlock* succ_bb = id2block_[sbid];
+ if (!seen->count(succ_bb)) {
+ stack.push_back(succ_bb);
+ return false;
+ }
+ return true;
+ });
+ if (stack.back() == bb) {
+ order->push_back(bb);
+ stack.pop_back();
+ }
+ }
}
BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h
index afbc67c..5095428 100644
--- a/source/opt/cfg_cleanup_pass.h
+++ b/source/opt/cfg_cleanup_pass.h
@@ -30,7 +30,8 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse;
+ return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
};
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
new file mode 100644
index 0000000..9d54ee5
--- /dev/null
+++ b/source/opt/code_sink.cpp
@@ -0,0 +1,319 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "code_sink.h"
+
+#include <set>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/util/bit_vector.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status CodeSinkingPass::Process() {
+ bool modified = false;
+ for (Function& function : *get_module()) {
+ cfg()->ForEachBlockInPostOrder(function.entry().get(),
+ [&modified, this](BasicBlock* bb) {
+ if (SinkInstructionsInBB(bb)) {
+ modified = true;
+ }
+ });
+ }
+ return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
+ bool modified = false;
+ for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
+ if (SinkInstruction(&*inst)) {
+ inst = bb->rbegin();
+ modified = true;
+ }
+ }
+ return modified;
+}
+
+bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
+ if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
+ return false;
+ }
+
+ if (ReferencesMutableMemory(inst)) {
+ return false;
+ }
+
+ if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
+ Instruction* pos = &*target_bb->begin();
+ while (pos->opcode() == SpvOpPhi) {
+ pos = pos->NextNode();
+ }
+
+ inst->InsertBefore(pos);
+ context()->set_instr_block(inst, target_bb);
+ return true;
+ }
+ return false;
+}
+
+BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
+ assert(inst->result_id() != 0 && "Instruction should have a result.");
+ BasicBlock* original_bb = context()->get_instr_block(inst);
+ BasicBlock* bb = original_bb;
+
+ std::unordered_set<uint32_t> bbs_with_uses;
+ get_def_use_mgr()->ForEachUse(
+ inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
+ if (use->opcode() != SpvOpPhi) {
+ BasicBlock* use_bb = context()->get_instr_block(use);
+ if (use_bb) {
+ bbs_with_uses.insert(use_bb->id());
+ }
+ } else {
+ bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
+ }
+ });
+
+ while (true) {
+ // If |inst| is used in |bb|, then |inst| cannot be moved any further.
+ if (bbs_with_uses.count(bb->id())) {
+ break;
+ }
+
+ // If |bb| has one successor (succ_bb), and |bb| is the only predecessor
+ // of succ_bb, then |inst| can be moved to succ_bb. If succ_bb, has move
+ // then one predecessor, then moving |inst| into succ_bb could cause it to
+ // be executed more often, so the search has to stop.
+ if (bb->terminator()->opcode() == SpvOpBranch) {
+ uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
+ if (cfg()->preds(succ_bb_id).size() == 1) {
+ bb = context()->get_instr_block(succ_bb_id);
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ // The remaining checks need to know the merge node. If there is no merge
+ // instruction or an OpLoopMerge, then it is a break or continue. We could
+ // figure it out, but not worth doing it now.
+ Instruction* merge_inst = bb->GetMergeInst();
+ if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
+ break;
+ }
+
+ // Check all of the successors of |bb| it see which lead to a use of |inst|
+ // before reaching the merge node.
+ bool used_in_multiple_blocks = false;
+ uint32_t bb_used_in = 0;
+ bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
+ &bbs_with_uses](uint32_t* succ_bb_id) {
+ if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
+ if (bb_used_in == 0) {
+ bb_used_in = *succ_bb_id;
+ } else {
+ used_in_multiple_blocks = true;
+ }
+ }
+ });
+
+ // If more than one successor, which is not the merge block, uses |inst|
+ // then we have to leave |inst| in bb because there is none of the
+ // successors dominate all uses of |inst|.
+ if (used_in_multiple_blocks) {
+ break;
+ }
+
+ if (bb_used_in == 0) {
+ // If |inst| is not used before reaching the merge node, then we can move
+ // |inst| to the merge node.
+ bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
+ } else {
+ // If the only successor that leads to a used of |inst| has more than 1
+ // predecessor, then moving |inst| could cause it to be executed more
+ // often, so we cannot move it.
+ if (cfg()->preds(bb_used_in).size() != 1) {
+ break;
+ }
+
+ // If |inst| is used after the merge block, then |bb_used_in| does not
+ // dominate all of the uses. So we cannot move |inst| any further.
+ if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
+ bbs_with_uses)) {
+ break;
+ }
+
+ // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
+ // block.
+ bb = context()->get_instr_block(bb_used_in);
+ }
+ continue;
+ }
+ return (bb != original_bb ? bb : nullptr);
+}
+
+bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
+ if (!inst->IsLoad()) {
+ return false;
+ }
+
+ Instruction* base_ptr = inst->GetBaseAddress();
+ if (base_ptr->opcode() != SpvOpVariable) {
+ return true;
+ }
+
+ if (base_ptr->IsReadOnlyVariable()) {
+ return false;
+ }
+
+ if (HasUniformMemorySync()) {
+ return true;
+ }
+
+ if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+ return true;
+ }
+
+ return HasPossibleStore(base_ptr);
+}
+
+bool CodeSinkingPass::HasUniformMemorySync() {
+ if (checked_for_uniform_sync_) {
+ return has_uniform_sync_;
+ }
+
+ bool has_sync = false;
+ get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
+ switch (inst->opcode()) {
+ case SpvOpMemoryBarrier: {
+ uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
+ if (IsSyncOnUniform(mem_semantics_id)) {
+ has_sync = true;
+ }
+ break;
+ }
+ case SpvOpControlBarrier:
+ case SpvOpAtomicLoad:
+ case SpvOpAtomicStore:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ case SpvOpAtomicFlagClear: {
+ uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
+ if (IsSyncOnUniform(mem_semantics_id)) {
+ has_sync = true;
+ }
+ break;
+ }
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
+ IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
+ has_sync = true;
+ }
+ break;
+ default:
+ break;
+ }
+ });
+ has_uniform_sync_ = has_sync;
+ return has_sync;
+}
+
+bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
+ const analysis::Constant* mem_semantics_const =
+ context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
+ assert(mem_semantics_const != nullptr &&
+ "Expecting memory semantics id to be a constant.");
+ assert(mem_semantics_const->AsIntConstant() &&
+ "Memory semantics should be an integer.");
+ uint32_t mem_semantics_int = mem_semantics_const->GetU32();
+
+ // If it does not affect uniform memory, then it is does not apply to uniform
+ // memory.
+ if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
+ return false;
+ }
+
+ // Check if there is an acquire or release. If so not, this it does not add
+ // any memory constraints.
+ return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
+ SpvMemorySemanticsAcquireReleaseMask |
+ SpvMemorySemanticsReleaseMask)) != 0;
+}
+
+bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
+ assert(var_inst->opcode() == SpvOpVariable ||
+ var_inst->opcode() == SpvOpAccessChain ||
+ var_inst->opcode() == SpvOpPtrAccessChain);
+
+ return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
+ switch (use->opcode()) {
+ case SpvOpStore:
+ return true;
+ case SpvOpAccessChain:
+ case SpvOpPtrAccessChain:
+ return HasPossibleStore(use);
+ default:
+ return false;
+ }
+ });
+}
+
+bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
+ const std::unordered_set<uint32_t>& set) {
+ std::vector<uint32_t> worklist;
+ worklist.push_back(start);
+ std::unordered_set<uint32_t> already_done;
+ already_done.insert(start);
+
+ while (!worklist.empty()) {
+ BasicBlock* bb = context()->get_instr_block(worklist.back());
+ worklist.pop_back();
+
+ if (bb->id() == end) {
+ continue;
+ }
+
+ if (set.count(bb->id())) {
+ return true;
+ }
+
+ bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
+ if (already_done.insert(*succ_bb_id).second) {
+ worklist.push_back(*succ_bb_id);
+ }
+ });
+ }
+ return false;
+}
+
+// namespace opt
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/code_sink.h b/source/opt/code_sink.h
new file mode 100644
index 0000000..d24df03
--- /dev/null
+++ b/source/opt/code_sink.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_CODE_SINK_H_
+#define SOURCE_OPT_CODE_SINK_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass does code sinking for OpAccessChain and OpLoad on variables in
+// uniform storage or in read only memory. Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+//
+// The goal is to move these instructions as close as possible to their uses
+// without having to execute them more often or to replicate the instruction.
+// Moving the instruction in this way can lead to shorter live ranges, which can
+// lead to less register pressure. It can also cause instructions to be
+// executed less often because they could be moved into one path of a selection
+// construct.
+//
+// This optimization can cause register pressure to rise if the operands of the
+// instructions go dead after the instructions being moved. That is why we only
+// move certain OpLoad and OpAccessChain instructions. They generally have
+// constants, loop induction variables, and global pointers as operands. The
+// operands are live for a longer time in most cases.
+class CodeSinkingPass : public Pass {
+ public:
+ const char* name() const override { return "code-sink"; }
+ Status Process() override;
+
+ // Return the mask of preserved Analyses.
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+ IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+ }
+
+ private:
+ // Sinks the instructions in |bb| as much as possible. Returns true if
+ // something changes.
+ bool SinkInstructionsInBB(BasicBlock* bb);
+
+ // Tries the sink |inst| as much as possible. Returns true if the instruction
+ // is moved.
+ bool SinkInstruction(Instruction* inst);
+
+ // Returns the basic block in which to move |inst| to move is as close as
+ // possible to the uses of |inst| without increasing the number of times
+ // |inst| will be executed. Return |nullptr| if there is no need to move
+ // |inst|.
+ BasicBlock* FindNewBasicBlockFor(Instruction* inst);
+
+ // Return true if |inst| reference memory and it is possible that the data in
+ // the memory changes at some point.
+ bool ReferencesMutableMemory(Instruction* inst);
+
+ // Returns true if the module contains an instruction that has a memory
+ // semantics id as an operand, and the memory semantics enforces a
+ // synchronization of uniform memory. See section 3.25 of the SPIR-V
+ // specification.
+ bool HasUniformMemorySync();
+
+ // Returns true if there may be a store to the variable |var_inst|.
+ bool HasPossibleStore(Instruction* var_inst);
+
+ // Returns true if one of the basic blocks in |set| exists on a path from the
+ // basic block |start| to |end|.
+ bool IntersectsPath(uint32_t start, uint32_t end,
+ const std::unordered_set<uint32_t>& set);
+
+ // Returns true if |mem_semantics_id| is the id of a constant that, when
+ // interpreted as a memory semantics mask enforces synchronization of uniform
+ // memory. See section 3.25 of the SPIR-V specification.
+ bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
+
+ // True if a check has for uniform storage has taken place.
+ bool checked_for_uniform_sync_;
+
+ // Cache of whether or not the module has a memory sync on uniform storage.
+ // only valid if |check_for_uniform_sync_| is true.
+ bool has_uniform_sync_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_CODE_SINK_H_
diff --git a/source/opt/combine_access_chains.h b/source/opt/combine_access_chains.h
index 75885da..531209e 100644
--- a/source/opt/combine_access_chains.h
+++ b/source/opt/combine_access_chains.h
@@ -33,7 +33,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/common_uniform_elim_pass.cpp b/source/opt/common_uniform_elim_pass.cpp
index efa40aa..7762fba 100644
--- a/source/opt/common_uniform_elim_pass.cpp
+++ b/source/opt/common_uniform_elim_pass.cpp
@@ -282,6 +282,7 @@
uint32_t replId;
GenACLoadRepl(ptrInst, &newInsts, &replId);
inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
+ assert(inst->opcode() != SpvOpPhi);
inst = inst->InsertBefore(std::move(newInsts));
modified = true;
}
@@ -355,6 +356,10 @@
if (mergeBlockId == bp->id()) {
mergeBlockId = 0;
insertItr = bp->begin();
+ while (insertItr->opcode() == SpvOpPhi) {
+ ++insertItr;
+ }
+
// Update insertItr until it will not be removed. Without this code,
// ReplaceAndDeleteLoad() can set |insertItr| as a dangling pointer.
while (IsUniformLoadToBeRemoved(&*insertItr)) ++insertItr;
@@ -509,6 +514,9 @@
// TODO(greg-lunarg): Add support for physical addressing
if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
+ if (context()->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointersStorageBuffer))
+ return Status::SuccessWithoutChange;
// Do not process if any disallowed extensions are enabled
if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
// Do not process if module contains OpGroupDecorate. Additional
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index f6013a3..3df5a83 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -582,13 +582,17 @@
std::vector<uint32_t> words = result.GetWords();
const analysis::Constant* result_const =
const_mgr->GetConstant(float_type, words);
- for (uint32_t i = 0; i < a_components.size(); ++i) {
+ for (uint32_t i = 0; i < a_components.size() && result_const != nullptr;
+ ++i) {
if (a_components[i] == nullptr || b_components[i] == nullptr) {
return nullptr;
}
const analysis::Constant* component = FOLD_FPARITH_OP(*)(
new_type, a_components[i], b_components[i], const_mgr);
+ if (component == nullptr) {
+ return nullptr;
+ }
result_const =
FOLD_FPARITH_OP(+)(new_type, result_const, component, const_mgr);
}
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index e508c05..751786c 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -563,11 +563,6 @@
}
void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
Instruction* new_ptr_inst) {
- // TODO (s-perron): Keep the def-use manager up to date. Not done now because
- // it can cause problems for the |ForEachUse| traversals. Can be use by
- // keeping a list of instructions that need updating, and then updating them
- // in |PropagateObject|.
-
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -703,74 +698,6 @@
}
}
-uint32_t CopyPropagateArrays::GenerateCopy(Instruction* object_inst,
- uint32_t new_type_id,
- Instruction* insertion_position) {
- analysis::TypeManager* type_mgr = context()->get_type_mgr();
- analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
- uint32_t original_type_id = object_inst->type_id();
- if (original_type_id == new_type_id) {
- return object_inst->result_id();
- }
-
- InstructionBuilder ir_builder(
- context(), insertion_position,
- IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
-
- analysis::Type* original_type = type_mgr->GetType(original_type_id);
- analysis::Type* new_type = type_mgr->GetType(new_type_id);
-
- if (const analysis::Array* original_array_type = original_type->AsArray()) {
- uint32_t original_element_type_id =
- type_mgr->GetId(original_array_type->element_type());
-
- analysis::Array* new_array_type = new_type->AsArray();
- assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
- uint32_t new_element_type_id =
- type_mgr->GetId(new_array_type->element_type());
-
- std::vector<uint32_t> element_ids;
- const analysis::Constant* length_const =
- const_mgr->FindDeclaredConstant(original_array_type->LengthId());
- assert(length_const->AsIntConstant());
- uint32_t array_length = length_const->AsIntConstant()->GetU32();
- for (uint32_t i = 0; i < array_length; i++) {
- Instruction* extract = ir_builder.AddCompositeExtract(
- original_element_type_id, object_inst->result_id(), {i});
- element_ids.push_back(
- GenerateCopy(extract, new_element_type_id, insertion_position));
- }
-
- return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
- ->result_id();
- } else if (const analysis::Struct* original_struct_type =
- original_type->AsStruct()) {
- analysis::Struct* new_struct_type = new_type->AsStruct();
-
- const std::vector<const analysis::Type*>& original_types =
- original_struct_type->element_types();
- const std::vector<const analysis::Type*>& new_types =
- new_struct_type->element_types();
- std::vector<uint32_t> element_ids;
- for (uint32_t i = 0; i < original_types.size(); i++) {
- Instruction* extract = ir_builder.AddCompositeExtract(
- type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
- element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
- insertion_position));
- }
- return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
- ->result_id();
- } else {
- // If we do not have an aggregate type, then we have a problem. Either we
- // found multiple instances of the same type, or we are copying to an
- // incompatible type. Either way the code is illegal.
- assert(false &&
- "Don't know how to copy this type. Code is likely illegal.");
- }
- return 0;
-}
-
uint32_t CopyPropagateArrays::GetMemberTypeId(
uint32_t id, const std::vector<uint32_t>& access_chain) const {
for (uint32_t element_index : access_chain) {
diff --git a/source/opt/copy_prop_arrays.h b/source/opt/copy_prop_arrays.h
index ed9da0e..f4314a7 100644
--- a/source/opt/copy_prop_arrays.h
+++ b/source/opt/copy_prop_arrays.h
@@ -47,7 +47,8 @@
return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
- IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
@@ -216,12 +217,6 @@
// |original_ptr_inst| to |type_id| and still have valid code.
bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id);
- // Returns the id whose value is the same as |object_to_copy| except its type
- // is |new_type_id|. Any instructions need to generate this value will be
- // inserted before |insertion_position|.
- uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
- Instruction* insertion_position);
-
// Returns a store to |var_inst| that writes to the entire variable, and is
// the only store that does so. Note it does not look through OpAccessChain
// instruction, so partial stores are not considered.
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 9893536..087c2af 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -95,7 +95,7 @@
Function* func, std::unordered_set<BasicBlock*>* live_blocks) {
StructuredCFGAnalysis* cfgAnalysis = context()->GetStructuredCFGAnalysis();
- std::unordered_set<BasicBlock*> continues;
+ std::unordered_set<BasicBlock*> blocks_with_backedge;
std::vector<BasicBlock*> stack;
stack.push_back(&*func->begin());
bool modified = false;
@@ -107,7 +107,10 @@
if (!live_blocks->insert(block).second) continue;
uint32_t cont_id = block->ContinueBlockIdIfAny();
- if (cont_id != 0) continues.insert(GetParentBlock(cont_id));
+ if (cont_id != 0) {
+ AddBlocksWithBackEdge(cont_id, block->id(), block->MergeBlockIdIfAny(),
+ &blocks_with_backedge);
+ }
Instruction* terminator = block->terminator();
uint32_t live_lab_id = 0;
@@ -146,13 +149,23 @@
}
}
- // Don't simplify branches of continue blocks. A path from the continue to
- // the header is required.
- // TODO(alan-baker): They can be simplified iff there remains a path to the
- // backedge. Structured control flow should guarantee one path hits the
- // backedge, but I've removed the requirement for structured control flow
- // from this pass.
- bool simplify = live_lab_id != 0 && !continues.count(block);
+ // Don't simplify back edges unless it becomes a branch to the header. Every
+ // loop must have exactly one back edge to the loop header, so we cannot
+ // remove it.
+ bool simplify = false;
+ if (live_lab_id != 0) {
+ if (!blocks_with_backedge.count(block)) {
+ // This is not a back edge.
+ simplify = true;
+ } else {
+ const auto& struct_cfg_analysis = context()->GetStructuredCFGAnalysis();
+ uint32_t header_id = struct_cfg_analysis->ContainingLoop(block->id());
+ if (live_lab_id == header_id) {
+ // The new branch will be a branch to the header.
+ simplify = true;
+ }
+ }
+ }
if (simplify) {
modified = true;
@@ -323,21 +336,7 @@
const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) {
bool modified = false;
for (auto ebi = func->begin(); ebi != func->end();) {
- if (unreachable_merges.count(&*ebi)) {
- if (ebi->begin() != ebi->tail() ||
- ebi->terminator()->opcode() != SpvOpUnreachable) {
- // Make unreachable, but leave the label.
- KillAllInsts(&*ebi, false);
- // Add unreachable terminator.
- ebi->AddInstruction(
- MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
- std::initializer_list<Operand>{}));
- context()->AnalyzeUses(ebi->terminator());
- context()->set_instr_block(ebi->terminator(), &*ebi);
- modified = true;
- }
- ++ebi;
- } else if (unreachable_continues.count(&*ebi)) {
+ if (unreachable_continues.count(&*ebi)) {
uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id();
if (ebi->begin() != ebi->tail() ||
ebi->terminator()->opcode() != SpvOpBranch ||
@@ -354,6 +353,20 @@
modified = true;
}
++ebi;
+ } else if (unreachable_merges.count(&*ebi)) {
+ if (ebi->begin() != ebi->tail() ||
+ ebi->terminator()->opcode() != SpvOpUnreachable) {
+ // Make unreachable, but leave the label.
+ KillAllInsts(&*ebi, false);
+ // Add unreachable terminator.
+ ebi->AddInstruction(
+ MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
+ std::initializer_list<Operand>{}));
+ context()->AnalyzeUses(ebi->terminator());
+ context()->set_instr_block(ebi->terminator(), &*ebi);
+ modified = true;
+ }
+ ++ebi;
} else if (!live_blocks.count(&*ebi)) {
// Kill this block.
KillAllInsts(&*ebi);
@@ -538,5 +551,39 @@
return nullptr;
}
+void DeadBranchElimPass::AddBlocksWithBackEdge(
+ uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+ std::unordered_set<BasicBlock*>* blocks_with_back_edges) {
+ std::unordered_set<uint32_t> visited;
+ visited.insert(cont_id);
+ visited.insert(header_id);
+ visited.insert(merge_id);
+
+ std::vector<uint32_t> work_list;
+ work_list.push_back(cont_id);
+
+ while (!work_list.empty()) {
+ uint32_t bb_id = work_list.back();
+ work_list.pop_back();
+
+ BasicBlock* bb = context()->get_instr_block(bb_id);
+
+ bool has_back_edge = false;
+ bb->ForEachSuccessorLabel([header_id, &visited, &work_list,
+ &has_back_edge](uint32_t* succ_label_id) {
+ if (visited.insert(*succ_label_id).second) {
+ work_list.push_back(*succ_label_id);
+ }
+ if (*succ_label_id == header_id) {
+ has_back_edge = true;
+ }
+ });
+
+ if (has_back_edge) {
+ blocks_with_back_edges->insert(bb);
+ }
+ }
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index a3a4f5a..2ced589 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -44,7 +44,9 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
@@ -146,6 +148,14 @@
uint32_t merge_block_id,
uint32_t loop_merge_id,
uint32_t loop_continue_id);
+
+ // Adds to |blocks_with_back_edges| all of the blocks on the path from the
+ // basic block |cont_id| to |header_id| and |merge_id|. The intention is that
+ // |cond_id| is a the continue target of a loop, |header_id| is the header of
+ // the loop, and |merge_id| is the merge block of the loop.
+ void AddBlocksWithBackEdge(
+ uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+ std::unordered_set<BasicBlock*>* blocks_with_back_edges);
};
} // namespace opt
diff --git a/source/opt/dead_insert_elim_pass.h b/source/opt/dead_insert_elim_pass.h
index 0b111d0..01f12bb 100644
--- a/source/opt/dead_insert_elim_pass.h
+++ b/source/opt/dead_insert_elim_pass.h
@@ -45,7 +45,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/dead_variable_elimination.h b/source/opt/dead_variable_elimination.h
index 40a7bc0..5dde71b 100644
--- a/source/opt/dead_variable_elimination.h
+++ b/source/opt/dead_variable_elimination.h
@@ -30,7 +30,8 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse;
+ return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/decompose_initialized_variables_pass.cpp b/source/opt/decompose_initialized_variables_pass.cpp
new file mode 100644
index 0000000..875bf7e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/decompose_initialized_variables_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool HasInitializer(Instruction* inst) {
+ if (inst->opcode() != SpvOpVariable) return false;
+ if (inst->NumOperands() < 4) return false;
+
+ return true;
+}
+
+} // namespace
+
+Pass::Status DecomposeInitializedVariablesPass::Process() {
+ auto* module = context()->module();
+ std::unordered_set<Instruction*> changed;
+
+ std::vector<std::tuple<uint32_t, uint32_t>> global_stores;
+ for (auto iter = module->types_values_begin();
+ iter != module->types_values_end(); ++iter) {
+ Instruction* inst = &(*iter);
+ if (!HasInitializer(inst)) continue;
+
+ auto var_id = inst->result_id();
+ auto val_id = inst->GetOperand(3).words[0];
+ global_stores.push_back(std::make_tuple(var_id, val_id));
+ iter->RemoveOperand(3);
+ changed.insert(&*iter);
+ }
+
+ std::unordered_set<uint32_t> entry_ids;
+ for (auto entry = module->entry_points().begin();
+ entry != module->entry_points().end(); ++entry) {
+ entry_ids.insert(entry->GetSingleWordInOperand(1));
+ }
+
+ for (auto func = module->begin(); func != module->end(); ++func) {
+ std::vector<Instruction*> function_stores;
+ auto first_block = func->entry().get();
+ inst_iterator insert_point = first_block->begin();
+ for (auto iter = first_block->begin();
+ iter != first_block->end() && iter->opcode() == SpvOpVariable;
+ ++iter) {
+ // For valid SPIRV-V, there is guaranteed to be at least one instruction
+ // after the OpVariable instructions.
+ insert_point = (*iter).NextNode();
+ Instruction* inst = &(*iter);
+ if (!HasInitializer(inst)) continue;
+
+ auto var_id = inst->result_id();
+ auto val_id = inst->GetOperand(3).words[0];
+ Instruction* store_inst = new Instruction(
+ context(), SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+ function_stores.push_back(store_inst);
+ iter->RemoveOperand(3);
+ changed.insert(&*iter);
+ }
+
+ if (entry_ids.find(func->result_id()) != entry_ids.end()) {
+ for (auto store_ids : global_stores) {
+ uint32_t var_id;
+ uint32_t val_id;
+ std::tie(var_id, val_id) = store_ids;
+ auto* store_inst = new Instruction(
+ context(), SpvOpStore, 0, 0,
+ {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+ context()->set_instr_block(store_inst, &*first_block);
+ first_block->AddInstruction(std::unique_ptr<Instruction>(store_inst));
+ store_inst->InsertBefore(&*insert_point);
+ changed.insert(store_inst);
+ }
+ }
+
+ for (auto store = function_stores.begin(); store != function_stores.end();
+ ++store) {
+ context()->set_instr_block(*store, first_block);
+ (*store)->InsertBefore(&*insert_point);
+ changed.insert(*store);
+ }
+ }
+
+ auto* def_use_mgr = get_def_use_mgr();
+ for (auto* inst : changed) def_use_mgr->UpdateDefUse(inst);
+
+ return !changed.empty() ? Pass::Status::SuccessWithChange
+ : Pass::Status::SuccessWithoutChange;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/decompose_initialized_variables_pass.h b/source/opt/decompose_initialized_variables_pass.h
new file mode 100644
index 0000000..c0bd35e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+#define SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts variable declartions with initializers into seperate declaration and
+// assignment statements. This is done due to known issues with some Vulkan
+// implementations' handling of initialized variables.
+//
+// Only decomposes variables with storage classes that are valid in Vulkan
+// execution environments; Output, Private, and Function.
+// Currently only Function is implemented.
+class DecomposeInitializedVariablesPass : public Pass {
+ public:
+ const char* name() const override {
+ return "decompose-initialized-variables";
+ }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisScalarEvolution |
+ IRContext::kAnalysisRegisterPressure |
+ IRContext::kAnalysisValueNumberTable |
+ IRContext::kAnalysisStructuredCFG |
+ IRContext::kAnalysisBuiltinVarId |
+ IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+ }
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
diff --git a/source/opt/eliminate_dead_functions_pass.cpp b/source/opt/eliminate_dead_functions_pass.cpp
index f067be5..a465521 100644
--- a/source/opt/eliminate_dead_functions_pass.cpp
+++ b/source/opt/eliminate_dead_functions_pass.cpp
@@ -13,6 +13,7 @@
// limitations under the License.
#include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_functions_util.h"
#include <unordered_set>
@@ -36,8 +37,8 @@
funcIter != get_module()->end();) {
if (live_function_set.count(&*funcIter) == 0) {
modified = true;
- EliminateFunction(&*funcIter);
- funcIter = funcIter.Erase();
+ funcIter =
+ eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
} else {
++funcIter;
}
@@ -47,10 +48,5 @@
: Pass::Status::SuccessWithoutChange;
}
-void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
- // Remove all of the instruction in the function body
- func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
- true);
-}
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_pass.h b/source/opt/eliminate_dead_functions_pass.h
index 165e9a6..6ed5c42 100644
--- a/source/opt/eliminate_dead_functions_pass.h
+++ b/source/opt/eliminate_dead_functions_pass.h
@@ -30,7 +30,8 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse;
+ return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
new file mode 100644
index 0000000..8a38959
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace eliminatedeadfunctionsutil {
+
+Module::iterator EliminateFunction(IRContext* context,
+ Module::iterator* func_iter) {
+ (*func_iter)
+ ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
+ true);
+ return func_iter->Erase();
+}
+
+} // namespace eliminatedeadfunctionsutil
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_util.h b/source/opt/eliminate_dead_functions_util.h
new file mode 100644
index 0000000..9fcce95
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functionality for eliminating functions that are not needed, for use
+// by various analyses and passes.
+namespace eliminatedeadfunctionsutil {
+
+// Removes all of the function's instructions, removes the function from the
+// module, and returns the next iterator.
+Module::iterator EliminateFunction(IRContext* context,
+ Module::iterator* func_iter);
+
+} // namespace eliminatedeadfunctionsutil
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
new file mode 100644
index 0000000..0b73b2d
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -0,0 +1,637 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/eliminate_dead_members_pass.h"
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+const uint32_t kRemovedMember = 0xFFFFFFFF;
+}
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadMembersPass::Process() {
+ if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+ return Status::SuccessWithoutChange;
+
+ FindLiveMembers();
+ if (RemoveDeadMembers()) {
+ return Status::SuccessWithChange;
+ }
+ return Status::SuccessWithoutChange;
+}
+
+void EliminateDeadMembersPass::FindLiveMembers() {
+ // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+ // we have to mark them as fully used just to be safe.
+ for (auto& inst : get_module()->types_values()) {
+ if (inst.opcode() == SpvOpSpecConstantOp) {
+ MarkTypeAsFullyUsed(inst.type_id());
+ } else if (inst.opcode() == SpvOpVariable) {
+ switch (inst.GetSingleWordInOperand(0)) {
+ case SpvStorageClassInput:
+ case SpvStorageClassOutput:
+ MarkPointeeTypeAsFullUsed(inst.type_id());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ for (const Function& func : *get_module()) {
+ FindLiveMembers(func);
+ }
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
+ function.ForEachInst(
+ [this](const Instruction* inst) { FindLiveMembers(inst); });
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
+ switch (inst->opcode()) {
+ case SpvOpStore:
+ MarkMembersAsLiveForStore(inst);
+ break;
+ case SpvOpCopyMemory:
+ case SpvOpCopyMemorySized:
+ MarkMembersAsLiveForCopyMemory(inst);
+ break;
+ case SpvOpCompositeExtract:
+ MarkMembersAsLiveForExtract(inst);
+ break;
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain:
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsPtrAccessChain:
+ MarkMembersAsLiveForAccessChain(inst);
+ break;
+ case SpvOpReturnValue:
+ // This should be an issue only if we are returning from the entry point.
+ // However, for now I will keep it more conservative because functions are
+ // often inlined leaving only the entry points.
+ MarkOperandTypeAsFullyUsed(inst, 0);
+ break;
+ case SpvOpArrayLength:
+ MarkMembersAsLiveForArrayLength(inst);
+ break;
+ case SpvOpLoad:
+ case SpvOpCompositeInsert:
+ case SpvOpCompositeConstruct:
+ break;
+ default:
+ // This path is here for safety. All instructions that can reference
+ // structs in a function body should be handled above. However, this will
+ // keep the pass valid, but not optimal, as new instructions get added
+ // or if something was missed.
+ MarkStructOperandsAsFullyUsed(inst);
+ break;
+ }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
+ const Instruction* inst) {
+ // We should only have to mark the members as live if the store is to
+ // memory that is read outside of the shader. Other passes can remove all
+ // store to memory that is not visible outside of the shader, so we do not
+ // complicate the code for now.
+ assert(inst->opcode() == SpvOpStore);
+ uint32_t object_id = inst->GetSingleWordInOperand(1);
+ Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
+ uint32_t object_type_id = object_inst->type_id();
+ MarkTypeAsFullyUsed(object_type_id);
+}
+
+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));
+ }
+}
+
+void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) {
+ Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
+ assert(ptr_type_inst->opcode() == SpvOpTypePointer);
+ MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1));
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
+ const Instruction* inst) {
+ uint32_t target_id = inst->GetSingleWordInOperand(0);
+ Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
+ uint32_t pointer_type_id = target_inst->type_id();
+ Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+ uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+ MarkTypeAsFullyUsed(type_id);
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
+ const Instruction* inst) {
+ assert(inst->opcode() == SpvOpCompositeExtract);
+
+ uint32_t composite_id = inst->GetSingleWordInOperand(0);
+ Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+ uint32_t type_id = composite_inst->type_id();
+
+ for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ uint32_t member_idx = inst->GetSingleWordInOperand(i);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeStruct:
+ used_members_[type_id].insert(member_idx);
+ type_id = type_inst->GetSingleWordInOperand(member_idx);
+ break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ type_id = type_inst->GetSingleWordInOperand(0);
+ break;
+ default:
+ assert(false);
+ }
+ }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
+ const Instruction* inst) {
+ assert(inst->opcode() == SpvOpAccessChain ||
+ inst->opcode() == SpvOpInBoundsAccessChain ||
+ inst->opcode() == SpvOpPtrAccessChain ||
+ inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+ uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+ Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+ uint32_t pointer_type_id = pointer_inst->type_id();
+ Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+ uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+ analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+ // For a pointer access chain, we need to skip the |element| index. It is not
+ // a reference to the member of a struct, and it does not change the type.
+ uint32_t i = (inst->opcode() == SpvOpAccessChain ||
+ inst->opcode() == SpvOpInBoundsAccessChain
+ ? 1
+ : 2);
+ for (; i < inst->NumInOperands(); ++i) {
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeStruct: {
+ const analysis::IntConstant* member_idx =
+ const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+ ->AsIntConstant();
+ assert(member_idx);
+ if (member_idx->type()->AsInteger()->width() == 32) {
+ used_members_[type_id].insert(member_idx->GetU32());
+ type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
+ } else {
+ used_members_[type_id].insert(
+ static_cast<uint32_t>(member_idx->GetU64()));
+ type_id = type_inst->GetSingleWordInOperand(
+ static_cast<uint32_t>(member_idx->GetU64()));
+ }
+ } break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ type_id = type_inst->GetSingleWordInOperand(0);
+ break;
+ default:
+ assert(false);
+ }
+ }
+}
+
+void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
+ const Instruction* inst, uint32_t in_idx) {
+ uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
+ Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
+ MarkTypeAsFullyUsed(op_inst->type_id());
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
+ const Instruction* inst) {
+ assert(inst->opcode() == SpvOpArrayLength);
+ uint32_t object_id = inst->GetSingleWordInOperand(0);
+ Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+ uint32_t pointer_type_id = object_inst->type_id();
+ Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+ uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+ used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
+}
+
+bool EliminateDeadMembersPass::RemoveDeadMembers() {
+ bool modified = false;
+
+ // First update all of the OpTypeStruct instructions.
+ get_module()->ForEachInst([&modified, this](Instruction* inst) {
+ switch (inst->opcode()) {
+ case SpvOpTypeStruct:
+ modified |= UpdateOpTypeStruct(inst);
+ break;
+ default:
+ break;
+ }
+ });
+
+ // Now update all of the instructions that reference the OpTypeStructs.
+ get_module()->ForEachInst([&modified, this](Instruction* inst) {
+ switch (inst->opcode()) {
+ case SpvOpMemberName:
+ modified |= UpdateOpMemberNameOrDecorate(inst);
+ break;
+ case SpvOpMemberDecorate:
+ modified |= UpdateOpMemberNameOrDecorate(inst);
+ break;
+ case SpvOpGroupMemberDecorate:
+ modified |= UpdateOpGroupMemberDecorate(inst);
+ break;
+ case SpvOpSpecConstantComposite:
+ case SpvOpConstantComposite:
+ case SpvOpCompositeConstruct:
+ modified |= UpdateConstantComposite(inst);
+ break;
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain:
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsPtrAccessChain:
+ modified |= UpdateAccessChain(inst);
+ break;
+ case SpvOpCompositeExtract:
+ modified |= UpdateCompsiteExtract(inst);
+ break;
+ case SpvOpCompositeInsert:
+ modified |= UpdateCompositeInsert(inst);
+ break;
+ case SpvOpArrayLength:
+ modified |= UpdateOpArrayLength(inst);
+ break;
+ case SpvOpSpecConstantOp:
+ assert(false && "Not yet implemented.");
+ // with OpCompositeExtract, OpCompositeInsert
+ // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
+ // OpInBoundsPtrAccessChain
+ break;
+ default:
+ break;
+ }
+ });
+ return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
+ assert(inst->opcode() == SpvOpTypeStruct);
+
+ const auto& live_members = used_members_[inst->result_id()];
+ if (live_members.size() == inst->NumInOperands()) {
+ return false;
+ }
+
+ Instruction::OperandList new_operands;
+ for (uint32_t idx : live_members) {
+ new_operands.emplace_back(inst->GetInOperand(idx));
+ }
+
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
+ assert(inst->opcode() == SpvOpMemberName ||
+ inst->opcode() == SpvOpMemberDecorate);
+
+ uint32_t type_id = inst->GetSingleWordInOperand(0);
+ auto live_members = used_members_.find(type_id);
+ if (live_members == used_members_.end()) {
+ return false;
+ }
+
+ uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+
+ if (new_member_idx == kRemovedMember) {
+ context()->KillInst(inst);
+ return true;
+ }
+
+ if (new_member_idx == orig_member_idx) {
+ return false;
+ }
+
+ inst->SetInOperand(1, {new_member_idx});
+ return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
+ assert(inst->opcode() == SpvOpGroupMemberDecorate);
+
+ bool modified = false;
+
+ Instruction::OperandList new_operands;
+ new_operands.emplace_back(inst->GetInOperand(0));
+ for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+ uint32_t type_id = inst->GetSingleWordInOperand(i);
+ uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+
+ if (new_member_idx == kRemovedMember) {
+ modified = true;
+ continue;
+ }
+
+ new_operands.emplace_back(inst->GetOperand(i));
+ if (new_member_idx != member_idx) {
+ new_operands.emplace_back(
+ Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+ modified = true;
+ } else {
+ new_operands.emplace_back(inst->GetOperand(i + 1));
+ }
+ }
+
+ if (!modified) {
+ return false;
+ }
+
+ if (new_operands.size() == 1) {
+ context()->KillInst(inst);
+ return true;
+ }
+
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
+ assert(inst->opcode() == SpvOpConstantComposite ||
+ inst->opcode() == SpvOpCompositeConstruct);
+ uint32_t type_id = inst->type_id();
+
+ bool modified = false;
+ Instruction::OperandList new_operands;
+ for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+ uint32_t new_idx = GetNewMemberIndex(type_id, i);
+ if (new_idx == kRemovedMember) {
+ modified = true;
+ } else {
+ new_operands.emplace_back(inst->GetInOperand(i));
+ }
+ }
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
+ assert(inst->opcode() == SpvOpAccessChain ||
+ inst->opcode() == SpvOpInBoundsAccessChain ||
+ inst->opcode() == SpvOpPtrAccessChain ||
+ inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+ uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+ Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+ uint32_t pointer_type_id = pointer_inst->type_id();
+ Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+ uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+ analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+ Instruction::OperandList new_operands;
+ bool modified = false;
+ new_operands.emplace_back(inst->GetInOperand(0));
+
+ // For pointer access chains we want to copy the element operand.
+ if (inst->opcode() == SpvOpPtrAccessChain ||
+ inst->opcode() == SpvOpInBoundsPtrAccessChain) {
+ new_operands.emplace_back(inst->GetInOperand(1));
+ }
+
+ for (uint32_t i = static_cast<uint32_t>(new_operands.size());
+ i < inst->NumInOperands(); ++i) {
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeStruct: {
+ const analysis::IntConstant* member_idx =
+ const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+ ->AsIntConstant();
+ assert(member_idx);
+ uint32_t orig_member_idx;
+ if (member_idx->type()->AsInteger()->width() == 32) {
+ orig_member_idx = member_idx->GetU32();
+ } else {
+ orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
+ }
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+ assert(new_member_idx != kRemovedMember);
+ if (orig_member_idx != new_member_idx) {
+ InstructionBuilder ir_builder(
+ context(), inst,
+ IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping);
+ uint32_t const_id =
+ ir_builder.GetUintConstant(new_member_idx)->result_id();
+ new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
+ modified = true;
+ } else {
+ new_operands.emplace_back(inst->GetInOperand(i));
+ }
+ // The type will have already been rewritten, so use the new member
+ // index.
+ type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+ } break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ new_operands.emplace_back(inst->GetInOperand(i));
+ type_id = type_inst->GetSingleWordInOperand(0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+
+ if (!modified) {
+ return false;
+ }
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
+ uint32_t member_idx) {
+ auto live_members = used_members_.find(type_id);
+ if (live_members == used_members_.end()) {
+ return member_idx;
+ }
+
+ auto current_member = live_members->second.find(member_idx);
+ if (current_member == live_members->second.end()) {
+ return kRemovedMember;
+ }
+
+ return static_cast<uint32_t>(
+ std::distance(live_members->second.begin(), current_member));
+}
+
+bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
+ uint32_t object_id = inst->GetSingleWordInOperand(0);
+ Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+ uint32_t type_id = object_inst->type_id();
+
+ Instruction::OperandList new_operands;
+ bool modified = false;
+ new_operands.emplace_back(inst->GetInOperand(0));
+ for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+ uint32_t member_idx = inst->GetSingleWordInOperand(i);
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+ assert(new_member_idx != kRemovedMember);
+ if (member_idx != new_member_idx) {
+ modified = true;
+ }
+ new_operands.emplace_back(
+ Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeStruct:
+ assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
+ inst->opcode() != SpvOpInBoundsPtrAccessChain));
+ // The type will have already been rewriten, so use the new member
+ // index.
+ type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+ break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ type_id = type_inst->GetSingleWordInOperand(0);
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ if (!modified) {
+ return false;
+ }
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
+ uint32_t composite_id = inst->GetSingleWordInOperand(1);
+ Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+ uint32_t type_id = composite_inst->type_id();
+
+ Instruction::OperandList new_operands;
+ bool modified = false;
+ new_operands.emplace_back(inst->GetInOperand(0));
+ new_operands.emplace_back(inst->GetInOperand(1));
+ for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+ uint32_t member_idx = inst->GetSingleWordInOperand(i);
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+ if (new_member_idx == kRemovedMember) {
+ context()->KillInst(inst);
+ return true;
+ }
+
+ if (member_idx != new_member_idx) {
+ modified = true;
+ }
+ new_operands.emplace_back(
+ Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeStruct:
+ // The type will have already been rewritten, so use the new member
+ // index.
+ type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+ break;
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeVector:
+ case SpvOpTypeMatrix:
+ type_id = type_inst->GetSingleWordInOperand(0);
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ if (!modified) {
+ return false;
+ }
+ inst->SetInOperands(std::move(new_operands));
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
+ uint32_t struct_id = inst->GetSingleWordInOperand(0);
+ Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
+ uint32_t pointer_type_id = struct_inst->type_id();
+ Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+ uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+ uint32_t member_idx = inst->GetSingleWordInOperand(1);
+ uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+ assert(new_member_idx != kRemovedMember);
+
+ if (member_idx == new_member_idx) {
+ return false;
+ }
+
+ inst->SetInOperand(1, {new_member_idx});
+ context()->UpdateDefUse(inst);
+ return true;
+}
+
+void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
+ const Instruction* inst) {
+ if (inst->type_id() != 0) {
+ MarkTypeAsFullyUsed(inst->type_id());
+ }
+
+ inst->ForEachInId([this](const uint32_t* id) {
+ Instruction* instruction = get_def_use_mgr()->GetDef(*id);
+ if (instruction->type_id() != 0) {
+ MarkTypeAsFullyUsed(instruction->type_id());
+ }
+ });
+}
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/eliminate_dead_members_pass.h b/source/opt/eliminate_dead_members_pass.h
new file mode 100644
index 0000000..4feaa55
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/mem_pass.h"
+#include "source/opt/module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Remove unused members from structures. The remaining members will remain at
+// the same offset.
+class EliminateDeadMembersPass : public MemPass {
+ public:
+ const char* name() const override { return "eliminate-dead-members"; }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+ IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis |
+ IRContext::kAnalysisScalarEvolution |
+ IRContext::kAnalysisRegisterPressure |
+ IRContext::kAnalysisValueNumberTable |
+ IRContext::kAnalysisStructuredCFG |
+ IRContext::kAnalysisBuiltinVarId |
+ IRContext::kAnalysisIdToFuncMapping;
+ }
+
+ private:
+ // Populate |used_members_| with the member of structures that are live in the
+ // current context.
+ void FindLiveMembers();
+
+ // Add to |used_members_| the member of structures that are live in
+ // |function|.
+ void FindLiveMembers(const Function& function);
+ // Add to |used_members_| the member of structures that are live in |inst|.
+ void FindLiveMembers(const Instruction* inst);
+
+ // Add to |used_members_| the members that are live in the |OpStore|
+ // instruction |inst|.
+ void MarkMembersAsLiveForStore(const Instruction* inst);
+
+ // Add to |used_members_| the members that are live in the |OpCopyMemory*|
+ // instruction |inst|.
+ void MarkMembersAsLiveForCopyMemory(const Instruction* inst);
+
+ // Add to |used_members_| the members that are live in the
+ // |OpCompositeExtract| instruction |inst|.
+ void MarkMembersAsLiveForExtract(const Instruction* inst);
+
+ // Add to |used_members_| the members that are live in the |Op*AccessChain|
+ // instruction |inst|.
+ void MarkMembersAsLiveForAccessChain(const Instruction* inst);
+
+ // Add the member referenced by the OpArrayLength instruction |inst| to
+ // |uses_members_|.
+ void MarkMembersAsLiveForArrayLength(const Instruction* inst);
+
+ // Remove dead members from structs and updates any instructions that need to
+ // be updated as a consequence. Return true if something changed.
+ bool RemoveDeadMembers();
+
+ // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate|
+ // instruction, so it references the correct member after the struct is
+ // updated. Return true if something changed.
+ bool UpdateOpMemberNameOrDecorate(Instruction* inst);
+
+ // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it
+ // references the correct member after the struct is updated. Return true if
+ // something changed.
+ bool UpdateOpGroupMemberDecorate(Instruction* inst);
+
+ // Update the |OpTypeStruct| instruction |inst| my removing the members that
+ // are not live. Return true if something changed.
+ bool UpdateOpTypeStruct(Instruction* inst);
+
+ // Update the |OpConstantComposite| instruction |inst| to match the change
+ // made to the type that was being generated. Return true if something
+ // changed.
+ bool UpdateConstantComposite(Instruction* inst);
+
+ // Update the |Op*AccessChain| instruction |inst| to reference the correct
+ // members. All members referenced in the access chain must be live. This
+ // function must be called after the |OpTypeStruct| instruction for the type
+ // has been updated. Return true if something changed.
+ bool UpdateAccessChain(Instruction* inst);
+
+ // Update the |OpCompositeExtract| instruction |inst| to reference the correct
+ // members. All members referenced in the instruction must be live. This
+ // function must be called after the |OpTypeStruct| instruction for the type
+ // has been updated. Return true if something changed.
+ bool UpdateCompsiteExtract(Instruction* inst);
+
+ // Update the |OpCompositeInsert| instruction |inst| to reference the correct
+ // members. If the member being inserted is not live, then |inst| is killed.
+ // This function must be called after the |OpTypeStruct| instruction for the
+ // type has been updated. Return true if something changed.
+ bool UpdateCompositeInsert(Instruction* inst);
+
+ // Update the |OpArrayLength| instruction |inst| to reference the correct
+ // member. The member referenced in the instruction must be live. Return true
+ // if something changed.
+ bool UpdateOpArrayLength(Instruction* inst);
+
+ // Add all of the members of type |type_id| and members of any subtypes to
+ // |used_members_|.
+ void MarkTypeAsFullyUsed(uint32_t type_id);
+
+ // Add all of the members of the type of the operand |in_idx| in |inst| and
+ // members of any subtypes to |uses_members_|.
+ void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx);
+
+ // Return the index of the member that use to be the |member_idx|th member of
+ // |type_id|. If the member has been removed, |kRemovedMember| is returned.
+ uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx);
+
+ // A map from a type id to a set of indices representing the members of the
+ // type that are used, and must be kept.
+ std::unordered_map<uint32_t, std::set<uint32_t>> used_members_;
+ void MarkStructOperandsAsFullyUsed(const Instruction* inst);
+ void MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id);
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
diff --git a/source/opt/fix_storage_class.cpp b/source/opt/fix_storage_class.cpp
new file mode 100644
index 0000000..03da0d0
--- /dev/null
+++ b/source/opt/fix_storage_class.cpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fix_storage_class.h"
+
+#include <set>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status FixStorageClass::Process() {
+ bool modified = false;
+
+ get_module()->ForEachInst([this, &modified](Instruction* inst) {
+ if (inst->opcode() == SpvOpVariable) {
+ std::set<uint32_t> seen;
+ std::vector<std::pair<Instruction*, uint32_t>> uses;
+ get_def_use_mgr()->ForEachUse(inst,
+ [&uses](Instruction* use, uint32_t op_idx) {
+ uses.push_back({use, op_idx});
+ });
+
+ for (auto& use : uses) {
+ modified |= PropagateStorageClass(
+ use.first,
+ static_cast<SpvStorageClass>(inst->GetSingleWordInOperand(0)),
+ &seen);
+ assert(seen.empty() && "Seen was not properly reset.");
+ modified |=
+ PropagateType(use.first, inst->type_id(), use.second, &seen);
+ assert(seen.empty() && "Seen was not properly reset.");
+ }
+ }
+ });
+ return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool FixStorageClass::PropagateStorageClass(Instruction* inst,
+ SpvStorageClass storage_class,
+ std::set<uint32_t>* seen) {
+ if (!IsPointerResultType(inst)) {
+ return false;
+ }
+
+ if (IsPointerToStorageClass(inst, storage_class)) {
+ if (inst->opcode() == SpvOpPhi) {
+ if (!seen->insert(inst->result_id()).second) {
+ return false;
+ }
+ }
+
+ bool modified = false;
+ std::vector<Instruction*> uses;
+ get_def_use_mgr()->ForEachUser(
+ inst, [&uses](Instruction* use) { uses.push_back(use); });
+ for (Instruction* use : uses) {
+ modified |= PropagateStorageClass(use, storage_class, seen);
+ }
+
+ if (inst->opcode() == SpvOpPhi) {
+ seen->erase(inst->result_id());
+ }
+ return modified;
+ }
+
+ switch (inst->opcode()) {
+ case SpvOpAccessChain:
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsAccessChain:
+ case SpvOpCopyObject:
+ case SpvOpPhi:
+ case SpvOpSelect:
+ FixInstructionStorageClass(inst, storage_class, seen);
+ return true;
+ case SpvOpFunctionCall:
+ // We cannot be sure of the actual connection between the storage class
+ // of the parameter and the storage class of the result, so we should not
+ // do anything. If the result type needs to be fixed, the function call
+ // should be inlined.
+ return false;
+ case SpvOpImageTexelPointer:
+ case SpvOpLoad:
+ case SpvOpStore:
+ case SpvOpCopyMemory:
+ case SpvOpCopyMemorySized:
+ case SpvOpVariable:
+ case SpvOpBitcast:
+ // Nothing to change for these opcode. The result type is the same
+ // regardless of the storage class of the operand.
+ return false;
+ default:
+ assert(false &&
+ "Not expecting instruction to have a pointer result type.");
+ return false;
+ }
+}
+
+void FixStorageClass::FixInstructionStorageClass(Instruction* inst,
+ SpvStorageClass storage_class,
+ std::set<uint32_t>* seen) {
+ assert(IsPointerResultType(inst) &&
+ "The result type of the instruction must be a pointer.");
+
+ ChangeResultStorageClass(inst, storage_class);
+
+ std::vector<Instruction*> uses;
+ get_def_use_mgr()->ForEachUser(
+ inst, [&uses](Instruction* use) { uses.push_back(use); });
+ for (Instruction* use : uses) {
+ PropagateStorageClass(use, storage_class, seen);
+ }
+}
+
+void FixStorageClass::ChangeResultStorageClass(
+ Instruction* inst, SpvStorageClass storage_class) const {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
+ assert(result_type_inst->opcode() == SpvOpTypePointer);
+ uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1);
+ uint32_t new_result_type_id =
+ type_mgr->FindPointerToType(pointee_type_id, storage_class);
+ inst->SetResultType(new_result_type_id);
+ context()->UpdateDefUse(inst);
+}
+
+bool FixStorageClass::IsPointerResultType(Instruction* inst) {
+ if (inst->type_id() == 0) {
+ return false;
+ }
+ const analysis::Type* ret_type =
+ context()->get_type_mgr()->GetType(inst->type_id());
+ return ret_type->AsPointer() != nullptr;
+}
+
+bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
+ SpvStorageClass storage_class) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Type* pType = type_mgr->GetType(inst->type_id());
+ const analysis::Pointer* result_type = pType->AsPointer();
+
+ if (result_type == nullptr) {
+ return false;
+ }
+
+ return (result_type->storage_class() == storage_class);
+}
+
+bool FixStorageClass::ChangeResultType(Instruction* inst,
+ uint32_t new_type_id) {
+ if (inst->type_id() == new_type_id) {
+ return false;
+ }
+
+ context()->ForgetUses(inst);
+ inst->SetResultType(new_type_id);
+ context()->AnalyzeUses(inst);
+ return true;
+}
+
+bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
+ uint32_t op_idx, std::set<uint32_t>* seen) {
+ assert(type_id != 0 && "Not given a valid type in PropagateType");
+ bool modified = false;
+
+ // If the type of operand |op_idx| forces the result type of |inst| to a
+ // particular type, then we want find that type.
+ uint32_t new_type_id = 0;
+ switch (inst->opcode()) {
+ case SpvOpAccessChain:
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsAccessChain:
+ case SpvOpInBoundsPtrAccessChain:
+ if (op_idx == 2) {
+ new_type_id = WalkAccessChainType(inst, type_id);
+ }
+ break;
+ case SpvOpCopyObject:
+ new_type_id = type_id;
+ break;
+ case SpvOpPhi:
+ if (seen->insert(inst->result_id()).second) {
+ new_type_id = type_id;
+ }
+ break;
+ case SpvOpSelect:
+ if (op_idx > 2) {
+ new_type_id = type_id;
+ }
+ break;
+ case SpvOpFunctionCall:
+ // We cannot be sure of the actual connection between the type
+ // of the parameter and the type of the result, so we should not
+ // do anything. If the result type needs to be fixed, the function call
+ // should be inlined.
+ return false;
+ case SpvOpLoad: {
+ Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+ new_type_id = type_inst->GetSingleWordInOperand(1);
+ break;
+ }
+ case SpvOpStore: {
+ uint32_t obj_id = inst->GetSingleWordInOperand(1);
+ Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id);
+ uint32_t obj_type_id = obj_inst->type_id();
+
+ uint32_t ptr_id = inst->GetSingleWordInOperand(0);
+ Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+ uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
+
+ if (obj_type_id != pointee_type_id) {
+ uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
+ inst->SetInOperand(1, {copy_id});
+ context()->UpdateDefUse(inst);
+ }
+ } break;
+ case SpvOpCopyMemory:
+ case SpvOpCopyMemorySized:
+ // TODO: May need to expand the copy as we do with the stores.
+ break;
+ case SpvOpCompositeConstruct:
+ case SpvOpCompositeExtract:
+ case SpvOpCompositeInsert:
+ // TODO: DXC does not seem to generate code that will require changes to
+ // these opcode. The can be implemented when they come up.
+ break;
+ case SpvOpImageTexelPointer:
+ case SpvOpBitcast:
+ // Nothing to change for these opcode. The result type is the same
+ // regardless of the type of the operand.
+ return false;
+ default:
+ // I expect the remaining instructions to act on types that are guaranteed
+ // to be unique, so no change will be necessary.
+ break;
+ }
+
+ // If the operand forces the result type, then make sure the result type
+ // matches, and update the uses of |inst|. We do not have to check the uses
+ // of |inst| in the result type is not forced because we are only looking for
+ // issue that come from mismatches between function formal and actual
+ // parameters after the function has been inlined. These parameters are
+ // pointers. Once the type no longer depends on the type of the parameter,
+ // then the types should have be correct.
+ if (new_type_id != 0) {
+ modified = ChangeResultType(inst, new_type_id);
+
+ std::vector<std::pair<Instruction*, uint32_t>> uses;
+ get_def_use_mgr()->ForEachUse(inst,
+ [&uses](Instruction* use, uint32_t idx) {
+ uses.push_back({use, idx});
+ });
+
+ for (auto& use : uses) {
+ PropagateType(use.first, new_type_id, use.second, seen);
+ }
+
+ if (inst->opcode() == SpvOpPhi) {
+ seen->erase(inst->result_id());
+ }
+ }
+ return modified;
+}
+
+uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
+ uint32_t start_idx = 0;
+ switch (inst->opcode()) {
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain:
+ start_idx = 1;
+ break;
+ case SpvOpPtrAccessChain:
+ case SpvOpInBoundsPtrAccessChain:
+ start_idx = 2;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ Instruction* orig_type_inst = get_def_use_mgr()->GetDef(id);
+ assert(orig_type_inst->opcode() == SpvOpTypePointer);
+ id = orig_type_inst->GetSingleWordInOperand(1);
+
+ for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
+ Instruction* type_inst = get_def_use_mgr()->GetDef(id);
+ switch (type_inst->opcode()) {
+ case SpvOpTypeArray:
+ case SpvOpTypeRuntimeArray:
+ case SpvOpTypeMatrix:
+ case SpvOpTypeVector:
+ id = type_inst->GetSingleWordInOperand(0);
+ break;
+ case SpvOpTypeStruct: {
+ const analysis::Constant* index_const =
+ context()->get_constant_mgr()->FindDeclaredConstant(
+ inst->GetSingleWordInOperand(i));
+ uint32_t index = index_const->GetU32();
+ id = type_inst->GetSingleWordInOperand(index);
+ break;
+ }
+ default:
+ break;
+ }
+ assert(id != 0 &&
+ "Tried to extract from an object where it cannot be done.");
+ }
+
+ return context()->get_type_mgr()->FindPointerToType(
+ id,
+ static_cast<SpvStorageClass>(orig_type_inst->GetSingleWordInOperand(0)));
+}
+
+// namespace opt
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/fix_storage_class.h b/source/opt/fix_storage_class.h
new file mode 100644
index 0000000..e72e864
--- /dev/null
+++ b/source/opt/fix_storage_class.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_FIX_STORAGE_CLASS_H_
+#define SOURCE_OPT_FIX_STORAGE_CLASS_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass tries to fix validation error due to a mismatch of storage classes
+// in instructions. There is no guarantee that all such error will be fixed,
+// and it is possible that in fixing these errors, it could lead to other
+// errors.
+class FixStorageClass : public Pass {
+ public:
+ const char* name() const override { return "fix-storage-class"; }
+ Status Process() override;
+
+ // Return the mask of preserved Analyses.
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+ IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+ }
+
+ private:
+ // Changes the storage class of the result of |inst| to |storage_class| in
+ // appropriate, and propagates the change to the users of |inst| as well.
+ // Returns true of any changes were made.
+ // |seen| is used to track OpPhi instructions that should not be processed.
+ bool PropagateStorageClass(Instruction* inst, SpvStorageClass storage_class,
+ std::set<uint32_t>* seen);
+
+ // Changes the storage class of the result of |inst| to |storage_class|.
+ // Is it assumed that the result type of |inst| is a pointer type.
+ // Propagates the change to the users of |inst| as well.
+ // Returns true of any changes were made.
+ // |seen| is used to track OpPhi instructions that should not be processed by
+ // |PropagateStorageClass|
+ void FixInstructionStorageClass(Instruction* inst,
+ SpvStorageClass storage_class,
+ std::set<uint32_t>* seen);
+
+ // Changes the storage class of the result of |inst| to |storage_class|. The
+ // result type of |inst| must be a pointer.
+ void ChangeResultStorageClass(Instruction* inst,
+ SpvStorageClass storage_class) const;
+
+ // Returns true if the result type of |inst| is a pointer.
+ bool IsPointerResultType(Instruction* inst);
+
+ // Returns true if the result of |inst| is a pointer to storage class
+ // |storage_class|.
+ bool IsPointerToStorageClass(Instruction* inst,
+ SpvStorageClass storage_class);
+
+ // Change |inst| to match that operand |op_idx| now has type |type_id|, and
+ // adjust any uses of |inst| accordingly. Returns true if the code changed.
+ bool PropagateType(Instruction* inst, uint32_t type_id, uint32_t op_idx,
+ std::set<uint32_t>* seen);
+
+ // Changes the result type of |inst| to |new_type_id|.
+ bool ChangeResultType(Instruction* inst, uint32_t new_type_id);
+
+ // Returns the type id of the member of the type |id| that would be returned
+ // by following the indices of the access chain instruction |inst|.
+ uint32_t WalkAccessChainType(Instruction* inst, uint32_t id);
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_FIX_STORAGE_CLASS_H_
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index d6b583f..944f438 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -45,8 +45,13 @@
uint32_t InstructionFolder::UnaryOperate(SpvOp opcode, uint32_t operand) const {
switch (opcode) {
// Arthimetics
- case SpvOp::SpvOpSNegate:
- return -static_cast<int32_t>(operand);
+ case SpvOp::SpvOpSNegate: {
+ int32_t s_operand = static_cast<int32_t>(operand);
+ if (s_operand == std::numeric_limits<int32_t>::min()) {
+ return s_operand;
+ }
+ return -s_operand;
+ }
case SpvOp::SpvOpNot:
return ~operand;
case SpvOp::SpvOpLogicalNot:
@@ -115,8 +120,10 @@
// Shifting
case SpvOp::SpvOpShiftRightLogical:
- if (b > 32) {
- // This is undefined behaviour. Choose 0 for consistency.
+ if (b >= 32) {
+ // This is undefined behaviour when |b| > 32. Choose 0 for consistency.
+ // When |b| == 32, doing the shift in C++ in undefined, but the result
+ // will be 0, so just return that value.
return 0;
}
return a >> b;
@@ -125,10 +132,21 @@
// This is undefined behaviour. Choose 0 for consistency.
return 0;
}
+ if (b == 32) {
+ // Doing the shift in C++ is undefined, but the result is defined in the
+ // spir-v spec. Find that value another way.
+ if (static_cast<int32_t>(a) >= 0) {
+ return 0;
+ } else {
+ return static_cast<uint32_t>(-1);
+ }
+ }
return (static_cast<int32_t>(a)) >> b;
case SpvOp::SpvOpShiftLeftLogical:
- if (b > 32) {
- // This is undefined behaviour. Choose 0 for consistency.
+ if (b >= 32) {
+ // This is undefined behaviour when |b| > 32. Choose 0 for consistency.
+ // When |b| == 32, doing the shift in C++ in undefined, but the result
+ // will be 0, so just return that value.
return 0;
}
return a << b;
@@ -307,7 +325,8 @@
if (constants[1] != nullptr) {
// When shifting by a value larger than the size of the result, the
// result is undefined. We are setting the undefined behaviour to a
- // result of 0.
+ // result of 0. If the shift amount is the same as the size of the
+ // result, then the result is defined, and it 0.
uint32_t shift_amount = constants[1]->GetU32BitValue();
if (shift_amount >= 32) {
*result = 0;
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 3274319..18d5149 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -2167,6 +2167,37 @@
};
}
+// Removes duplicate ids from the interface list of an OpEntryPoint
+// instruction.
+FoldingRule RemoveRedundantOperands() {
+ return [](IRContext*, Instruction* inst,
+ const std::vector<const analysis::Constant*>&) {
+ assert(inst->opcode() == SpvOpEntryPoint &&
+ "Wrong opcode. Should be OpEntryPoint.");
+ bool has_redundant_operand = false;
+ std::unordered_set<uint32_t> seen_operands;
+ std::vector<Operand> new_operands;
+
+ new_operands.emplace_back(inst->GetOperand(0));
+ new_operands.emplace_back(inst->GetOperand(1));
+ new_operands.emplace_back(inst->GetOperand(2));
+ for (uint32_t i = 3; i < inst->NumOperands(); ++i) {
+ if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) {
+ new_operands.emplace_back(inst->GetOperand(i));
+ } else {
+ has_redundant_operand = true;
+ }
+ }
+
+ if (!has_redundant_operand) {
+ return false;
+ }
+
+ inst->SetInOperands(std::move(new_operands));
+ return true;
+ };
+}
+
} // namespace
FoldingRules::FoldingRules() {
@@ -2183,6 +2214,8 @@
rules_[SpvOpDot].push_back(DotProductDoingExtract());
+ rules_[SpvOpEntryPoint].push_back(RemoveRedundantOperands());
+
rules_[SpvOpExtInst].push_back(RedundantFMix());
rules_[SpvOpFAdd].push_back(RedundantFAdd());
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index d4457ad..9bd46e2 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -46,29 +46,77 @@
void Function::ForEachInst(const std::function<void(Instruction*)>& f,
bool run_on_debug_line_insts) {
- if (def_inst_) def_inst_->ForEachInst(f, run_on_debug_line_insts);
- for (auto& param : params_) param->ForEachInst(f, run_on_debug_line_insts);
- for (auto& bb : blocks_) bb->ForEachInst(f, run_on_debug_line_insts);
- if (end_inst_) end_inst_->ForEachInst(f, run_on_debug_line_insts);
+ WhileEachInst(
+ [&f](Instruction* inst) {
+ f(inst);
+ return true;
+ },
+ run_on_debug_line_insts);
}
void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts) const {
- if (def_inst_)
- static_cast<const Instruction*>(def_inst_.get())
- ->ForEachInst(f, run_on_debug_line_insts);
+ WhileEachInst(
+ [&f](const Instruction* inst) {
+ f(inst);
+ return true;
+ },
+ run_on_debug_line_insts);
+}
- for (const auto& param : params_)
- static_cast<const Instruction*>(param.get())
- ->ForEachInst(f, run_on_debug_line_insts);
+bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
+ bool run_on_debug_line_insts) {
+ if (def_inst_) {
+ if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
- for (const auto& bb : blocks_)
- static_cast<const BasicBlock*>(bb.get())->ForEachInst(
- f, run_on_debug_line_insts);
+ for (auto& param : params_) {
+ if (!param->WhileEachInst(f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
+
+ for (auto& bb : blocks_) {
+ if (!bb->WhileEachInst(f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
+
+ if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts);
+
+ return true;
+}
+
+bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
+ bool run_on_debug_line_insts) const {
+ if (def_inst_) {
+ if (!static_cast<const Instruction*>(def_inst_.get())
+ ->WhileEachInst(f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
+
+ for (const auto& param : params_) {
+ if (!static_cast<const Instruction*>(param.get())
+ ->WhileEachInst(f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
+
+ for (const auto& bb : blocks_) {
+ if (!static_cast<const BasicBlock*>(bb.get())->WhileEachInst(
+ f, run_on_debug_line_insts)) {
+ return false;
+ }
+ }
if (end_inst_)
- static_cast<const Instruction*>(end_inst_.get())
- ->ForEachInst(f, run_on_debug_line_insts);
+ return static_cast<const Instruction*>(end_inst_.get())
+ ->WhileEachInst(f, run_on_debug_line_insts);
+
+ return true;
}
void Function::ForEachParam(const std::function<void(Instruction*)>& f,
diff --git a/source/opt/function.h b/source/opt/function.h
index 7c0166f..c80b078 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -110,6 +110,10 @@
bool run_on_debug_line_insts = false);
void ForEachInst(const std::function<void(const Instruction*)>& f,
bool run_on_debug_line_insts = false) const;
+ bool WhileEachInst(const std::function<bool(Instruction*)>& f,
+ bool run_on_debug_line_insts = false);
+ bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
+ bool run_on_debug_line_insts = false) const;
// Runs the given function |f| on each parameter instruction in this function,
// and optionally on debug line instructions that might precede them.
diff --git a/source/opt/generate_webgpu_initializers_pass.cpp b/source/opt/generate_webgpu_initializers_pass.cpp
new file mode 100644
index 0000000..9334b43
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/generate_webgpu_initializers_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool NeedsWebGPUInitializer(Instruction* inst) {
+ if (inst->opcode() != SpvOpVariable) return false;
+
+ auto storage_class = inst->GetSingleWordOperand(2);
+ if (storage_class != SpvStorageClassOutput &&
+ storage_class != SpvStorageClassPrivate &&
+ storage_class != SpvStorageClassFunction) {
+ return false;
+ }
+
+ if (inst->NumOperands() > 3) return false;
+
+ return true;
+}
+
+} // namespace
+
+Pass::Status GenerateWebGPUInitializersPass::Process() {
+ auto* module = context()->module();
+ bool changed = false;
+
+ // Handle global/module scoped variables
+ for (auto iter = module->types_values_begin();
+ iter != module->types_values_end(); ++iter) {
+ Instruction* inst = &(*iter);
+
+ if (inst->opcode() == SpvOpConstantNull) {
+ null_constant_type_map_[inst->type_id()] = inst;
+ seen_null_constants_.insert(inst);
+ continue;
+ }
+
+ if (!NeedsWebGPUInitializer(inst)) continue;
+
+ changed = true;
+
+ auto* constant_inst = GetNullConstantForVariable(inst);
+ if (seen_null_constants_.find(constant_inst) ==
+ seen_null_constants_.end()) {
+ constant_inst->InsertBefore(inst);
+ null_constant_type_map_[inst->type_id()] = inst;
+ seen_null_constants_.insert(inst);
+ }
+ AddNullInitializerToVariable(constant_inst, inst);
+ }
+
+ // Handle local/function scoped variables
+ for (auto func = module->begin(); func != module->end(); ++func) {
+ auto block = func->entry().get();
+ for (auto iter = block->begin();
+ iter != block->end() && iter->opcode() == SpvOpVariable; ++iter) {
+ Instruction* inst = &(*iter);
+ if (!NeedsWebGPUInitializer(inst)) continue;
+
+ changed = true;
+ auto* constant_inst = GetNullConstantForVariable(inst);
+ AddNullInitializerToVariable(constant_inst, inst);
+ }
+ }
+
+ return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Instruction* GenerateWebGPUInitializersPass::GetNullConstantForVariable(
+ Instruction* variable_inst) {
+ auto constant_mgr = context()->get_constant_mgr();
+ auto* def_use_mgr = get_def_use_mgr();
+
+ auto* ptr_inst = def_use_mgr->GetDef(variable_inst->type_id());
+ auto type_id = ptr_inst->GetInOperand(1).words[0];
+ if (null_constant_type_map_.find(type_id) == null_constant_type_map_.end()) {
+ auto* constant_type = context()->get_type_mgr()->GetType(type_id);
+ auto* constant = constant_mgr->GetConstant(constant_type, {});
+ return constant_mgr->GetDefiningInstruction(constant, type_id);
+ } else {
+ return null_constant_type_map_[type_id];
+ }
+}
+
+void GenerateWebGPUInitializersPass::AddNullInitializerToVariable(
+ Instruction* constant_inst, Instruction* variable_inst) {
+ auto constant_id = constant_inst->result_id();
+ variable_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {constant_id}));
+ get_def_use_mgr()->AnalyzeInstUse(variable_inst);
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/generate_webgpu_initializers_pass.h b/source/opt/generate_webgpu_initializers_pass.h
new file mode 100644
index 0000000..f95e84c
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+#define SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Adds initializers to variables with storage classes Output, Private, and
+// Function if they are missing. In the WebGPU environment these storage classes
+// require that the variables are initialized. Currently they are initialized to
+// NULL, though in the future some of them may be initialized to the first value
+// that is stored in them, if that was a constant.
+class GenerateWebGPUInitializersPass : public Pass {
+ public:
+ const char* name() const override { return "generate-webgpu-initializers"; }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisScalarEvolution |
+ IRContext::kAnalysisRegisterPressure |
+ IRContext::kAnalysisValueNumberTable |
+ IRContext::kAnalysisStructuredCFG |
+ IRContext::kAnalysisBuiltinVarId |
+ IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+ }
+
+ private:
+ using NullConstantTypeMap = std::unordered_map<uint32_t, Instruction*>;
+ NullConstantTypeMap null_constant_type_map_;
+ std::unordered_set<Instruction*> seen_null_constants_;
+
+ Instruction* GetNullConstantForVariable(Instruction* variable_inst);
+ void AddNullInitializerToVariable(Instruction* constant_inst,
+ Instruction* variable_inst);
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
diff --git a/source/opt/if_conversion.h b/source/opt/if_conversion.h
index 609bdf3..db84e70 100644
--- a/source/opt/if_conversion.h
+++ b/source/opt/if_conversion.h
@@ -32,7 +32,8 @@
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse | IRContext::kAnalysisDominatorAnalysis |
IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisCFG |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp
index 10b5e98..24f4e73 100644
--- a/source/opt/inline_exhaustive_pass.cpp
+++ b/source/opt/inline_exhaustive_pass.cpp
@@ -21,7 +21,7 @@
namespace spvtools {
namespace opt {
-bool InlineExhaustivePass::InlineExhaustive(Function* func) {
+Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
bool modified = false;
// Using block iterators here because of block erasures and insertions.
for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -30,7 +30,9 @@
// Inline call.
std::vector<std::unique_ptr<BasicBlock>> newBlocks;
std::vector<std::unique_ptr<Instruction>> newVars;
- GenInlineCode(&newBlocks, &newVars, ii, bi);
+ if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+ return Status::Failure;
+ }
// If call block is replaced with more than one block, point
// succeeding phis at new last block.
if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -58,14 +60,18 @@
}
}
}
- return modified;
+ return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
Pass::Status InlineExhaustivePass::ProcessImpl() {
+ Status status = Status::SuccessWithoutChange;
// Attempt exhaustive inlining on each entry point function in module
- ProcessFunction pfn = [this](Function* fp) { return InlineExhaustive(fp); };
- bool modified = context()->ProcessEntryPointCallTree(pfn);
- return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+ ProcessFunction pfn = [&status, this](Function* fp) {
+ status = CombineStatus(status, InlineExhaustive(fp));
+ return false;
+ };
+ context()->ProcessEntryPointCallTree(pfn);
+ return status;
}
InlineExhaustivePass::InlineExhaustivePass() = default;
diff --git a/source/opt/inline_exhaustive_pass.h b/source/opt/inline_exhaustive_pass.h
index 103e091..c2e8547 100644
--- a/source/opt/inline_exhaustive_pass.h
+++ b/source/opt/inline_exhaustive_pass.h
@@ -40,8 +40,8 @@
private:
// Exhaustively inline all function calls in func as well as in
- // all code that is inlined into func. Return true if func is modified.
- bool InlineExhaustive(Function* func);
+ // all code that is inlined into func. Returns the status.
+ Status InlineExhaustive(Function* func);
void Initialize();
Pass::Status ProcessImpl();
diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp
index e94f26d..6ccaf90 100644
--- a/source/opt/inline_opaque_pass.cpp
+++ b/source/opt/inline_opaque_pass.cpp
@@ -63,7 +63,7 @@
});
}
-bool InlineOpaquePass::InlineOpaque(Function* func) {
+Pass::Status InlineOpaquePass::InlineOpaque(Function* func) {
bool modified = false;
// Using block iterators here because of block erasures and insertions.
for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -72,7 +72,10 @@
// Inline call.
std::vector<std::unique_ptr<BasicBlock>> newBlocks;
std::vector<std::unique_ptr<Instruction>> newVars;
- GenInlineCode(&newBlocks, &newVars, ii, bi);
+ if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+ return Status::Failure;
+ }
+
// If call block is replaced with more than one block, point
// succeeding phis at new last block.
if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -90,16 +93,20 @@
}
}
}
- return modified;
+ return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
void InlineOpaquePass::Initialize() { InitializeInline(); }
Pass::Status InlineOpaquePass::ProcessImpl() {
+ Status status = Status::SuccessWithoutChange;
// Do opaque inlining on each function in entry point call tree
- ProcessFunction pfn = [this](Function* fp) { return InlineOpaque(fp); };
- bool modified = context()->ProcessEntryPointCallTree(pfn);
- return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+ ProcessFunction pfn = [&status, this](Function* fp) {
+ status = CombineStatus(status, InlineOpaque(fp));
+ return false;
+ };
+ context()->ProcessEntryPointCallTree(pfn);
+ return status;
}
InlineOpaquePass::InlineOpaquePass() = default;
diff --git a/source/opt/inline_opaque_pass.h b/source/opt/inline_opaque_pass.h
index aad43fd..1e3081d 100644
--- a/source/opt/inline_opaque_pass.h
+++ b/source/opt/inline_opaque_pass.h
@@ -48,7 +48,7 @@
// Inline all function calls in |func| that have opaque params or return
// type. Inline similarly all code that is inlined into func. Return true
// if func is modified.
- bool InlineOpaque(Function* func);
+ Status InlineOpaque(Function* func);
void Initialize();
Pass::Status ProcessImpl();
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index cdd5659..f348bbe 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -34,7 +34,11 @@
uint32_t InlinePass::AddPointerToType(uint32_t type_id,
SpvStorageClass storage_class) {
- uint32_t resultId = TakeNextId();
+ uint32_t resultId = context()->TakeNextId();
+ if (resultId == 0) {
+ return resultId;
+ }
+
std::unique_ptr<Instruction> type_inst(
new Instruction(context(), SpvOpTypePointer, 0, resultId,
{{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
@@ -108,10 +112,16 @@
if (false_id_ != 0) return false_id_;
uint32_t boolId = get_module()->GetGlobalValue(SpvOpTypeBool);
if (boolId == 0) {
- boolId = TakeNextId();
+ boolId = context()->TakeNextId();
+ if (boolId == 0) {
+ return 0;
+ }
get_module()->AddGlobalValue(SpvOpTypeBool, boolId, 0);
}
- false_id_ = TakeNextId();
+ false_id_ = context()->TakeNextId();
+ if (false_id_ == 0) {
+ return 0;
+ }
get_module()->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
return false_id_;
}
@@ -120,50 +130,64 @@
Function* calleeFn, BasicBlock::iterator call_inst_itr,
std::unordered_map<uint32_t, uint32_t>* callee2caller) {
int param_idx = 0;
- calleeFn->ForEachParam([&call_inst_itr, ¶m_idx,
- &callee2caller](const Instruction* cpi) {
- const uint32_t pid = cpi->result_id();
- (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
- kSpvFunctionCallArgumentId + param_idx);
- ++param_idx;
- });
+ calleeFn->ForEachParam(
+ [&call_inst_itr, ¶m_idx, &callee2caller](const Instruction* cpi) {
+ const uint32_t pid = cpi->result_id();
+ (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
+ kSpvFunctionCallArgumentId + param_idx);
+ ++param_idx;
+ });
}
-void InlinePass::CloneAndMapLocals(
+bool InlinePass::CloneAndMapLocals(
Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars,
std::unordered_map<uint32_t, uint32_t>* callee2caller) {
auto callee_block_itr = calleeFn->begin();
auto callee_var_itr = callee_block_itr->begin();
while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
- uint32_t newId = TakeNextId();
+ uint32_t newId = context()->TakeNextId();
+ if (newId == 0) {
+ return false;
+ }
get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
var_inst->SetResultId(newId);
(*callee2caller)[callee_var_itr->result_id()] = newId;
new_vars->push_back(std::move(var_inst));
++callee_var_itr;
}
+ return true;
}
uint32_t InlinePass::CreateReturnVar(
Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
uint32_t returnVarId = 0;
const uint32_t calleeTypeId = calleeFn->type_id();
- analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
- if (calleeType->AsVoid() == nullptr) {
- // Find or create ptr to callee return type.
- uint32_t returnVarTypeId = context()->get_type_mgr()->FindPointerToType(
- calleeTypeId, SpvStorageClassFunction);
- if (returnVarTypeId == 0)
- returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
- // Add return var to new function scope variables.
- returnVarId = TakeNextId();
- std::unique_ptr<Instruction> var_inst(
- new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
- {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
- {SpvStorageClassFunction}}}));
- new_vars->push_back(std::move(var_inst));
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
+ "Cannot create a return variable of type void.");
+ // Find or create ptr to callee return type.
+ uint32_t returnVarTypeId =
+ type_mgr->FindPointerToType(calleeTypeId, SpvStorageClassFunction);
+
+ if (returnVarTypeId == 0) {
+ returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
+ if (returnVarTypeId == 0) {
+ return 0;
+ }
}
+
+ // Add return var to new function scope variables.
+ returnVarId = context()->TakeNextId();
+ if (returnVarId == 0) {
+ return 0;
+ }
+
+ std::unique_ptr<Instruction> var_inst(
+ new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
+ {SpvStorageClassFunction}}}));
+ new_vars->push_back(std::move(var_inst));
get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId);
return returnVarId;
}
@@ -172,37 +196,44 @@
return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
}
-void InlinePass::CloneSameBlockOps(
+bool InlinePass::CloneSameBlockOps(
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unordered_map<uint32_t, Instruction*>* preCallSB,
std::unique_ptr<BasicBlock>* block_ptr) {
- (*inst)->ForEachInId(
- [&postCallSB, &preCallSB, &block_ptr, this](uint32_t* iid) {
- const auto mapItr = (*postCallSB).find(*iid);
- if (mapItr == (*postCallSB).end()) {
- const auto mapItr2 = (*preCallSB).find(*iid);
- if (mapItr2 != (*preCallSB).end()) {
- // Clone pre-call same-block ops, map result id.
- const Instruction* inInst = mapItr2->second;
- std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
- CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr);
- const uint32_t rid = sb_inst->result_id();
- const uint32_t nid = this->TakeNextId();
- get_decoration_mgr()->CloneDecorations(rid, nid);
- sb_inst->SetResultId(nid);
- (*postCallSB)[rid] = nid;
- *iid = nid;
- (*block_ptr)->AddInstruction(std::move(sb_inst));
- }
- } else {
- // Reset same-block op operand.
- *iid = mapItr->second;
+ return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
+ this](uint32_t* iid) {
+ const auto mapItr = (*postCallSB).find(*iid);
+ if (mapItr == (*postCallSB).end()) {
+ const auto mapItr2 = (*preCallSB).find(*iid);
+ if (mapItr2 != (*preCallSB).end()) {
+ // Clone pre-call same-block ops, map result id.
+ const Instruction* inInst = mapItr2->second;
+ std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
+ if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
+ return false;
}
- });
+
+ const uint32_t rid = sb_inst->result_id();
+ const uint32_t nid = context()->TakeNextId();
+ if (nid == 0) {
+ return false;
+ }
+ get_decoration_mgr()->CloneDecorations(rid, nid);
+ sb_inst->SetResultId(nid);
+ (*postCallSB)[rid] = nid;
+ *iid = nid;
+ (*block_ptr)->AddInstruction(std::move(sb_inst));
+ }
+ } else {
+ // Reset same-block op operand.
+ *iid = mapItr->second;
+ }
+ return true;
+ });
}
-void InlinePass::GenInlineCode(
+bool InlinePass::GenInlineCode(
std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
std::vector<std::unique_ptr<Instruction>>* new_vars,
BasicBlock::iterator call_inst_itr,
@@ -232,10 +263,20 @@
// Define caller local variables for all callee variables and create map to
// them.
- CloneAndMapLocals(calleeFn, new_vars, &callee2caller);
+ if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller)) {
+ return false;
+ }
// Create return var if needed.
- uint32_t returnVarId = CreateReturnVar(calleeFn, new_vars);
+ const uint32_t calleeTypeId = calleeFn->type_id();
+ uint32_t returnVarId = 0;
+ analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
+ if (calleeType->AsVoid() == nullptr) {
+ returnVarId = CreateReturnVar(calleeFn, new_vars);
+ if (returnVarId == 0) {
+ return false;
+ }
+ }
// Create set of callee result ids. Used to detect forward references
std::unordered_set<uint32_t> callee_result_ids;
@@ -269,241 +310,294 @@
uint32_t singleTripLoopContinueId = 0;
uint32_t returnLabelId = 0;
bool multiBlocks = false;
- const uint32_t calleeTypeId = calleeFn->type_id();
// new_blk_ptr is a new basic block in the caller. New instructions are
// written to it. It is created when we encounter the OpLabel
// of the first callee block. It is appended to new_blocks only when
// it is complete.
std::unique_ptr<BasicBlock> new_blk_ptr;
- calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
- &call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
- &returnLabelId, &returnVarId, caller_is_loop_header,
- callee_begins_with_structured_header, &calleeTypeId,
- &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
- &singleTripLoopHeaderId, &singleTripLoopContinueId,
- &callee_result_ids, this](const Instruction* cpi) {
- switch (cpi->opcode()) {
- case SpvOpFunction:
- case SpvOpFunctionParameter:
- // Already processed
- break;
- case SpvOpVariable:
- if (cpi->NumInOperands() == 2) {
- assert(callee2caller.count(cpi->result_id()) &&
- "Expected the variable to have already been mapped.");
- uint32_t new_var_id = callee2caller.at(cpi->result_id());
+ bool successful = calleeFn->WhileEachInst(
+ [&new_blocks, &callee2caller, &call_block_itr, &call_inst_itr,
+ &new_blk_ptr, &prevInstWasReturn, &returnLabelId, &returnVarId,
+ caller_is_loop_header, callee_begins_with_structured_header,
+ &calleeTypeId, &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
+ &singleTripLoopHeaderId, &singleTripLoopContinueId, &callee_result_ids,
+ this](const Instruction* cpi) {
+ switch (cpi->opcode()) {
+ case SpvOpFunction:
+ case SpvOpFunctionParameter:
+ // Already processed
+ break;
+ case SpvOpVariable:
+ if (cpi->NumInOperands() == 2) {
+ assert(callee2caller.count(cpi->result_id()) &&
+ "Expected the variable to have already been mapped.");
+ uint32_t new_var_id = callee2caller.at(cpi->result_id());
- // The initializer must be a constant or global value. No mapped
- // should be used.
- uint32_t val_id = cpi->GetSingleWordInOperand(1);
- AddStore(new_var_id, val_id, &new_blk_ptr);
- }
- break;
- case SpvOpUnreachable:
- case SpvOpKill: {
- // Generate a return label so that we split the block with the function
- // call. Copy the terminator into the new block.
- if (returnLabelId == 0) returnLabelId = this->TakeNextId();
- std::unique_ptr<Instruction> terminator(
- new Instruction(context(), cpi->opcode(), 0, 0, {}));
- new_blk_ptr->AddInstruction(std::move(terminator));
- break;
- }
- case SpvOpLabel: {
- // If previous instruction was early return, insert branch
- // instruction to return block.
- if (prevInstWasReturn) {
- if (returnLabelId == 0) returnLabelId = this->TakeNextId();
- AddBranch(returnLabelId, &new_blk_ptr);
- prevInstWasReturn = false;
- }
- // Finish current block (if it exists) and get label for next block.
- uint32_t labelId;
- bool firstBlock = false;
- if (new_blk_ptr != nullptr) {
- new_blocks->push_back(std::move(new_blk_ptr));
- // If result id is already mapped, use it, otherwise get a new
- // one.
- const uint32_t rid = cpi->result_id();
- const auto mapItr = callee2caller.find(rid);
- labelId = (mapItr != callee2caller.end()) ? mapItr->second
- : this->TakeNextId();
- } else {
- // First block needs to use label of original block
- // but map callee label in case of phi reference.
- labelId = call_block_itr->id();
- callee2caller[cpi->result_id()] = labelId;
- firstBlock = true;
- }
- // Create first/next block.
- new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
- if (firstBlock) {
- // Copy contents of original caller block up to call instruction.
- for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
- cii = call_block_itr->begin()) {
- Instruction* inst = &*cii;
- inst->RemoveFromList();
- std::unique_ptr<Instruction> cp_inst(inst);
- // Remember same-block ops for possible regeneration.
- if (IsSameBlockOp(&*cp_inst)) {
- auto* sb_inst_ptr = cp_inst.get();
- preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+ // The initializer must be a constant or global value. No mapped
+ // should be used.
+ uint32_t val_id = cpi->GetSingleWordInOperand(1);
+ AddStore(new_var_id, val_id, &new_blk_ptr);
+ }
+ break;
+ case SpvOpUnreachable:
+ case SpvOpKill: {
+ // Generate a return label so that we split the block with the
+ // function call. Copy the terminator into the new block.
+ if (returnLabelId == 0) {
+ returnLabelId = context()->TakeNextId();
+ if (returnLabelId == 0) {
+ return false;
+ }
+ }
+ std::unique_ptr<Instruction> terminator(
+ new Instruction(context(), cpi->opcode(), 0, 0, {}));
+ new_blk_ptr->AddInstruction(std::move(terminator));
+ break;
+ }
+ case SpvOpLabel: {
+ // If previous instruction was early return, insert branch
+ // instruction to return block.
+ if (prevInstWasReturn) {
+ if (returnLabelId == 0) {
+ returnLabelId = context()->TakeNextId();
+ if (returnLabelId == 0) {
+ return false;
+ }
+ }
+ AddBranch(returnLabelId, &new_blk_ptr);
+ prevInstWasReturn = false;
+ }
+ // Finish current block (if it exists) and get label for next block.
+ uint32_t labelId;
+ bool firstBlock = false;
+ if (new_blk_ptr != nullptr) {
+ new_blocks->push_back(std::move(new_blk_ptr));
+ // If result id is already mapped, use it, otherwise get a new
+ // one.
+ const uint32_t rid = cpi->result_id();
+ const auto mapItr = callee2caller.find(rid);
+ labelId = (mapItr != callee2caller.end())
+ ? mapItr->second
+ : context()->TakeNextId();
+ if (labelId == 0) {
+ return false;
+ }
+ } else {
+ // First block needs to use label of original block
+ // but map callee label in case of phi reference.
+ labelId = call_block_itr->id();
+ callee2caller[cpi->result_id()] = labelId;
+ firstBlock = true;
+ }
+ // Create first/next block.
+ new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
+ if (firstBlock) {
+ // Copy contents of original caller block up to call instruction.
+ for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
+ cii = call_block_itr->begin()) {
+ Instruction* inst = &*cii;
+ inst->RemoveFromList();
+ std::unique_ptr<Instruction> cp_inst(inst);
+ // Remember same-block ops for possible regeneration.
+ if (IsSameBlockOp(&*cp_inst)) {
+ auto* sb_inst_ptr = cp_inst.get();
+ preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+ }
+ new_blk_ptr->AddInstruction(std::move(cp_inst));
+ }
+ if (caller_is_loop_header &&
+ callee_begins_with_structured_header) {
+ // We can't place both the caller's merge instruction and
+ // another merge instruction in the same block. So split the
+ // calling block. Insert an unconditional branch to a new guard
+ // block. Later, once we know the ID of the last block, we
+ // will move the caller's OpLoopMerge from the last generated
+ // block into the first block. We also wait to avoid
+ // invalidating various iterators.
+ const auto guard_block_id = context()->TakeNextId();
+ if (guard_block_id == 0) {
+ return false;
+ }
+ AddBranch(guard_block_id, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ // Start the next block.
+ new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
+ // Reset the mapping of the callee's entry block to point to
+ // the guard block. Do this so we can fix up phis later on to
+ // satisfy dominance.
+ callee2caller[cpi->result_id()] = guard_block_id;
+ }
+ // If callee has early return, insert a header block for
+ // single-trip loop that will encompass callee code. Start
+ // postheader block.
+ //
+ // Note: Consider the following combination:
+ // - the caller is a single block loop
+ // - the callee does not begin with a structure header
+ // - the callee has multiple returns.
+ // We still need to split the caller block and insert a guard
+ // block. But we only need to do it once. We haven't done it yet,
+ // but the single-trip loop header will serve the same purpose.
+ if (earlyReturn) {
+ singleTripLoopHeaderId = context()->TakeNextId();
+ if (singleTripLoopHeaderId == 0) {
+ return false;
+ }
+ AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr =
+ MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
+ returnLabelId = context()->TakeNextId();
+ singleTripLoopContinueId = context()->TakeNextId();
+ if (returnLabelId == 0 || singleTripLoopContinueId == 0) {
+ return false;
+ }
+ AddLoopMerge(returnLabelId, singleTripLoopContinueId,
+ &new_blk_ptr);
+ uint32_t postHeaderId = context()->TakeNextId();
+ if (postHeaderId == 0) {
+ return false;
+ }
+ AddBranch(postHeaderId, &new_blk_ptr);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
+ multiBlocks = true;
+ // Reset the mapping of the callee's entry block to point to
+ // the post-header block. Do this so we can fix up phis later
+ // on to satisfy dominance.
+ callee2caller[cpi->result_id()] = postHeaderId;
+ }
+ } else {
+ multiBlocks = true;
+ }
+ } break;
+ case SpvOpReturnValue: {
+ // Store return value to return variable.
+ assert(returnVarId != 0);
+ uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
+ const auto mapItr = callee2caller.find(valId);
+ if (mapItr != callee2caller.end()) {
+ valId = mapItr->second;
+ }
+ AddStore(returnVarId, valId, &new_blk_ptr);
+
+ // Remember we saw a return; if followed by a label, will need to
+ // insert branch.
+ prevInstWasReturn = true;
+ } break;
+ case SpvOpReturn: {
+ // Remember we saw a return; if followed by a label, will need to
+ // insert branch.
+ prevInstWasReturn = true;
+ } break;
+ case SpvOpFunctionEnd: {
+ // If there was an early return, we generated a return label id
+ // for it. Now we have to generate the return block with that Id.
+ if (returnLabelId != 0) {
+ // If previous instruction was return, insert branch instruction
+ // to return block.
+ if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
+ if (earlyReturn) {
+ // If we generated a loop header for the single-trip loop
+ // to accommodate early returns, insert the continue
+ // target block now, with a false branch back to the loop
+ // header.
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr =
+ MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
+ uint32_t false_id = GetFalseId();
+ if (false_id == 0) {
+ return false;
+ }
+ AddBranchCond(false_id, singleTripLoopHeaderId, returnLabelId,
+ &new_blk_ptr);
+ }
+ // Generate the return block.
+ new_blocks->push_back(std::move(new_blk_ptr));
+ new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
+ multiBlocks = true;
+ }
+ // Load return value into result id of call, if it exists.
+ if (returnVarId != 0) {
+ const uint32_t resId = call_inst_itr->result_id();
+ assert(resId != 0);
+ AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
+ }
+ // Copy remaining instructions from caller block.
+ for (Instruction* inst = call_inst_itr->NextNode(); inst;
+ inst = call_inst_itr->NextNode()) {
+ inst->RemoveFromList();
+ std::unique_ptr<Instruction> cp_inst(inst);
+ // If multiple blocks generated, regenerate any same-block
+ // instruction that has not been seen in this last block.
+ if (multiBlocks) {
+ if (!CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB,
+ &new_blk_ptr)) {
+ return false;
+ }
+
+ // Remember same-block ops in this block.
+ if (IsSameBlockOp(&*cp_inst)) {
+ const uint32_t rid = cp_inst->result_id();
+ postCallSB[rid] = rid;
+ }
+ }
+ new_blk_ptr->AddInstruction(std::move(cp_inst));
+ }
+ // Finalize inline code.
+ new_blocks->push_back(std::move(new_blk_ptr));
+ } break;
+ default: {
+ // Copy callee instruction and remap all input Ids.
+ std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
+ bool succeeded = cp_inst->WhileEachInId(
+ [&callee2caller, &callee_result_ids, this](uint32_t* iid) {
+ const auto mapItr = callee2caller.find(*iid);
+ if (mapItr != callee2caller.end()) {
+ *iid = mapItr->second;
+ } else if (callee_result_ids.find(*iid) !=
+ callee_result_ids.end()) {
+ // Forward reference. Allocate a new id, map it,
+ // use it and check for it when remapping result ids
+ const uint32_t nid = context()->TakeNextId();
+ if (nid == 0) {
+ return false;
+ }
+ callee2caller[*iid] = nid;
+ *iid = nid;
+ }
+ return true;
+ });
+ if (!succeeded) {
+ return false;
+ }
+ // If result id is non-zero, remap it. If already mapped, use mapped
+ // value, else use next id.
+ const uint32_t rid = cp_inst->result_id();
+ if (rid != 0) {
+ const auto mapItr = callee2caller.find(rid);
+ uint32_t nid;
+ if (mapItr != callee2caller.end()) {
+ nid = mapItr->second;
+ } else {
+ nid = context()->TakeNextId();
+ if (nid == 0) {
+ return false;
+ }
+ callee2caller[rid] = nid;
+ }
+ cp_inst->SetResultId(nid);
+ get_decoration_mgr()->CloneDecorations(rid, nid);
}
new_blk_ptr->AddInstruction(std::move(cp_inst));
- }
- if (caller_is_loop_header && callee_begins_with_structured_header) {
- // We can't place both the caller's merge instruction and another
- // merge instruction in the same block. So split the calling block.
- // Insert an unconditional branch to a new guard block. Later,
- // once we know the ID of the last block, we will move the caller's
- // OpLoopMerge from the last generated block into the first block.
- // We also wait to avoid invalidating various iterators.
- const auto guard_block_id = this->TakeNextId();
- AddBranch(guard_block_id, &new_blk_ptr);
- new_blocks->push_back(std::move(new_blk_ptr));
- // Start the next block.
- new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
- // Reset the mapping of the callee's entry block to point to
- // the guard block. Do this so we can fix up phis later on to
- // satisfy dominance.
- callee2caller[cpi->result_id()] = guard_block_id;
- }
- // If callee has early return, insert a header block for
- // single-trip loop that will encompass callee code. Start postheader
- // block.
- //
- // Note: Consider the following combination:
- // - the caller is a single block loop
- // - the callee does not begin with a structure header
- // - the callee has multiple returns.
- // We still need to split the caller block and insert a guard block.
- // But we only need to do it once. We haven't done it yet, but the
- // single-trip loop header will serve the same purpose.
- if (earlyReturn) {
- singleTripLoopHeaderId = this->TakeNextId();
- AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
- new_blocks->push_back(std::move(new_blk_ptr));
- new_blk_ptr =
- MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
- returnLabelId = this->TakeNextId();
- singleTripLoopContinueId = this->TakeNextId();
- AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr);
- uint32_t postHeaderId = this->TakeNextId();
- AddBranch(postHeaderId, &new_blk_ptr);
- new_blocks->push_back(std::move(new_blk_ptr));
- new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
- multiBlocks = true;
- // Reset the mapping of the callee's entry block to point to
- // the post-header block. Do this so we can fix up phis later
- // on to satisfy dominance.
- callee2caller[cpi->result_id()] = postHeaderId;
- }
- } else {
- multiBlocks = true;
+ } break;
}
- } break;
- case SpvOpReturnValue: {
- // Store return value to return variable.
- assert(returnVarId != 0);
- uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
- const auto mapItr = callee2caller.find(valId);
- if (mapItr != callee2caller.end()) {
- valId = mapItr->second;
- }
- AddStore(returnVarId, valId, &new_blk_ptr);
+ return true;
+ });
- // Remember we saw a return; if followed by a label, will need to
- // insert branch.
- prevInstWasReturn = true;
- } break;
- case SpvOpReturn: {
- // Remember we saw a return; if followed by a label, will need to
- // insert branch.
- prevInstWasReturn = true;
- } break;
- case SpvOpFunctionEnd: {
- // If there was an early return, we generated a return label id
- // for it. Now we have to generate the return block with that Id.
- if (returnLabelId != 0) {
- // If previous instruction was return, insert branch instruction
- // to return block.
- if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
- if (earlyReturn) {
- // If we generated a loop header for the single-trip loop
- // to accommodate early returns, insert the continue
- // target block now, with a false branch back to the loop header.
- new_blocks->push_back(std::move(new_blk_ptr));
- new_blk_ptr =
- MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
- AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId,
- &new_blk_ptr);
- }
- // Generate the return block.
- new_blocks->push_back(std::move(new_blk_ptr));
- new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
- multiBlocks = true;
- }
- // Load return value into result id of call, if it exists.
- if (returnVarId != 0) {
- const uint32_t resId = call_inst_itr->result_id();
- assert(resId != 0);
- AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
- }
- // Copy remaining instructions from caller block.
- for (Instruction* inst = call_inst_itr->NextNode(); inst;
- inst = call_inst_itr->NextNode()) {
- inst->RemoveFromList();
- std::unique_ptr<Instruction> cp_inst(inst);
- // If multiple blocks generated, regenerate any same-block
- // instruction that has not been seen in this last block.
- if (multiBlocks) {
- CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, &new_blk_ptr);
- // Remember same-block ops in this block.
- if (IsSameBlockOp(&*cp_inst)) {
- const uint32_t rid = cp_inst->result_id();
- postCallSB[rid] = rid;
- }
- }
- new_blk_ptr->AddInstruction(std::move(cp_inst));
- }
- // Finalize inline code.
- new_blocks->push_back(std::move(new_blk_ptr));
- } break;
- default: {
- // Copy callee instruction and remap all input Ids.
- std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
- cp_inst->ForEachInId([&callee2caller, &callee_result_ids,
- this](uint32_t* iid) {
- const auto mapItr = callee2caller.find(*iid);
- if (mapItr != callee2caller.end()) {
- *iid = mapItr->second;
- } else if (callee_result_ids.find(*iid) != callee_result_ids.end()) {
- // Forward reference. Allocate a new id, map it,
- // use it and check for it when remapping result ids
- const uint32_t nid = this->TakeNextId();
- callee2caller[*iid] = nid;
- *iid = nid;
- }
- });
- // If result id is non-zero, remap it. If already mapped, use mapped
- // value, else use next id.
- const uint32_t rid = cp_inst->result_id();
- if (rid != 0) {
- const auto mapItr = callee2caller.find(rid);
- uint32_t nid;
- if (mapItr != callee2caller.end()) {
- nid = mapItr->second;
- } else {
- nid = this->TakeNextId();
- callee2caller[rid] = nid;
- }
- cp_inst->SetResultId(nid);
- get_decoration_mgr()->CloneDecorations(rid, nid);
- }
- new_blk_ptr->AddInstruction(std::move(cp_inst));
- } break;
- }
- });
+ if (!successful) {
+ return false;
+ }
if (caller_is_loop_header && (new_blocks->size() > 1)) {
// Move the OpLoopMerge from the last block back to the first, where
@@ -532,6 +626,7 @@
for (auto& blk : *new_blocks) {
id2block_[blk->id()] = &*blk;
}
+ return true;
}
bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index e23e7f0..ecfe964 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -41,7 +41,8 @@
protected:
InlinePass();
- // Add pointer to type to module and return resultId.
+ // Add pointer to type to module and return resultId. Returns 0 if the type
+ // could not be created.
uint32_t AddPointerToType(uint32_t type_id, SpvStorageClass storage_class);
// Add unconditional branch to labelId to end of block block_ptr.
@@ -67,20 +68,22 @@
std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
// Returns the id for the boolean false value. Looks in the module first
- // and creates it if not found. Remembers it for future calls.
+ // and creates it if not found. Remembers it for future calls. Returns 0 if
+ // the value could not be created.
uint32_t GetFalseId();
// Map callee params to caller args
void MapParams(Function* calleeFn, BasicBlock::iterator call_inst_itr,
std::unordered_map<uint32_t, uint32_t>* callee2caller);
- // Clone and map callee locals
- void CloneAndMapLocals(Function* calleeFn,
+ // Clone and map callee locals. Return true if successful.
+ bool CloneAndMapLocals(Function* calleeFn,
std::vector<std::unique_ptr<Instruction>>* new_vars,
std::unordered_map<uint32_t, uint32_t>* callee2caller);
- // Create return variable for callee clone code if needed. Return id
- // if created, otherwise 0.
+ // Create return variable for callee clone code. The return type of
+ // |calleeFn| must not be void. Returns the id of the return variable if
+ // created. Returns 0 if the return variable could not be created.
uint32_t CreateReturnVar(Function* calleeFn,
std::vector<std::unique_ptr<Instruction>>* new_vars);
@@ -92,7 +95,7 @@
// Look in preCallSB for instructions that need cloning. Look in
// postCallSB for instructions already cloned. Add cloned instruction
// to postCallSB.
- void CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
+ bool CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* postCallSB,
std::unordered_map<uint32_t, Instruction*>* preCallSB,
std::unique_ptr<BasicBlock>* block_ptr);
@@ -111,7 +114,9 @@
// Also return in new_vars additional OpVariable instructions required by
// and to be inserted into the caller function after the block at
// call_block_itr is replaced with new_blocks.
- void GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
+ //
+ // Returns true if successful.
+ bool GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
std::vector<std::unique_ptr<Instruction>>* new_vars,
BasicBlock::iterator call_inst_itr,
UptrVectorIterator<BasicBlock> call_block_itr);
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 1901f76..47c8134 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -35,14 +35,79 @@
namespace spvtools {
namespace opt {
-void InstBindlessCheckPass::GenBindlessCheckCode(
- BasicBlock::iterator ref_inst_itr,
- UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
- uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
- // Look for reference through bindless descriptor. If not, return.
- std::unique_ptr<BasicBlock> new_blk_ptr;
- uint32_t image_id;
- switch (ref_inst_itr->opcode()) {
+uint32_t InstBindlessCheckPass::GenDebugReadLength(
+ uint32_t var_id, InstructionBuilder* builder) {
+ uint32_t desc_set_idx =
+ var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
+ uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
+ uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+ return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
+}
+
+uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
+ uint32_t desc_idx_id,
+ InstructionBuilder* builder) {
+ uint32_t desc_set_base_id =
+ builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+ uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
+ uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+ uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
+ return GenDebugDirectRead(
+ {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+ builder);
+}
+
+uint32_t InstBindlessCheckPass::CloneOriginalReference(
+ ref_analysis* ref, InstructionBuilder* builder) {
+ // Clone descriptor load
+ Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
+ Instruction* new_load_inst =
+ builder->AddLoad(load_inst->type_id(),
+ load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
+ uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()];
+ uint32_t new_load_id = new_load_inst->result_id();
+ get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
+ uint32_t new_image_id = new_load_id;
+ // Clone Image/SampledImage with new load, if needed
+ if (ref->image_id != 0) {
+ Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
+ if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+ Instruction* new_image_inst = builder->AddBinaryOp(
+ image_inst->type_id(), SpvOpSampledImage, new_load_id,
+ image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
+ uid2offset_[new_image_inst->unique_id()] =
+ uid2offset_[image_inst->unique_id()];
+ new_image_id = new_image_inst->result_id();
+ } else {
+ assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
+ Instruction* new_image_inst =
+ builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
+ uid2offset_[new_image_inst->unique_id()] =
+ uid2offset_[image_inst->unique_id()];
+ new_image_id = new_image_inst->result_id();
+ }
+ get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
+ }
+ // Clone original reference using new image code
+ std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
+ uint32_t ref_result_id = ref->ref_inst->result_id();
+ uint32_t new_ref_id = 0;
+ if (ref_result_id != 0) {
+ new_ref_id = TakeNextId();
+ new_ref_inst->SetResultId(new_ref_id);
+ }
+ new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
+ // Register new reference and add to new block
+ Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
+ uid2offset_[added_inst->unique_id()] =
+ uid2offset_[ref->ref_inst->unique_id()];
+ if (new_ref_id != 0)
+ get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
+ return new_ref_id;
+}
+
+uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
+ switch (inst->opcode()) {
case SpvOp::SpvOpImageSampleImplicitLod:
case SpvOp::SpvOpImageSampleExplicitLod:
case SpvOp::SpvOpImageSampleDrefImplicitLod:
@@ -75,152 +140,205 @@
case SpvOp::SpvOpImageSparseFetch:
case SpvOp::SpvOpImageSparseRead:
case SpvOp::SpvOpImageWrite:
- image_id =
- ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
- break;
+ return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
default:
- return;
+ break;
}
- Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
- uint32_t load_id;
- Instruction* load_inst;
+ return 0;
+}
+
+bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
+ ref_analysis* ref) {
+ ref->image_id = GetDescriptorValueId(ref_inst);
+ if (ref->image_id == 0) return false;
+ Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
- load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
- load_inst = get_def_use_mgr()->GetDef(load_id);
+ ref->load_id =
+ image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
} else if (image_inst->opcode() == SpvOp::SpvOpImage) {
- load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
- load_inst = get_def_use_mgr()->GetDef(load_id);
+ ref->load_id =
+ image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
} else {
- load_id = image_id;
- load_inst = image_inst;
- image_id = 0;
+ ref->load_id = ref->image_id;
+ ref->image_id = 0;
}
+ Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
if (load_inst->opcode() != SpvOp::SpvOpLoad) {
- // TODO(greg-lunarg): Handle additional possibilities
- return;
+ // TODO(greg-lunarg): Handle additional possibilities?
+ return false;
}
- uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
- Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
- if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
- if (ptr_inst->NumInOperands() != 2) {
- assert(false && "unexpected bindless index number");
- return;
+ ref->ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+ Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+ if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
+ ref->index_id = 0;
+ ref->var_id = ref->ptr_id;
+ } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
+ if (ptr_inst->NumInOperands() != 2) {
+ assert(false && "unexpected bindless index number");
+ return false;
+ }
+ ref->index_id =
+ ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+ ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+ Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+ if (var_inst->opcode() != SpvOpVariable) {
+ assert(false && "unexpected bindless base");
+ return false;
+ }
+ } else {
+ // TODO(greg-lunarg): Handle additional possibilities?
+ return false;
}
- uint32_t index_id =
- ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
- ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
- ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
- if (ptr_inst->opcode() != SpvOpVariable) {
- assert(false && "unexpected bindless base");
- return;
- }
- uint32_t var_type_id = ptr_inst->type_id();
- Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
- uint32_t ptr_type_id =
- var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
- Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
- // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
- // out of debug input buffer.
- if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
- // If index and bound both compile-time constants and index < bound,
- // return without changing
- uint32_t length_id =
- ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
- Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
- Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
- if (index_inst->opcode() == SpvOpConstant &&
- length_inst->opcode() == SpvOpConstant &&
- index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
- length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
- return;
- // Generate full runtime bounds test code with true branch
- // being full reference and false branch being debug output and zero
- // for the referenced value.
- MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+ ref->ref_inst = ref_inst;
+ return true;
+}
+
+void InstBindlessCheckPass::GenCheckCode(
+ uint32_t check_id, uint32_t error_id, uint32_t length_id,
+ uint32_t stage_idx, ref_analysis* ref,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ BasicBlock* back_blk_ptr = &*new_blocks->back();
InstructionBuilder builder(
- context(), &*new_blk_ptr,
+ context(), back_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
- uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
- Instruction* ult_inst =
- builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+ // Gen conditional branch on check_id. Valid branch generates original
+ // reference. Invalid generates debug output and zero result (if needed).
uint32_t merge_blk_id = TakeNextId();
uint32_t valid_blk_id = TakeNextId();
uint32_t invalid_blk_id = TakeNextId();
std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
- (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
- invalid_blk_id, merge_blk_id,
- SpvSelectionControlMaskNone);
- // Close selection block and gen valid reference block
- new_blocks->push_back(std::move(new_blk_ptr));
- new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
+ (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
+ merge_blk_id, SpvSelectionControlMaskNone);
+ // Gen valid bounds branch
+ std::unique_ptr<BasicBlock> new_blk_ptr(
+ new BasicBlock(std::move(valid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
- // Clone descriptor load
- Instruction* new_load_inst =
- builder.AddLoad(load_inst->type_id(),
- load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
- uint32_t new_load_id = new_load_inst->result_id();
- get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
- uint32_t new_image_id = new_load_id;
- // Clone Image/SampledImage with new load, if needed
- if (image_id != 0) {
- if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
- Instruction* new_image_inst = builder.AddBinaryOp(
- image_inst->type_id(), SpvOpSampledImage, new_load_id,
- image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
- new_image_id = new_image_inst->result_id();
- } else {
- assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
- Instruction* new_image_inst =
- builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
- new_image_id = new_image_inst->result_id();
- }
- get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
- }
- // Clone original reference using new image code
- std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
- uint32_t ref_result_id = ref_inst_itr->result_id();
- uint32_t new_ref_id = 0;
- if (ref_result_id != 0) {
- new_ref_id = TakeNextId();
- new_ref_inst->SetResultId(new_ref_id);
- }
- new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
- // Register new reference and add to new block
- builder.AddInstruction(std::move(new_ref_inst));
- if (new_ref_id != 0)
- get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
- // Close valid block and gen invalid block
+ uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
+ // Gen invalid block
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
builder.SetInsertPoint(&*new_blk_ptr);
- uint32_t u_index_id = GenUintCastCode(index_id, &builder);
- GenDebugStreamWrite(instruction_idx, stage_idx,
+ uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
+ GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
{error_id, u_index_id, length_id}, &builder);
// Remember last invalid block id
uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
// Gen zero for invalid reference
- uint32_t ref_type_id = ref_inst_itr->type_id();
- // Close invalid block and gen merge block
+ uint32_t ref_type_id = ref->ref_inst->type_id();
(void)builder.AddBranch(merge_blk_id);
new_blocks->push_back(std::move(new_blk_ptr));
+ // Gen merge block
new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
builder.SetInsertPoint(&*new_blk_ptr);
// Gen phi of new reference and zero, if necessary, and replace the
// result id of the original reference with that of the Phi. Kill original
- // reference and move in remainder of original block.
+ // reference.
if (new_ref_id != 0) {
Instruction* phi_inst = builder.AddPhi(
ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
last_invalid_blk_id});
- context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
+ context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
+ phi_inst->result_id());
}
- context()->KillInst(&*ref_inst_itr);
- MovePostludeCode(ref_block_itr, &new_blk_ptr);
- // Add remainder/merge block to new blocks
new_blocks->push_back(std::move(new_blk_ptr));
+ context()->KillInst(ref->ref_inst);
+}
+
+void InstBindlessCheckPass::GenBoundsCheckCode(
+ BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ // Look for reference through indexed descriptor. If found, analyze and
+ // save components. If not, return.
+ ref_analysis ref;
+ if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+ Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+ if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
+ // If index and bound both compile-time constants and index < bound,
+ // return without changing
+ Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
+ uint32_t var_type_id = var_inst->type_id();
+ Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
+ uint32_t desc_type_id =
+ var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
+ Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id);
+ uint32_t length_id = 0;
+ if (desc_type_inst->opcode() == SpvOpTypeArray) {
+ length_id =
+ desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+ Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+ Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+ if (index_inst->opcode() == SpvOpConstant &&
+ length_inst->opcode() == SpvOpConstant &&
+ index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+ length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+ return;
+ } else if (!input_length_enabled_ ||
+ desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
+ return;
+ }
+ // Move original block's preceding instructions into first new block
+ std::unique_ptr<BasicBlock> new_blk_ptr;
+ MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+ InstructionBuilder builder(
+ context(), &*new_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+ // If length id not yet set, descriptor array is runtime size so
+ // generate load of length from stage's debug input buffer.
+ if (length_id == 0) {
+ assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
+ "unexpected bindless type");
+ length_id = GenDebugReadLength(ref.var_id, &builder);
+ }
+ // Generate full runtime bounds test code with true branch
+ // being full reference and false branch being debug output and zero
+ // for the referenced value.
+ Instruction* ult_inst =
+ builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
+ GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+ new_blocks);
+ // Move original block's remaining code into remainder/merge block and add
+ // to new blocks
+ BasicBlock* back_blk_ptr = &*new_blocks->back();
+ MovePostludeCode(ref_block_itr, back_blk_ptr);
+}
+
+void InstBindlessCheckPass::GenInitCheckCode(
+ BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ // Look for reference through descriptor. If not, return.
+ ref_analysis ref;
+ if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+ // Move original block's preceding instructions into first new block
+ std::unique_ptr<BasicBlock> new_blk_ptr;
+ MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+ InstructionBuilder builder(
+ context(), &*new_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ new_blocks->push_back(std::move(new_blk_ptr));
+ // Read initialization status from debug input buffer. If index id not yet
+ // set, binding is single descriptor, so set index to constant 0.
+ uint32_t zero_id = builder.GetUintConstantId(0u);
+ if (ref.index_id == 0) ref.index_id = zero_id;
+ uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
+ // Generate full runtime non-zero init test code with true branch
+ // being full reference and false branch being debug output and zero
+ // for the referenced value.
+ Instruction* uneq_inst =
+ builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
+ uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
+ GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
+ new_blocks);
+ // Move original block's remaining code into remainder/merge block and add
+ // to new blocks
+ BasicBlock* back_blk_ptr = &*new_blocks->back();
+ MovePostludeCode(ref_block_itr, back_blk_ptr);
}
void InstBindlessCheckPass::InitializeInstBindlessCheck() {
@@ -236,21 +354,43 @@
break;
}
}
+ // If descriptor indexing extension and runtime array length support enabled,
+ // create variable mappings. Length support is always enabled if descriptor
+ // init check is enabled.
+ if (ext_descriptor_indexing_defined_ && input_length_enabled_)
+ for (auto& anno : get_module()->annotations())
+ if (anno.opcode() == SpvOpDecorate) {
+ if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
+ var2desc_set_[anno.GetSingleWordInOperand(0u)] =
+ anno.GetSingleWordInOperand(2u);
+ else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
+ var2binding_[anno.GetSingleWordInOperand(0u)] =
+ anno.GetSingleWordInOperand(2u);
+ }
}
Pass::Status InstBindlessCheckPass::ProcessImpl() {
- // Perform instrumentation on each entry point function in module
+ // Perform bindless bounds check on each entry point function in module
InstProcessFunction pfn =
[this](BasicBlock::iterator ref_inst_itr,
- UptrVectorIterator<BasicBlock> ref_block_itr,
- uint32_t instruction_idx, uint32_t stage_idx,
+ UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
- return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
- instruction_idx, stage_idx, new_blocks);
+ return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+ new_blocks);
};
bool modified = InstProcessEntryPointCallTree(pfn);
- // This pass does not update inst->blk info
- context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+ if (ext_descriptor_indexing_defined_ && input_init_enabled_) {
+ // Perform descriptor initialization check on each entry point function in
+ // module
+ pfn = [this](BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+ return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+ new_blocks);
+ };
+ modified |= InstProcessEntryPointCallTree(pfn);
+ }
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index 3ab5ab7..79c34f1 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -29,10 +29,16 @@
class InstBindlessCheckPass : public InstrumentPass {
public:
// For test harness only
- InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+ InstBindlessCheckPass()
+ : InstrumentPass(7, 23, kInstValidationIdBindless),
+ input_length_enabled_(true),
+ input_init_enabled_(true) {}
// For all other interfaces
- InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
- : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+ InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+ bool input_length_enable, bool input_init_enable)
+ : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
+ input_length_enabled_(input_length_enable),
+ input_init_enabled_(input_init_enable) {}
~InstBindlessCheckPass() override = default;
@@ -42,28 +48,40 @@
const char* name() const override { return "inst-bindless-check-pass"; }
private:
- // Initialize state for instrumenting bindless checking
- void InitializeInstBindlessCheck();
-
- // This function does bindless checking instrumentation on a single
- // instruction. It is designed to be passed to
+ // These functions do bindless checking instrumentation on a single
+ // instruction which references through a descriptor (ie references into an
+ // image or buffer). Refer to Vulkan API for further information on
+ // descriptors. GenBoundsCheckCode checks that an index into a descriptor
+ // array (array of images or buffers) is in-bounds. GenInitCheckCode
+ // checks that the referenced descriptor has been initialized, if the
+ // SPV_EXT_descriptor_indexing extension is enabled.
+ //
+ // TODO(greg-lunarg): Add support for buffers. Currently only does
+ // checking of references of images.
+ //
+ // The functions are designed to be passed to
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
// function to each instruction in a module and replaces the instruction
// if warranted.
//
// If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
// result of instrumenting it with validation code within its block at
- // |ref_block_itr|. Specifically, generate code to check that the index
- // into the descriptor array is in-bounds. If the check passes, execute
- // the remainder of the reference, otherwise write a record to the debug
+ // |ref_block_itr|. The validation code first executes a check for the
+ // specific condition called for. If the check passes, it executes
+ // the remainder of the reference, otherwise writes a record to the debug
// output buffer stream including |function_idx, instruction_idx, stage_idx|
- // and replace the reference with the null value of the original type. The
+ // and replaces the reference with the null value of the original type. The
// block at |ref_block_itr| can just be replaced with the blocks in
// |new_blocks|, which will contain at least two blocks. The last block will
// comprise all instructions following |ref_inst_itr|,
// preceded by a phi instruction.
//
- // This instrumentation pass utilizes GenDebugStreamWrite() to write its
+ // These instrumentation functions utilize GenDebugDirectRead() to read data
+ // from the debug input buffer, specifically the lengths of variable length
+ // descriptor arrays, and the initialization status of each descriptor.
+ // The format of the debug input buffer is documented in instrument.hpp.
+ //
+ // These instrumentation functions utilize GenDebugStreamWrite() to write its
// error records. The validation-specific part of the error record will
// have the format:
//
@@ -76,15 +94,83 @@
//
// The Descriptor Array Size is the size of the descriptor array which was
// indexed.
- void GenBindlessCheckCode(
- BasicBlock::iterator ref_inst_itr,
- UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
- uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+ void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+ void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
+ UptrVectorIterator<BasicBlock> ref_block_itr,
+ uint32_t stage_idx,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+ // Generate instructions into |builder| to read length of runtime descriptor
+ // array |var_id| from debug input buffer and return id of value.
+ uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
+
+ // Generate instructions into |builder| to read initialization status of
+ // descriptor array |image_id| at |index_id| from debug input buffer and
+ // return id of value.
+ uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id,
+ InstructionBuilder* builder);
+
+ // Analysis data for descriptor reference components, generated by
+ // AnalyzeDescriptorReference. It is necessary and sufficient for further
+ // analysis and regeneration of the reference.
+ typedef struct ref_analysis {
+ uint32_t image_id;
+ uint32_t load_id;
+ uint32_t ptr_id;
+ uint32_t var_id;
+ uint32_t index_id;
+ Instruction* ref_inst;
+ } ref_analysis;
+
+ // Clone original original reference encapsulated by |ref| into |builder|.
+ // This may generate more than one instruction if neccessary.
+ uint32_t CloneOriginalReference(ref_analysis* ref,
+ InstructionBuilder* builder);
+
+ // If |inst| references through a descriptor, (ie references into an image
+ // or buffer), return the id of the value it references. Else return 0.
+ uint32_t GetDescriptorValueId(Instruction* inst);
+
+ // Analyze descriptor reference |ref_inst| and save components into |ref|.
+ // Return true if |ref_inst| is a descriptor reference, false otherwise.
+ bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref);
+
+ // Generate instrumentation code for generic test result |check_id|, starting
+ // with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
+ // Generate conditional branch to a valid or invalid branch. Generate valid
+ // block which does original reference |ref|. Generate invalid block which
+ // writes debug error output utilizing |ref|, |error_id|, |length_id| and
+ // |stage_idx|. Generate merge block for valid and invalid branches. Kill
+ // original reference.
+ void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
+ uint32_t stage_idx, ref_analysis* ref,
+ std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+ // Initialize state for instrumenting bindless checking
+ void InitializeInstBindlessCheck();
+
+ // Apply GenBoundsCheckCode to every instruction in module. Then apply
+ // GenInitCheckCode to every instruction in module.
Pass::Status ProcessImpl();
// True if VK_EXT_descriptor_indexing is defined
bool ext_descriptor_indexing_defined_;
+
+ // Enable instrumentation of runtime array length checking
+ bool input_length_enabled_;
+
+ // Enable instrumentation of descriptor initialization checking
+ bool input_init_enabled_;
+
+ // Mapping from variable to descriptor set
+ std::unordered_map<uint32_t, uint32_t> var2desc_set_;
+
+ // Mapping from variable to binding
+ std::unordered_map<uint32_t, uint32_t> var2binding_;
};
} // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 5f3c5a8..5a82e53 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -155,12 +155,6 @@
}
Instruction* Instruction::GetBaseAddress() const {
- assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain ||
- opcode() == SpvOpPtrAccessChain ||
- opcode() == SpvOpInBoundsAccessChain || opcode() == SpvOpCopyObject ||
- opcode() == SpvOpImageTexelPointer) &&
- "GetBaseAddress should only be called on instructions that take a "
- "pointer or image.");
uint32_t base = GetSingleWordInOperand(kLoadBaseIndex);
Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base);
bool done = false;
@@ -182,24 +176,6 @@
break;
}
}
-
- switch (opcode()) {
- case SpvOpLoad:
- case SpvOpStore:
- case SpvOpAccessChain:
- case SpvOpInBoundsAccessChain:
- case SpvOpPtrAccessChain:
- case SpvOpImageTexelPointer:
- case SpvOpCopyObject:
- // A load or store through a pointer.
- assert(base_inst->IsValidBasePointer() &&
- "We cannot have a base pointer come from this load");
- break;
- default:
- // A load or store of an image.
- assert(base_inst->IsValidBaseImage() && "We are expecting an image.");
- break;
- }
return base_inst;
}
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 7f56a1e..668c980 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -57,8 +57,7 @@
}
void InstrumentPass::MovePostludeCode(
- UptrVectorIterator<BasicBlock> ref_block_itr,
- std::unique_ptr<BasicBlock>* new_blk_ptr) {
+ UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
// new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
// Move contents of original ref block.
for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
@@ -77,7 +76,7 @@
same_block_post_[rid] = rid;
}
}
- (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+ new_blk_ptr->AddInstruction(std::move(mv_inst));
}
}
@@ -107,7 +106,7 @@
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
builder->GetUintConstantId(field_offset));
uint32_t buf_id = GetOutputBufferId();
- uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+ uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* achain_inst =
builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
builder->GetUintConstantId(kDebugOutputDataOffset),
@@ -168,10 +167,10 @@
switch (stage_idx) {
case SpvExecutionModelVertex: {
// Load and store VertexId and InstanceId
- GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
- kInstVertOutVertexId, base_offset_id, builder);
- GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
- kInstVertOutInstanceId, base_offset_id, builder);
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexIndex),
+ kInstVertOutVertexIndex, base_offset_id, builder);
+ GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceIndex),
+ kInstVertOutInstanceIndex, base_offset_id, builder);
} break;
case SpvExecutionModelGLCompute: {
// Load and store GlobalInvocationId. Second word is unused; store zero.
@@ -222,6 +221,16 @@
(void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
}
+uint32_t InstrumentPass::GenDebugDirectRead(
+ const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+ // Call debug input function. Pass func_idx and offset ids as args.
+ uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
+ uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
+ std::vector<uint32_t> args = {input_func_id};
+ (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
+ return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+}
+
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
}
@@ -230,7 +239,7 @@
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
- std::unique_ptr<BasicBlock>* block_ptr) {
+ BasicBlock* block_ptr) {
(*inst)->ForEachInId(
[&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
const auto map_itr = (*same_blk_post).find(*iid);
@@ -247,7 +256,7 @@
sb_inst->SetResultId(nid);
(*same_blk_post)[rid] = nid;
*iid = nid;
- (*block_ptr)->AddInstruction(std::move(sb_inst));
+ block_ptr->AddInstruction(std::move(sb_inst));
}
} else {
// Reset same-block op operand.
@@ -280,12 +289,12 @@
}
// Return id for output buffer uint ptr type
-uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
- if (output_buffer_uint_ptr_id_ == 0) {
- output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+uint32_t InstrumentPass::GetBufferUintPtrId() {
+ if (buffer_uint_ptr_id_ == 0) {
+ buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
GetUintId(), SpvStorageClassStorageBuffer);
}
- return output_buffer_uint_ptr_id_;
+ return buffer_uint_ptr_id_;
}
uint32_t InstrumentPass::GetOutputBufferBinding() {
@@ -298,20 +307,74 @@
return 0;
}
+uint32_t InstrumentPass::GetInputBufferBinding() {
+ switch (validation_id_) {
+ case kInstValidationIdBindless:
+ return kDebugInputBindingBindless;
+ default:
+ assert(false && "unexpected validation id");
+ }
+ return 0;
+}
+
+analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
+ analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
+ if (uint_rarr_ty_ == nullptr) {
+ analysis::Integer uint_ty(32, false);
+ analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+ analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
+ uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
+ uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
+ // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
+ // a block, and will therefore be decorated with an ArrayStride. Therefore
+ // the undecorated type returned here will not be pre-existing and can
+ // safely be decorated. Since this type is now decorated, it is out of
+ // sync with the TypeManager and therefore the TypeManager must be
+ // invalidated after this pass.
+ assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+ "used RuntimeArray type returned");
+ deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
+ }
+ return uint_rarr_ty_;
+}
+
+void InstrumentPass::AddStorageBufferExt() {
+ if (storage_buffer_ext_defined_) return;
+ if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
+ const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+ const auto num_chars = ext_name.size();
+ // Compute num words, accommodate the terminating null character.
+ const auto num_words = (num_chars + 1 + 3) / 4;
+ std::vector<uint32_t> ext_words(num_words, 0u);
+ std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+ context()->AddExtension(std::unique_ptr<Instruction>(
+ new Instruction(context(), SpvOpExtension, 0u, 0u,
+ {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+ }
+ storage_buffer_ext_defined_ = true;
+}
+
// Return id for output buffer
uint32_t InstrumentPass::GetOutputBufferId() {
if (output_buffer_id_ == 0) {
// If not created yet, create one
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Type* reg_uint_rarr_ty =
+ GetUintRuntimeArrayType(deco_mgr, type_mgr);
analysis::Integer uint_ty(32, false);
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
- analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
- analysis::Type* reg_uint_rarr_ty =
- type_mgr->GetRegisteredType(&uint_rarr_ty);
- analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
- analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
- uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+ analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
+ analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+ uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+ // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+ // must be a block, and will therefore be decorated with Block. Therefore
+ // the undecorated type returned here will not be pre-existing and can
+ // safely be decorated. Since this type is now decorated, it is out of
+ // sync with the TypeManager and therefore the TypeManager must be
+ // invalidated after this pass.
+ assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
+ "used struct type returned");
deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
SpvDecorationOffset, 0);
@@ -329,23 +392,48 @@
desc_set_);
deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
GetOutputBufferBinding());
- // Look for storage buffer extension. If none, create one.
- if (!get_feature_mgr()->HasExtension(
- kSPV_KHR_storage_buffer_storage_class)) {
- const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
- const auto num_chars = ext_name.size();
- // Compute num words, accommodate the terminating null character.
- const auto num_words = (num_chars + 1 + 3) / 4;
- std::vector<uint32_t> ext_words(num_words, 0u);
- std::memcpy(ext_words.data(), ext_name.data(), num_chars);
- context()->AddExtension(std::unique_ptr<Instruction>(
- new Instruction(context(), SpvOpExtension, 0u, 0u,
- {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
- }
+ AddStorageBufferExt();
}
return output_buffer_id_;
}
+uint32_t InstrumentPass::GetInputBufferId() {
+ if (input_buffer_id_ == 0) {
+ // If not created yet, create one
+ analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::Type* reg_uint_rarr_ty =
+ GetUintRuntimeArrayType(deco_mgr, type_mgr);
+ analysis::Struct buf_ty({reg_uint_rarr_ty});
+ analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+ uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+ // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+ // must be a block, and will therefore be decorated with Block. Therefore
+ // the undecorated type returned here will not be pre-existing and can
+ // safely be decorated. Since this type is now decorated, it is out of
+ // sync with the TypeManager and therefore the TypeManager must be
+ // invalidated after this pass.
+ assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
+ "used struct type returned");
+ deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
+ deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
+ uint32_t ibufTyPtrId_ =
+ type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
+ input_buffer_id_ = TakeNextId();
+ std::unique_ptr<Instruction> newVarOp(new Instruction(
+ context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {SpvStorageClassStorageBuffer}}}));
+ context()->AddGlobalValue(std::move(newVarOp));
+ deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
+ desc_set_);
+ deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
+ GetInputBufferBinding());
+ AddStorageBufferExt();
+ }
+ return input_buffer_id_;
+}
+
uint32_t InstrumentPass::GetVec4FloatId() {
if (v4float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -445,7 +533,7 @@
// Gen test if debug output buffer size will not be exceeded.
uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
uint32_t buf_id = GetOutputBufferId();
- uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+ uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* obuf_curr_sz_ac_inst =
builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
builder.GetUintConstantId(kDebugOutputSizeOffset));
@@ -513,6 +601,81 @@
return output_func_id_;
}
+uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
+ uint32_t func_id = param2input_func_id_[param_cnt];
+ if (func_id != 0) return func_id;
+ // Create input function for param_cnt
+ func_id = TakeNextId();
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ std::vector<const analysis::Type*> param_types;
+ for (uint32_t c = 0; c < param_cnt; ++c)
+ param_types.push_back(type_mgr->GetType(GetUintId()));
+ analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types);
+ analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+ std::unique_ptr<Instruction> func_inst(new Instruction(
+ get_module()->context(), SpvOpFunction, GetUintId(), func_id,
+ {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+ {SpvFunctionControlMaskNone}},
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+ {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+ std::unique_ptr<Function> input_func =
+ MakeUnique<Function>(std::move(func_inst));
+ // Add parameters
+ std::vector<uint32_t> param_vec;
+ for (uint32_t c = 0; c < param_cnt; ++c) {
+ uint32_t pid = TakeNextId();
+ param_vec.push_back(pid);
+ std::unique_ptr<Instruction> param_inst(new Instruction(
+ get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {}));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
+ input_func->AddParameter(std::move(param_inst));
+ }
+ // Create block
+ uint32_t blk_id = TakeNextId();
+ std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
+ std::unique_ptr<BasicBlock> new_blk_ptr =
+ MakeUnique<BasicBlock>(std::move(blk_label));
+ InstructionBuilder builder(
+ context(), &*new_blk_ptr,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ // For each offset parameter, generate new offset with parameter, adding last
+ // loaded value if it exists, and load value from input buffer at new offset.
+ // Return last loaded value.
+ uint32_t buf_id = GetInputBufferId();
+ uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
+ uint32_t last_value_id = 0;
+ for (uint32_t p = 0; p < param_cnt; ++p) {
+ uint32_t offset_id;
+ if (p == 0) {
+ offset_id = param_vec[0];
+ } else {
+ Instruction* offset_inst = builder.AddBinaryOp(
+ GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]);
+ offset_id = offset_inst->result_id();
+ }
+ Instruction* ac_inst = builder.AddTernaryOp(
+ buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+ builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
+ Instruction* load_inst =
+ builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id());
+ last_value_id = load_inst->result_id();
+ }
+ (void)builder.AddInstruction(MakeUnique<Instruction>(
+ context(), SpvOpReturnValue, 0, 0,
+ std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
+ // Close block and function and add function to module
+ new_blk_ptr->SetParent(&*input_func);
+ input_func->AddBasicBlock(std::move(new_blk_ptr));
+ std::unique_ptr<Instruction> func_end_inst(
+ new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
+ get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
+ input_func->SetFunctionEnd(std::move(func_end_inst));
+ context()->AddFunction(std::move(input_func));
+ param2input_func_id_[param_cnt] = func_id;
+ return func_id;
+}
+
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
InstProcessFunction& pfn) {
bool modified = false;
@@ -523,21 +686,17 @@
++function_idx;
}
std::vector<std::unique_ptr<BasicBlock>> new_blks;
- // Start count after function instruction
- uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
// Using block iterators here because of block erasures and insertions.
for (auto bi = func->begin(); bi != func->end(); ++bi) {
- // Count block's label
- ++instruction_idx;
- for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
- // Bump instruction count if debug instructions
- instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
+ for (auto ii = bi->begin(); ii != bi->end();) {
// Generate instrumentation if warranted
- pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+ pfn(ii, bi, stage_idx, &new_blks);
if (new_blks.size() == 0) {
++ii;
continue;
}
+ // Add new blocks to label id map
+ for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
// If there are new blocks we know there will always be two or
// more, so update succeeding phis with label of new last block.
size_t newBlocksSize = new_blks.size();
@@ -567,6 +726,9 @@
uint32_t stage_idx) {
bool modified = false;
std::unordered_set<uint32_t> done;
+ // Don't process input and output functions
+ for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
+ if (output_func_id_ != 0) done.insert(output_func_id_);
// Process all functions from roots
while (!roots->empty()) {
const uint32_t fi = roots->front();
@@ -618,14 +780,17 @@
void InstrumentPass::InitializeInstrument() {
output_buffer_id_ = 0;
- output_buffer_uint_ptr_id_ = 0;
+ buffer_uint_ptr_id_ = 0;
output_func_id_ = 0;
output_func_param_cnt_ = 0;
+ input_buffer_id_ = 0;
v4float_id_ = 0;
uint_id_ = 0;
v4uint_id_ = 0;
bool_id_ = 0;
void_id_ = 0;
+ storage_buffer_ext_defined_ = false;
+ uint_rarr_ty_ = nullptr;
// clear collections
id2function_.clear();
@@ -639,70 +804,68 @@
}
}
- // Calculate instruction offset of first function
- uint32_t pre_func_size = 0;
+ // Remember original instruction offsets
+ uint32_t module_offset = 0;
Module* module = get_module();
for (auto& i : context()->capabilities()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->extensions()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->ext_inst_imports()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
- ++pre_func_size; // memory_model
+ ++module_offset; // memory_model
for (auto& i : module->entry_points()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->execution_modes()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->debugs1()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->debugs2()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->debugs3()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->annotations()) {
(void)i;
- ++pre_func_size;
+ ++module_offset;
}
for (auto& i : module->types_values()) {
- pre_func_size += 1;
- pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
+ module_offset += 1;
+ module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
}
- funcIdx2offset_[0] = pre_func_size;
- // Set instruction offsets for all other functions.
- uint32_t func_idx = 1;
- auto prev_fn = get_module()->begin();
- auto curr_fn = prev_fn;
- for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
- // Count function and end instructions
- uint32_t func_size = 2;
- for (auto& blk : *prev_fn) {
+ auto curr_fn = get_module()->begin();
+ for (; curr_fn != get_module()->end(); ++curr_fn) {
+ // Count function instruction
+ module_offset += 1;
+ curr_fn->ForEachParam(
+ [&module_offset](const Instruction*) { module_offset += 1; }, true);
+ for (auto& blk : *curr_fn) {
// Count label
- func_size += 1;
+ module_offset += 1;
for (auto& inst : blk) {
- func_size += 1;
- func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+ module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
+ uid2offset_[inst.unique_id()] = module_offset;
+ module_offset += 1;
}
}
- funcIdx2offset_[func_idx] = func_size;
- ++prev_fn;
- ++func_idx;
+ // Count function end instruction
+ module_offset += 1;
}
}
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index baf510b..c4b97d6 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -65,17 +65,16 @@
using cbb_ptr = const BasicBlock*;
public:
- using InstProcessFunction = std::function<void(
- BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
- std::vector<std::unique_ptr<BasicBlock>>*)>;
+ using InstProcessFunction =
+ std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
+ uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
~InstrumentPass() override = default;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse |
- IRContext::kAnalysisInstrToBlockMapping |
- IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
- IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
+ return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
+ IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
}
protected:
@@ -106,7 +105,7 @@
// Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
// to be instrumented into block |new_blk_ptr|.
void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
- std::unique_ptr<BasicBlock>* new_blk_ptr);
+ BasicBlock* new_blk_ptr);
// Generate instructions in |builder| which will atomically fetch and
// increment the size of the debug output buffer stream of the current
@@ -194,6 +193,17 @@
const std::vector<uint32_t>& validation_ids,
InstructionBuilder* builder);
+ // Generate in |builder| instructions to read the unsigned integer from the
+ // input buffer specified by the offsets in |offset_ids|. Given offsets
+ // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
+ //
+ // ibuf[...ibuf[ibuf[o0]+o1]...+oN]
+ //
+ // The binding and the format of the input buffer is determined by each
+ // specific validation, which is specified at the creation of the pass.
+ uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
+ InstructionBuilder* builder);
+
// Generate code to cast |value_id| to unsigned, if needed. Return
// an id to the unsigned equivalent.
uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@@ -210,15 +220,28 @@
// Return id for void type
uint32_t GetVoidId();
- // Return id for output buffer uint type
- uint32_t GetOutputBufferUintPtrId();
+ // Return pointer to type for runtime array of uint
+ analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
+ analysis::TypeManager* type_mgr);
+
+ // Return id for buffer uint type
+ uint32_t GetBufferUintPtrId();
// Return binding for output buffer for current validation.
uint32_t GetOutputBufferBinding();
+ // Return binding for input buffer for current validation.
+ uint32_t GetInputBufferBinding();
+
+ // Add storage buffer extension if needed
+ void AddStorageBufferExt();
+
// Return id for debug output buffer
uint32_t GetOutputBufferId();
+ // Return id for debug input buffer
+ uint32_t GetInputBufferId();
+
// Return id for v4float type
uint32_t GetVec4FloatId();
@@ -226,10 +249,14 @@
uint32_t GetVec4UintId();
// Return id for output function. Define if it doesn't exist with
- // |val_spec_arg_cnt| validation-specific uint32 arguments.
+ // |val_spec_param_cnt| validation-specific uint32 parameters.
uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
uint32_t val_spec_param_cnt);
+ // Return id for input function taking |param_cnt| uint32 parameters. Define
+ // if it doesn't exist.
+ uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
+
// Apply instrumentation function |pfn| to every instruction in |func|.
// If code is generated for an instruction, replace the instruction's
// block with the new blocks that are generated. Continue processing at the
@@ -291,7 +318,7 @@
std::unique_ptr<Instruction>* inst,
std::unordered_map<uint32_t, uint32_t>* same_blk_post,
std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
- std::unique_ptr<BasicBlock>* block_ptr);
+ BasicBlock* block_ptr);
// Update phis in succeeding blocks to point to new last block
void UpdateSucceedingPhis(
@@ -310,8 +337,8 @@
// CFG. It has functionality not present in CFG. Consolidate.
std::unordered_map<uint32_t, BasicBlock*> id2block_;
- // Map from function's position index to the offset of its first instruction
- std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
+ // Map from instruction's unique id to offset in original file.
+ std::unordered_map<uint32_t, uint32_t> uid2offset_;
// result id for OpConstantFalse
uint32_t validation_id_;
@@ -320,14 +347,20 @@
uint32_t output_buffer_id_;
// type id for output buffer element
- uint32_t output_buffer_uint_ptr_id_;
+ uint32_t buffer_uint_ptr_id_;
// id for debug output function
uint32_t output_func_id_;
+ // ids for debug input functions
+ std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
+
// param count for output function
uint32_t output_func_param_cnt_;
+ // id for input buffer variable
+ uint32_t input_buffer_id_;
+
// id for v4float type
uint32_t v4float_id_;
@@ -343,6 +376,12 @@
// id for void type
uint32_t void_id_;
+ // boolean to remember storage buffer extension
+ bool storage_buffer_ext_defined_;
+
+ // runtime array of uint type
+ analysis::Type* uint_rarr_ty_;
+
// Pre-instrumentation same-block insts
std::unordered_map<uint32_t, Instruction*> same_block_pre_;
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 2f741d8..da74055 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -455,6 +455,16 @@
return AddInstruction(std::move(new_inst));
}
+ Instruction* AddStore(uint32_t ptr_id, uint32_t obj_id) {
+ std::vector<Operand> operands;
+ operands.push_back({SPV_OPERAND_TYPE_ID, {ptr_id}});
+ operands.push_back({SPV_OPERAND_TYPE_ID, {obj_id}});
+
+ std::unique_ptr<Instruction> new_inst(
+ new Instruction(GetContext(), SpvOpStore, 0, 0, operands));
+ return AddInstruction(std::move(new_inst));
+ }
+
// Inserts the new instruction before the insertion point.
Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index af9ac1a..61c5425 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -74,6 +74,12 @@
if (set & kAnalysisIdToFuncMapping) {
BuildIdToFuncMapping();
}
+ if (set & kAnalysisConstants) {
+ BuildConstantManager();
+ }
+ if (set & kAnalysisTypes) {
+ BuildTypeManager();
+ }
}
void IRContext::InvalidateAnalysesExceptFor(
@@ -83,6 +89,11 @@
}
void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
+ // The ConstantManager contains Type pointers. If the TypeManager goes
+ // away, the ConstantManager has to go away.
+ if (analyses_to_invalidate & kAnalysisTypes) {
+ analyses_to_invalidate |= kAnalysisConstants;
+ }
if (analyses_to_invalidate & kAnalysisDefUse) {
def_use_mgr_.reset(nullptr);
}
@@ -117,6 +128,12 @@
if (analyses_to_invalidate & kAnalysisIdToFuncMapping) {
id_to_func_.clear();
}
+ if (analyses_to_invalidate & kAnalysisConstants) {
+ constant_mgr_.reset(nullptr);
+ }
+ if (analyses_to_invalidate & kAnalysisTypes) {
+ type_mgr_.reset(nullptr);
+ }
valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate);
}
@@ -328,6 +345,7 @@
SpvOpTypeImage,
SpvOpTypeSampler,
SpvOpTypeSampledImage,
+ SpvOpTypeAccelerationStructureNV,
SpvOpTypeArray,
SpvOpTypeRuntimeArray,
SpvOpTypeStruct,
@@ -656,8 +674,8 @@
reg_type = type_mgr->GetRegisteredType(&v4float_ty);
break;
}
- case SpvBuiltInVertexId:
- case SpvBuiltInInstanceId:
+ case SpvBuiltInVertexIndex:
+ case SpvBuiltInInstanceIndex:
case SpvBuiltInPrimitiveId:
case SpvBuiltInInvocationId:
case SpvBuiltInGlobalInvocationId: {
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 94bfd01..185b494 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -76,7 +76,9 @@
kAnalysisStructuredCFG = 1 << 11,
kAnalysisBuiltinVarId = 1 << 12,
kAnalysisIdToFuncMapping = 1 << 13,
- kAnalysisEnd = 1 << 14
+ kAnalysisConstants = 1 << 14,
+ kAnalysisTypes = 1 << 15,
+ kAnalysisEnd = 1 << 16
};
using ProcessFunction = std::function<bool(Function*)>;
@@ -292,8 +294,9 @@
// created yet, it creates one. NOTE: Once created, the constant manager
// remains active and it is never re-built.
analysis::ConstantManager* get_constant_mgr() {
- if (!constant_mgr_)
- constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+ if (!AreAnalysesValid(kAnalysisConstants)) {
+ BuildConstantManager();
+ }
return constant_mgr_.get();
}
@@ -301,8 +304,9 @@
// yet, it creates one. NOTE: Once created, the type manager remains active it
// is never re-built.
analysis::TypeManager* get_type_mgr() {
- if (!type_mgr_)
- type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+ if (!AreAnalysesValid(kAnalysisTypes)) {
+ BuildTypeManager();
+ }
return type_mgr_.get();
}
@@ -584,6 +588,20 @@
valid_analyses_ = valid_analyses_ | kAnalysisStructuredCFG;
}
+ // Builds the constant manager from scratch, even if it was already
+ // valid.
+ void BuildConstantManager() {
+ constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+ valid_analyses_ = valid_analyses_ | kAnalysisConstants;
+ }
+
+ // Builds the type manager from scratch, even if it was already
+ // valid.
+ void BuildTypeManager() {
+ type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+ valid_analyses_ = valid_analyses_ | kAnalysisTypes;
+ }
+
// Removes all computed dominator and post-dominator trees. This will force
// the context to rebuild the trees on demand.
void ResetDominatorAnalysis() {
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 46e2bee..edd4832 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -116,8 +116,10 @@
opcode == SpvOpUndef) {
module_->AddGlobalValue(std::move(spv_inst));
} else {
- SPIRV_UNIMPLEMENTED(consumer_,
- "unhandled inst type outside function definition");
+ Errorf(consumer_, src, loc,
+ "Unhandled inst type (opcode: %d) found outside function definition.",
+ opcode);
+ return false;
}
} else {
if (block_ == nullptr) { // Inside function but outside blocks
diff --git a/source/opt/legalize_vector_shuffle_pass.cpp b/source/opt/legalize_vector_shuffle_pass.cpp
new file mode 100644
index 0000000..b5d5d59
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/legalize_vector_shuffle_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status LegalizeVectorShufflePass::Process() {
+ bool changed = false;
+ context()->module()->ForEachInst([&changed](Instruction* inst) {
+ if (inst->opcode() != SpvOpVectorShuffle) return;
+
+ for (uint32_t idx = 2; idx < inst->NumInOperands(); ++idx) {
+ auto literal = inst->GetSingleWordInOperand(idx);
+ if (literal != 0xFFFFFFFF) continue;
+ changed = true;
+ inst->SetInOperand(idx, {0});
+ }
+ });
+
+ return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/legalize_vector_shuffle_pass.h b/source/opt/legalize_vector_shuffle_pass.h
new file mode 100644
index 0000000..ca6e1df
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+#define SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts any usages of 0xFFFFFFFF for the literals in OpVectorShuffle to a
+// literal 0. This is needed because using OxFFFFFFFF is forbidden by the WebGPU
+// spec. 0xFFFFFFFF in the main spec indicates that the result for this
+// component has no source, thus is undefined. Since this is undefined
+// behaviour we are free to use 0.
+class LegalizeVectorShufflePass : public Pass {
+ public:
+ const char* name() const override { return "legalize-vector-shuffle"; }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisScalarEvolution |
+ IRContext::kAnalysisRegisterPressure |
+ IRContext::kAnalysisValueNumberTable |
+ IRContext::kAnalysisStructuredCFG |
+ IRContext::kAnalysisBuiltinVarId |
+ IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+ }
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
diff --git a/source/opt/licm_pass.cpp b/source/opt/licm_pass.cpp
index c553221..82851fd 100644
--- a/source/opt/licm_pass.cpp
+++ b/source/opt/licm_pass.cpp
@@ -124,7 +124,14 @@
if (!pre_header_bb) {
return false;
}
- inst->InsertBefore(std::move(&(*pre_header_bb->tail())));
+ Instruction* insertion_point = &*pre_header_bb->tail();
+ Instruction* previous_node = insertion_point->PreviousNode();
+ if (previous_node && (previous_node->opcode() == SpvOpLoopMerge ||
+ previous_node->opcode() == SpvOpSelectionMerge)) {
+ insertion_point = previous_node;
+ }
+
+ inst->InsertBefore(insertion_point);
context()->set_instr_block(inst, pre_header_bb);
return true;
}
diff --git a/source/opt/licm_pass.h b/source/opt/licm_pass.h
index a94ae11..597fe92 100644
--- a/source/opt/licm_pass.h
+++ b/source/opt/licm_pass.h
@@ -61,7 +61,7 @@
// Returns true if |bb| is immediately contained in |loop|
bool IsImmediatelyContainedInLoop(Loop* loop, Function* f, BasicBlock* bb);
- // Move the instruction to the given BasicBlock
+ // Move the instruction to the preheader of |loop|.
// This method will update the instruction to block mapping for the context
bool HoistInstruction(Loop* loop, Instruction* inst);
};
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 5b976a1..2258e61 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -264,6 +264,12 @@
}
bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
+ // This capability can now exist without the extension, so we have to check
+ // for the capability. This pass is only looking at function scope symbols,
+ // so we do not care if there are variable pointers on storage buffers.
+ if (context()->get_feature_mgr()->HasCapability(
+ SpvCapabilityVariablePointers))
+ return false;
// If any extension not in whitelist, return false
for (auto& ei : get_module()->extensions()) {
const char* extName =
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index 9d06890..1c7e3d5 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -44,7 +44,8 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse;
+ return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
using ProcessFunction = std::function<bool(Function*)>;
diff --git a/source/opt/local_redundancy_elimination.h b/source/opt/local_redundancy_elimination.h
index 9f55c8b..770457a 100644
--- a/source/opt/local_redundancy_elimination.h
+++ b/source/opt/local_redundancy_elimination.h
@@ -41,7 +41,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
protected:
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 9330ab7..3e2eb7e 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -187,6 +187,7 @@
// Assumes relaxed logical addressing only (see instruction.h).
if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
return Status::SuccessWithoutChange;
+
// Do not process if module contains OpGroupDecorate. Additional
// support required in KillNamesAndDecorates().
// TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -233,8 +234,7 @@
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
- // SPV_KHR_variable_pointers
- // Currently do not support extended pointer expressions
+ "SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_block_elim_pass.h b/source/opt/local_single_block_elim_pass.h
index 3dead98..0fe7732 100644
--- a/source/opt/local_single_block_elim_pass.h
+++ b/source/opt/local_single_block_elim_pass.h
@@ -42,7 +42,9 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 6c09dec..f47777e 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -98,8 +98,7 @@
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
- // SPV_KHR_variable_pointers
- // Currently do not support extended pointer expressions
+ "SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_store_elim_pass.h b/source/opt/local_single_store_elim_pass.h
index d3d64b8..4cf8bbb 100644
--- a/source/opt/local_single_store_elim_pass.h
+++ b/source/opt/local_single_store_elim_pass.h
@@ -45,7 +45,9 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
index 8209aa4..df327a6 100644
--- a/source/opt/local_ssa_elim_pass.cpp
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -83,8 +83,7 @@
"SPV_NV_geometry_shader_passthrough",
"SPV_AMD_texture_gather_bias_lod",
"SPV_KHR_storage_buffer_storage_class",
- // SPV_KHR_variable_pointers
- // Currently do not support extended pointer expressions
+ "SPV_KHR_variable_pointers",
"SPV_AMD_gpu_shader_int16",
"SPV_KHR_post_depth_coverage",
"SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_ssa_elim_pass.h b/source/opt/local_ssa_elim_pass.h
index 63d3c33..de80d5a 100644
--- a/source/opt/local_ssa_elim_pass.h
+++ b/source/opt/local_ssa_elim_pass.h
@@ -48,7 +48,9 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index 5aff34c..11f7e9c 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -937,22 +937,23 @@
for (Loop* loop : loops_to_remove_) {
loops_.erase(std::find(loops_.begin(), loops_.end(), loop));
+ delete loop;
}
for (auto& pair : loops_to_add_) {
Loop* parent = pair.first;
- Loop* loop = pair.second;
+ std::unique_ptr<Loop> loop = std::move(pair.second);
if (parent) {
loop->SetParent(nullptr);
- parent->AddNestedLoop(loop);
+ parent->AddNestedLoop(loop.get());
for (uint32_t block_id : loop->GetBlocks()) {
parent->AddBasicBlock(block_id);
}
}
- loops_.emplace_back(loop);
+ loops_.emplace_back(loop.release());
}
loops_to_add_.clear();
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 38f017b..6e2b828 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -499,8 +499,8 @@
// Mark the loop |loop_to_add| as needing to be added when the user calls
// PostModificationCleanup. |parent| may be null.
- inline void AddLoop(Loop* loop_to_add, Loop* parent) {
- loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add));
+ inline void AddLoop(std::unique_ptr<Loop>&& loop_to_add, Loop* parent) {
+ loops_to_add_.emplace_back(std::make_pair(parent, std::move(loop_to_add)));
}
// Checks all loops in |this| and will create pre-headers for all loops
@@ -537,7 +537,9 @@
// TODO(dneto): This should be a vector of unique_ptr. But VisualStudio 2013
// is unable to compile it.
using LoopContainerType = std::vector<Loop*>;
- using LoopsToAddContainerType = std::vector<std::pair<Loop*, Loop*>>;
+
+ using LoopsToAddContainerType =
+ std::vector<std::pair<Loop*, std::unique_ptr<Loop>>>;
// Creates loop descriptors for the function |f|.
void PopulateList(IRContext* context, const Function* f);
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 0d49d88..10fac04 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -394,12 +394,12 @@
// This is a naked new due to the VS2013 requirement of not having unique
// pointers in vectors, as it will be inserted into a vector with
// loop_descriptor.AddLoop.
- Loop* new_loop = new Loop(*loop);
+ std::unique_ptr<Loop> new_loop = MakeUnique<Loop>(*loop);
// Clear the basic blocks of the new loop.
new_loop->ClearBlocks();
- DuplicateLoop(loop, new_loop);
+ DuplicateLoop(loop, new_loop.get());
// Add the blocks to the function.
AddBlocksToFunction(loop->GetMergeBlock());
@@ -414,10 +414,10 @@
loop_induction_variable_ = state_.new_phi;
// Unroll the new loop by the factor with the usual -1 to account for the
// existing block iteration.
- Unroll(new_loop, factor);
+ Unroll(new_loop.get(), factor);
- LinkLastPhisToStart(new_loop);
- AddBlocksToLoop(new_loop);
+ LinkLastPhisToStart(new_loop.get());
+ AddBlocksToLoop(new_loop.get());
// Add the new merge block to the back of the list of blocks to be added. It
// needs to be the last block added to maintain dominator order in the binary.
@@ -507,7 +507,7 @@
LoopDescriptor& loop_descriptor = *context_->GetLoopDescriptor(&function_);
- loop_descriptor.AddLoop(new_loop, loop->GetParent());
+ loop_descriptor.AddLoop(std::move(new_loop), loop->GetParent());
RemoveDeadInstructions();
}
diff --git a/source/opt/loop_unroller.h b/source/opt/loop_unroller.h
index 41a91a2..71e7cca 100644
--- a/source/opt/loop_unroller.h
+++ b/source/opt/loop_unroller.h
@@ -34,7 +34,8 @@
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 7e374d9..502fc6b 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -39,8 +39,6 @@
namespace {
static const uint32_t kTypePointerStorageClassInIdx = 0;
-static const uint32_t kBranchCondTrueLabIdInIdx = 1;
-static const uint32_t kBranchCondFalseLabIdInIdx = 2;
} // anonymous namespace
@@ -74,9 +72,13 @@
for (uint32_t bb_id : loop_->GetBlocks()) {
BasicBlock* bb = cfg.block(bb_id);
+ if (loop_->GetLatchBlock() == bb) {
+ continue;
+ }
+
if (bb->terminator()->IsBranch() &&
bb->terminator()->opcode() != SpvOpBranch) {
- if (IsConditionLoopInvariant(bb->terminator())) {
+ if (IsConditionNonConstantLoopInvariant(bb->terminator())) {
switch_block_ = bb;
break;
}
@@ -110,6 +112,35 @@
return bb;
}
+ Instruction* GetValueForDefaultPathForSwitch(Instruction* switch_inst) {
+ assert(switch_inst->opcode() == SpvOpSwitch &&
+ "The given instructoin must be an OpSwitch.");
+
+ // Find a value that can be used to select the default path.
+ // If none are possible, then it will just use 0. The value does not matter
+ // because this path will never be taken becaues the new switch outside of
+ // the loop cannot select this path either.
+ std::vector<uint32_t> existing_values;
+ for (uint32_t i = 2; i < switch_inst->NumInOperands(); i += 2) {
+ existing_values.push_back(switch_inst->GetSingleWordInOperand(i));
+ }
+ std::sort(existing_values.begin(), existing_values.end());
+ uint32_t value_for_default_path = 0;
+ if (existing_values.size() < std::numeric_limits<uint32_t>::max()) {
+ for (value_for_default_path = 0;
+ value_for_default_path < existing_values.size();
+ value_for_default_path++) {
+ if (existing_values[value_for_default_path] != value_for_default_path) {
+ break;
+ }
+ }
+ }
+ InstructionBuilder builder(
+ context_, static_cast<Instruction*>(nullptr),
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ return builder.GetUintConstant(value_for_default_path);
+ }
+
// Unswitches |loop_|.
void PerformUnswitch() {
assert(CanUnswitchLoop() &&
@@ -154,6 +185,7 @@
if_merge_block->ForEachPhiInst(
[loop_merge_block, &builder, this](Instruction* phi) {
Instruction* cloned = phi->Clone(context_);
+ cloned->SetResultId(TakeNextId());
builder.AddInstruction(std::unique_ptr<Instruction>(cloned));
phi->SetInOperand(0, {cloned->result_id()});
phi->SetInOperand(1, {loop_merge_block->id()});
@@ -177,7 +209,6 @@
ploop->AddBasicBlock(loop_merge_block);
loop_desc_.SetBasicBlockToLoop(loop_merge_block->id(), ploop);
}
-
// Update the dominator tree.
DominatorTreeNode* loop_merge_dtn =
dom_tree->GetOrInsertNode(loop_merge_block);
@@ -195,7 +226,7 @@
////////////////////////////////////////////////////////////////////////////
// Step 2: Build a new preheader for |loop_|, use the old one
- // for the constant branch.
+ // for the invariant branch.
////////////////////////////////////////////////////////////////////////////
BasicBlock* if_block = loop_->GetPreHeaderBlock();
@@ -273,7 +304,6 @@
std::vector<std::pair<Instruction*, BasicBlock*>> constant_branch;
// Special case for the original loop
Instruction* original_loop_constant_value;
- BasicBlock* original_loop_target;
if (iv_opcode == SpvOpBranchConditional) {
constant_branch.emplace_back(
cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {0})),
@@ -283,7 +313,9 @@
} else {
// We are looking to take the default branch, so we can't provide a
// specific value.
- original_loop_constant_value = nullptr;
+ original_loop_constant_value =
+ GetValueForDefaultPathForSwitch(iv_condition);
+
for (uint32_t i = 2; i < iv_condition->NumInOperands(); i += 2) {
constant_branch.emplace_back(
cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(
@@ -323,24 +355,7 @@
////////////////////////////////////
{
- std::unordered_set<uint32_t> dead_blocks;
- std::unordered_set<uint32_t> unreachable_merges;
- SimplifyLoop(
- make_range(
- UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
- clone_result.cloned_bb_.begin()),
- UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
- clone_result.cloned_bb_.end())),
- cloned_loop, condition, specialisation_value, &dead_blocks);
-
- // We tagged dead blocks, create the loop before we invalidate any basic
- // block.
- cloned_loop =
- CleanLoopNest(cloned_loop, dead_blocks, &unreachable_merges);
- CleanUpCFG(
- UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
- clone_result.cloned_bb_.begin()),
- dead_blocks, unreachable_merges);
+ SpecializeLoop(cloned_loop, condition, specialisation_value);
///////////////////////////////////////////////////////////
// Step 5: Connect convergent edges to the landing pads. //
@@ -349,27 +364,25 @@
for (uint32_t merge_bb_id : if_merging_blocks) {
BasicBlock* merge = context_->cfg()->block(merge_bb_id);
// We are in LCSSA so we only care about phi instructions.
- merge->ForEachPhiInst([is_from_original_loop, &dead_blocks,
- &clone_result](Instruction* phi) {
- uint32_t num_in_operands = phi->NumInOperands();
- for (uint32_t i = 0; i < num_in_operands; i += 2) {
- uint32_t pred = phi->GetSingleWordInOperand(i + 1);
- if (is_from_original_loop(pred)) {
- pred = clone_result.value_map_.at(pred);
- if (!dead_blocks.count(pred)) {
- uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
- // Not all the incoming value are coming from the loop.
- ValueMapTy::iterator new_value =
- clone_result.value_map_.find(incoming_value_id);
- if (new_value != clone_result.value_map_.end()) {
- incoming_value_id = new_value->second;
+ merge->ForEachPhiInst(
+ [is_from_original_loop, &clone_result](Instruction* phi) {
+ uint32_t num_in_operands = phi->NumInOperands();
+ for (uint32_t i = 0; i < num_in_operands; i += 2) {
+ uint32_t pred = phi->GetSingleWordInOperand(i + 1);
+ if (is_from_original_loop(pred)) {
+ pred = clone_result.value_map_.at(pred);
+ uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
+ // Not all the incoming values are coming from the loop.
+ ValueMapTy::iterator new_value =
+ clone_result.value_map_.find(incoming_value_id);
+ if (new_value != clone_result.value_map_.end()) {
+ incoming_value_id = new_value->second;
+ }
+ phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
+ phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
}
- phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
- phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
}
- }
- }
- });
+ });
}
}
function_->AddBasicBlocks(clone_result.cloned_bb_.begin(),
@@ -377,38 +390,9 @@
++FindBasicBlockPosition(if_block));
}
- // Same as above but specialize the existing loop
- {
- std::unordered_set<uint32_t> dead_blocks;
- std::unordered_set<uint32_t> unreachable_merges;
- SimplifyLoop(make_range(function_->begin(), function_->end()), loop_,
- condition, original_loop_constant_value, &dead_blocks);
-
- for (uint32_t merge_bb_id : if_merging_blocks) {
- BasicBlock* merge = context_->cfg()->block(merge_bb_id);
- // LCSSA, so we only care about phi instructions.
- // If we the phi is reduced to a single incoming branch, do not
- // propagate it to preserve LCSSA.
- PatchPhis(merge, dead_blocks, true);
- }
- if (if_merge_block) {
- bool has_live_pred = false;
- for (uint32_t pid : cfg.preds(if_merge_block->id())) {
- if (!dead_blocks.count(pid)) {
- has_live_pred = true;
- break;
- }
- }
- if (!has_live_pred) unreachable_merges.insert(if_merge_block->id());
- }
- original_loop_target = loop_->GetPreHeaderBlock();
- // We tagged dead blocks, prune the loop descriptor from any dead loops.
- // After this call, |loop_| can be nullptr (i.e. the unswitch killed this
- // loop).
- loop_ = CleanLoopNest(loop_, dead_blocks, &unreachable_merges);
-
- CleanUpCFG(function_->begin(), dead_blocks, unreachable_merges);
- }
+ // Specialize the existing loop.
+ SpecializeLoop(loop_, condition, original_loop_constant_value);
+ BasicBlock* original_loop_target = loop_->GetPreHeaderBlock();
/////////////////////////////////////
// Finally: connect the new loops. //
@@ -441,9 +425,6 @@
IRContext::Analysis::kAnalysisLoopAnalysis);
}
- // Returns true if the unswitch killed the original |loop_|.
- bool WasLoopKilled() const { return loop_ == nullptr; }
-
private:
using ValueMapTy = std::unordered_map<uint32_t, uint32_t>;
using BlockMapTy = std::unordered_map<uint32_t, BasicBlock*>;
@@ -465,96 +446,6 @@
return context_->TakeNextId();
}
- // Patches |bb|'s phi instruction by removing incoming value from unexisting
- // or tagged as dead branches.
- void PatchPhis(BasicBlock* bb,
- const std::unordered_set<uint32_t>& dead_blocks,
- bool preserve_phi) {
- CFG& cfg = *context_->cfg();
-
- std::vector<Instruction*> phi_to_kill;
- const std::vector<uint32_t>& bb_preds = cfg.preds(bb->id());
- auto is_branch_dead = [&bb_preds, &dead_blocks](uint32_t id) {
- return dead_blocks.count(id) ||
- std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end();
- };
- bb->ForEachPhiInst(
- [&phi_to_kill, &is_branch_dead, preserve_phi, this](Instruction* insn) {
- uint32_t i = 0;
- while (i < insn->NumInOperands()) {
- uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
- if (is_branch_dead(incoming_id)) {
- // Remove the incoming block id operand.
- insn->RemoveInOperand(i + 1);
- // Remove the definition id operand.
- insn->RemoveInOperand(i);
- continue;
- }
- i += 2;
- }
- // If there is only 1 remaining edge, propagate the value and
- // kill the instruction.
- if (insn->NumInOperands() == 2 && !preserve_phi) {
- phi_to_kill.push_back(insn);
- context_->ReplaceAllUsesWith(insn->result_id(),
- insn->GetSingleWordInOperand(0));
- }
- });
- for (Instruction* insn : phi_to_kill) {
- context_->KillInst(insn);
- }
- }
-
- // Removes any block that is tagged as dead, if the block is in
- // |unreachable_merges| then all block's instructions are replaced by a
- // OpUnreachable.
- void CleanUpCFG(UptrVectorIterator<BasicBlock> bb_it,
- const std::unordered_set<uint32_t>& dead_blocks,
- const std::unordered_set<uint32_t>& unreachable_merges) {
- CFG& cfg = *context_->cfg();
-
- while (bb_it != bb_it.End()) {
- BasicBlock& bb = *bb_it;
-
- if (unreachable_merges.count(bb.id())) {
- if (bb.begin() != bb.tail() ||
- bb.terminator()->opcode() != SpvOpUnreachable) {
- // Make unreachable, but leave the label.
- bb.KillAllInsts(false);
- InstructionBuilder(context_, &bb).AddUnreachable();
- cfg.RemoveNonExistingEdges(bb.id());
- }
- ++bb_it;
- } else if (dead_blocks.count(bb.id())) {
- cfg.ForgetBlock(&bb);
- // Kill this block.
- bb.KillAllInsts(true);
- bb_it = bb_it.Erase();
- } else {
- cfg.RemoveNonExistingEdges(bb.id());
- ++bb_it;
- }
- }
- }
-
- // Return true if |c_inst| is a Boolean constant and set |cond_val| with the
- // value that |c_inst|
- bool GetConstCondition(const Instruction* c_inst, bool* cond_val) {
- bool cond_is_const;
- switch (c_inst->opcode()) {
- case SpvOpConstantFalse: {
- *cond_val = false;
- cond_is_const = true;
- } break;
- case SpvOpConstantTrue: {
- *cond_val = true;
- cond_is_const = true;
- } break;
- default: { cond_is_const = false; } break;
- }
- return cond_is_const;
- }
-
// Simplifies |loop| assuming the instruction |to_version_insn| takes the
// value |cst_value|. |block_range| is an iterator range returning the loop
// basic blocks in a structured order (dominator first).
@@ -564,13 +455,9 @@
//
// Requirements:
// - |loop| must be in the LCSSA form;
- // - |cst_value| must be constant or null (to represent the default target
- // of an OpSwitch).
- void SimplifyLoop(IteratorRange<UptrVectorIterator<BasicBlock>> block_range,
- Loop* loop, Instruction* to_version_insn,
- Instruction* cst_value,
- std::unordered_set<uint32_t>* dead_blocks) {
- CFG& cfg = *context_->cfg();
+ // - |cst_value| must be constant.
+ void SpecializeLoop(Loop* loop, Instruction* to_version_insn,
+ Instruction* cst_value) {
analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
std::function<bool(uint32_t)> ignore_node;
@@ -595,192 +482,15 @@
for (auto use : use_list) {
Instruction* inst = use.first;
uint32_t operand_index = use.second;
- BasicBlock* bb = context_->get_instr_block(inst);
- // If it is not a branch, simply inject the value.
- if (!inst->IsBranch()) {
- // To also handle switch, cst_value can be nullptr: this case
- // means that we are looking to branch to the default target of
- // the switch. We don't actually know its value so we don't touch
- // it if it not a switch.
- if (cst_value) {
- inst->SetOperand(operand_index, {cst_value->result_id()});
- def_use_mgr->AnalyzeInstUse(inst);
- }
- }
-
- // The user is a branch, kill dead branches.
- uint32_t live_target = 0;
- std::unordered_set<uint32_t> dead_branches;
- switch (inst->opcode()) {
- case SpvOpBranchConditional: {
- assert(cst_value && "No constant value to specialize !");
- bool branch_cond = false;
- if (GetConstCondition(cst_value, &branch_cond)) {
- uint32_t true_label =
- inst->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx);
- uint32_t false_label =
- inst->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
- live_target = branch_cond ? true_label : false_label;
- uint32_t dead_target = !branch_cond ? true_label : false_label;
- cfg.RemoveEdge(bb->id(), dead_target);
- }
- break;
- }
- case SpvOpSwitch: {
- live_target = inst->GetSingleWordInOperand(1);
- if (cst_value) {
- if (!cst_value->IsConstant()) break;
- const Operand& cst = cst_value->GetInOperand(0);
- for (uint32_t i = 2; i < inst->NumInOperands(); i += 2) {
- const Operand& literal = inst->GetInOperand(i);
- if (literal == cst) {
- live_target = inst->GetSingleWordInOperand(i + 1);
- break;
- }
- }
- }
- for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
- uint32_t id = inst->GetSingleWordInOperand(i);
- if (id != live_target) {
- cfg.RemoveEdge(bb->id(), id);
- }
- }
- }
- default:
- break;
- }
- if (live_target != 0) {
- // Check for the presence of the merge block.
- if (Instruction* merge = bb->GetMergeInst()) context_->KillInst(merge);
- context_->KillInst(&*bb->tail());
- InstructionBuilder builder(context_, bb,
- IRContext::kAnalysisDefUse |
- IRContext::kAnalysisInstrToBlockMapping);
- builder.AddBranch(live_target);
- }
+ // To also handle switch, cst_value can be nullptr: this case
+ // means that we are looking to branch to the default target of
+ // the switch. We don't actually know its value so we don't touch
+ // it if it not a switch.
+ assert(cst_value && "We do not have a value to use.");
+ inst->SetOperand(operand_index, {cst_value->result_id()});
+ def_use_mgr->AnalyzeInstUse(inst);
}
-
- // Go through the loop basic block and tag all blocks that are obviously
- // dead.
- std::unordered_set<uint32_t> visited;
- for (BasicBlock& bb : block_range) {
- if (ignore_node(bb.id())) continue;
- visited.insert(bb.id());
-
- // Check if this block is dead, if so tag it as dead otherwise patch phi
- // instructions.
- bool has_live_pred = false;
- for (uint32_t pid : cfg.preds(bb.id())) {
- if (!dead_blocks->count(pid)) {
- has_live_pred = true;
- break;
- }
- }
- if (!has_live_pred) {
- dead_blocks->insert(bb.id());
- const BasicBlock& cbb = bb;
- // Patch the phis for any back-edge.
- cbb.ForEachSuccessorLabel(
- [dead_blocks, &visited, &cfg, this](uint32_t id) {
- if (!visited.count(id) || dead_blocks->count(id)) return;
- BasicBlock* succ = cfg.block(id);
- PatchPhis(succ, *dead_blocks, false);
- });
- continue;
- }
- // Update the phi instructions, some incoming branch have/will disappear.
- PatchPhis(&bb, *dead_blocks, /* preserve_phi = */ false);
- }
- }
-
- // Returns true if the header is not reachable or tagged as dead or if we
- // never loop back.
- bool IsLoopDead(BasicBlock* header, BasicBlock* latch,
- const std::unordered_set<uint32_t>& dead_blocks) {
- if (!header || dead_blocks.count(header->id())) return true;
- if (!latch || dead_blocks.count(latch->id())) return true;
- for (uint32_t pid : context_->cfg()->preds(header->id())) {
- if (!dead_blocks.count(pid)) {
- // Seems reachable.
- return false;
- }
- }
- return true;
- }
-
- // Cleans the loop nest under |loop| and reflect changes to the loop
- // descriptor. This will kill all descriptors that represent dead loops.
- // If |loop_| is killed, it will be set to nullptr.
- // Any merge blocks that become unreachable will be added to
- // |unreachable_merges|.
- // The function returns the pointer to |loop| or nullptr if the loop was
- // killed.
- Loop* CleanLoopNest(Loop* loop,
- const std::unordered_set<uint32_t>& dead_blocks,
- std::unordered_set<uint32_t>* unreachable_merges) {
- // This represent the pair of dead loop and nearest alive parent (nullptr if
- // no parent).
- std::unordered_map<Loop*, Loop*> dead_loops;
- auto get_parent = [&dead_loops](Loop* l) -> Loop* {
- std::unordered_map<Loop*, Loop*>::iterator it = dead_loops.find(l);
- if (it != dead_loops.end()) return it->second;
- return nullptr;
- };
-
- bool is_main_loop_dead =
- IsLoopDead(loop->GetHeaderBlock(), loop->GetLatchBlock(), dead_blocks);
- if (is_main_loop_dead) {
- if (Instruction* merge = loop->GetHeaderBlock()->GetLoopMergeInst()) {
- context_->KillInst(merge);
- }
- dead_loops[loop] = loop->GetParent();
- } else {
- dead_loops[loop] = loop;
- }
-
- // For each loop, check if we killed it. If we did, find a suitable parent
- // for its children.
- for (Loop& sub_loop :
- make_range(++TreeDFIterator<Loop>(loop), TreeDFIterator<Loop>())) {
- if (IsLoopDead(sub_loop.GetHeaderBlock(), sub_loop.GetLatchBlock(),
- dead_blocks)) {
- if (Instruction* merge =
- sub_loop.GetHeaderBlock()->GetLoopMergeInst()) {
- context_->KillInst(merge);
- }
- dead_loops[&sub_loop] = get_parent(&sub_loop);
- } else {
- // The loop is alive, check if its merge block is dead, if it is, tag it
- // as required.
- if (sub_loop.GetMergeBlock()) {
- uint32_t merge_id = sub_loop.GetMergeBlock()->id();
- if (dead_blocks.count(merge_id)) {
- unreachable_merges->insert(sub_loop.GetMergeBlock()->id());
- }
- }
- }
- }
- if (!is_main_loop_dead) dead_loops.erase(loop);
-
- // Remove dead blocks from live loops.
- for (uint32_t bb_id : dead_blocks) {
- Loop* l = loop_desc_[bb_id];
- if (l) {
- l->RemoveBasicBlock(bb_id);
- loop_desc_.ForgetBasicBlock(bb_id);
- }
- }
-
- std::for_each(
- dead_loops.begin(), dead_loops.end(),
- [&loop,
- this](std::unordered_map<Loop*, Loop*>::iterator::reference it) {
- if (it.first == loop) loop = nullptr;
- loop_desc_.RemoveLoop(it.first);
- });
-
- return loop;
}
// Returns true if |var| is dynamically uniform.
@@ -839,17 +549,25 @@
});
}
- // Returns true if |insn| is constant and dynamically uniform within the loop.
- bool IsConditionLoopInvariant(Instruction* insn) {
+ // Returns true if |insn| is not a constant, but is loop invariant and
+ // dynamically uniform.
+ bool IsConditionNonConstantLoopInvariant(Instruction* insn) {
assert(insn->IsBranch());
assert(insn->opcode() != SpvOpBranch);
analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
Instruction* condition = def_use_mgr->GetDef(insn->GetOperand(0).words[0]);
- return !loop_->IsInsideLoop(condition) &&
- IsDynamicallyUniform(
- condition, function_->entry().get(),
- context_->GetPostDominatorAnalysis(function_)->GetDomTree());
+ if (condition->IsConstant()) {
+ return false;
+ }
+
+ if (loop_->IsInsideLoop(condition)) {
+ return false;
+ }
+
+ return IsDynamicallyUniform(
+ condition, function_->entry().get(),
+ context_->GetPostDominatorAnalysis(function_)->GetDomTree());
}
};
@@ -883,7 +601,7 @@
processed_loop.insert(&loop);
LoopUnswitch unswitcher(context(), f, &loop, &loop_descriptor);
- while (!unswitcher.WasLoopKilled() && unswitcher.CanUnswitchLoop()) {
+ while (unswitcher.CanUnswitchLoop()) {
if (!loop.IsLCSSA()) {
LoopUtils(context(), &loop).MakeLoopClosedSSA();
}
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index a13540c..a12b2ca 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -197,6 +197,7 @@
tail_opcode == SpvOpUnreachable) {
assert(CurrentState().InLoop() && "Should be in the dummy loop.");
BranchToBlock(block, CurrentState().LoopMergeId());
+ return_blocks_.insert(block->id());
}
}
@@ -232,11 +233,19 @@
const auto& target_pred = cfg()->preds(target->id());
if (target_pred.size() == 1) {
MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
+ } else {
+ // If the loop contained a break and a return, OpPhi instructions may be
+ // required starting from the dominator of the loop merge.
+ DominatorAnalysis* dom_tree =
+ context()->GetDominatorAnalysis(target->GetParent());
+ auto idom = dom_tree->ImmediateDominator(target);
+ if (idom) {
+ MarkForNewPhiNodes(target, idom);
+ }
}
}
void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
- uint32_t predecessor,
Instruction& inst) {
DominatorAnalysis* dom_tree =
context()->GetDominatorAnalysis(merge_block->GetParent());
@@ -275,22 +284,22 @@
// There is at least one values that needs to be replaced.
// First create the OpPhi instruction.
- InstructionBuilder builder(context(), &*merge_block->begin(),
- IRContext::kAnalysisDefUse);
+ InstructionBuilder builder(
+ context(), &*merge_block->begin(),
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t undef_id = Type2Undef(inst.type_id());
std::vector<uint32_t> phi_operands;
- // Add the operands for the defining instructions.
- phi_operands.push_back(inst.result_id());
- phi_operands.push_back(predecessor);
-
- // Add undef from all other blocks.
+ // Add the OpPhi operands. If the predecessor is a return block use undef,
+ // otherwise use |inst|'s id.
std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
for (uint32_t pred_id : preds) {
- if (pred_id != predecessor) {
+ if (return_blocks_.count(pred_id)) {
phi_operands.push_back(undef_id);
- phi_operands.push_back(pred_id);
+ } else {
+ phi_operands.push_back(inst.result_id());
}
+ phi_operands.push_back(pred_id);
}
Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
@@ -342,25 +351,28 @@
while (block != nullptr && block != final_return_block_) {
if (!predicated->insert(block).second) break;
// Skip structured subgraphs.
- BasicBlock* next = nullptr;
assert(state->InLoop() && "Should be in the dummy loop at the very least.");
- next = context()->get_instr_block(state->LoopMergeId());
- while (state->LoopMergeId() == next->id()) {
+ Instruction* current_loop_merge_inst = state->LoopMergeInst();
+ uint32_t merge_block_id =
+ current_loop_merge_inst->GetSingleWordInOperand(0);
+ while (state->LoopMergeId() == merge_block_id) {
state++;
}
- if (!BreakFromConstruct(block, next, predicated, order)) {
+ if (!BreakFromConstruct(block, predicated, order,
+ current_loop_merge_inst)) {
return false;
}
- block = next;
+ block = context()->get_instr_block(merge_block_id);
}
return true;
}
bool MergeReturnPass::BreakFromConstruct(
- BasicBlock* block, BasicBlock* merge_block,
- std::unordered_set<BasicBlock*>* predicated,
- std::list<BasicBlock*>* order) {
- // Make sure the cfg is build here. If we don't then it becomes very hard
+ BasicBlock* block, std::unordered_set<BasicBlock*>* predicated,
+ std::list<BasicBlock*>* order, Instruction* loop_merge_inst) {
+ assert(loop_merge_inst->opcode() == SpvOpLoopMerge &&
+ "loop_merge_inst must be a loop merge instruction.");
+ // Make sure the CFG is build here. If we don't then it becomes very hard
// to know which new blocks need to be updated.
context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG);
@@ -380,6 +392,9 @@
return false;
}
}
+
+ uint32_t merge_block_id = loop_merge_inst->GetSingleWordInOperand(0);
+ BasicBlock* merge_block = context()->get_instr_block(merge_block_id);
if (merge_block->GetLoopMergeInst()) {
cfg()->SplitLoopHeader(merge_block);
}
@@ -393,8 +408,21 @@
// Forget about the edges leaving block. They will be removed.
cfg()->RemoveSuccessorEdges(block);
- BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
+ auto old_body_id = TakeNextId();
+ BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter);
predicated->insert(old_body);
+ // If a return block is being split, mark the new body block also as a return
+ // block.
+ if (return_blocks_.count(block->id())) {
+ return_blocks_.insert(old_body_id);
+ }
+
+ // If |block| was a continue target for a loop |old_body| is now the correct
+ // continue target.
+ if (loop_merge_inst->GetSingleWordInOperand(1) == block->id()) {
+ loop_merge_inst->SetInOperand(1, {old_body->id()});
+ context()->UpdateDefUse(loop_merge_inst);
+ }
// Update |order| so old_block will be traversed.
InsertAfterElement(block, old_body, order);
@@ -403,6 +431,7 @@
// 1. Load of the return status flag
// 2. Branch to |merge_block| (true) or old body (false)
// 3. Update OpPhi instructions in |merge_block|.
+ // 4. Update the CFG.
//
// Sine we are branching to the merge block of the current construct, there is
// no need for an OpSelectionMerge.
@@ -421,10 +450,6 @@
builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(),
old_body->id());
- // Update the cfg
- cfg()->AddEdges(block);
- cfg()->RegisterBlock(old_body);
-
// 3. Update OpPhi instructions in |merge_block|.
BasicBlock* merge_original_pred = MarkedSinglePred(merge_block);
if (merge_original_pred == nullptr) {
@@ -433,6 +458,12 @@
MarkForNewPhiNodes(merge_block, old_body);
}
+ // 4. Update the CFG. We do this after updating the OpPhi instructions
+ // because |UpdatePhiNodes| assumes the edge from |block| has not been added
+ // to the CFG yet.
+ cfg()->AddEdges(block);
+ cfg()->RegisterBlock(old_body);
+
assert(old_body->begin() != old_body->end());
assert(block->begin() != block->end());
return true;
@@ -643,7 +674,7 @@
BasicBlock* current_bb = pred;
while (current_bb != nullptr && current_bb->id() != header_id) {
for (Instruction& inst : *current_bb) {
- CreatePhiNodesForInst(bb, pred->id(), inst);
+ CreatePhiNodesForInst(bb, inst);
}
current_bb = dom_tree->ImmediateDominator(current_bb);
}
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index 264e8b7..63094b7 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -104,7 +104,7 @@
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
- return IRContext::kAnalysisNone;
+ return IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
@@ -219,16 +219,18 @@
std::list<BasicBlock*>* order);
// Add a conditional branch at the start of |block| that either jumps to
- // |merge_block| or the original code in |block| depending on the value in
- // |return_flag_|.
+ // the merge block of |loop_merge_inst| or the original code in |block|
+ // depending on the value in |return_flag_|. The continue target in
+ // |loop_merge_inst| will be updated if needed.
//
// If new blocks that are created will be added to |order|. This way a call
// can traverse these new block in structured order.
//
// Returns true if successful.
- bool BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
+ bool BreakFromConstruct(BasicBlock* block,
std::unordered_set<BasicBlock*>* predicated,
- std::list<BasicBlock*>* order);
+ std::list<BasicBlock*>* order,
+ Instruction* loop_merge_inst);
// Add an |OpReturn| or |OpReturnValue| to the end of |block|. If an
// |OpReturnValue| is needed, the return value is loaded from |return_value_|.
@@ -238,12 +240,11 @@
// return block at the end of the pass.
void CreateReturnBlock();
- // Creates a Phi node in |merge_block| for the result of |inst| coming from
- // |predecessor|. Any uses of the result of |inst| that are no longer
+ // Creates a Phi node in |merge_block| for the result of |inst|.
+ // Any uses of the result of |inst| that are no longer
// dominated by |inst|, are replaced with the result of the new |OpPhi|
// instruction.
- void CreatePhiNodesForInst(BasicBlock* merge_block, uint32_t predecessor,
- Instruction& inst);
+ void CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst);
// Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
// that are needed to make the code correct. It is assumed that at this point
@@ -275,6 +276,8 @@
// new edge from |new_source|. The value for that edge will be an Undef. If
// |target| only had a single predecessor, then it is marked as needing new
// phi nodes. See |MarkForNewPhiNodes|.
+ //
+ // The CFG must not include the edge from |new_source| to |target| yet.
void UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target);
StructuredControlState& CurrentState() { return state_.back(); }
@@ -327,6 +330,11 @@
// values that will need a phi on the new edges.
std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
bool HasNontrivialUnreachableBlocks(Function* function);
+
+ // Contains all return blocks that are merged. This is set is populated while
+ // processing structured blocks and used to properly construct OpPhi
+ // instructions.
+ std::unordered_set<uint32_t> return_blocks_;
};
} // namespace opt
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 856ede7..34ddedc 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,8 +56,8 @@
struct Optimizer::Impl {
explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
- spv_target_env target_env; // Target environment.
- opt::PassManager pass_manager; // Internal implementation pass manager.
+ spv_target_env target_env; // Target environment.
+ opt::PassManager pass_manager; // Internal implementation pass manager.
};
Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@@ -115,6 +115,10 @@
// Make private variable function scope
.RegisterPass(CreateEliminateDeadFunctionsPass())
.RegisterPass(CreatePrivateToLocalPass())
+ // Fix up the storage classes that DXC may have purposely generated
+ // incorrectly. All functions are inlined, and a lot of dead code has
+ // been removed.
+ .RegisterPass(CreateFixStorageClassPass())
// Propagate the value stored to the loads in very simple cases.
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
.RegisterPass(CreateLocalSingleStoreElimPass())
@@ -216,6 +220,21 @@
.RegisterPass(CreateAggressiveDCEPass());
}
+Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
+ return RegisterPass(CreateStripDebugInfoPass())
+ .RegisterPass(CreateStripAtomicCounterMemoryPass())
+ .RegisterPass(CreateGenerateWebGPUInitializersPass())
+ .RegisterPass(CreateLegalizeVectorShufflePass())
+ .RegisterPass(CreateEliminateDeadConstantPass())
+ .RegisterPass(CreateFlattenDecorationPass())
+ .RegisterPass(CreateAggressiveDCEPass())
+ .RegisterPass(CreateDeadBranchElimPass());
+}
+
+Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() {
+ return RegisterPass(CreateDecomposeInitializedVariablesPass());
+}
+
bool Optimizer::RegisterPassesFromFlags(const std::vector<std::string>& flags) {
for (const auto& flag : flags) {
if (!RegisterPassFromFlag(flag)) {
@@ -258,7 +277,9 @@
//
// Both Pass::name() and Pass::desc() should be static class members so they
// can be invoked without creating a pass instance.
- if (pass_name == "strip-debug") {
+ if (pass_name == "strip-atomic-counter-memory") {
+ RegisterPass(CreateStripAtomicCounterMemoryPass());
+ } else if (pass_name == "strip-debug") {
RegisterPass(CreateStripDebugInfoPass());
} else if (pass_name == "strip-reflect") {
RegisterPass(CreateStripReflectInfoPass());
@@ -324,6 +345,8 @@
RegisterPass(CreateDeadInsertElimPass());
} else if (pass_name == "eliminate-dead-variables") {
RegisterPass(CreateDeadVariableEliminationPass());
+ } else if (pass_name == "eliminate-dead-members") {
+ RegisterPass(CreateEliminateDeadMembersPass());
} else if (pass_name == "fold-spec-const-op-composite") {
RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
} else if (pass_name == "loop-unswitch") {
@@ -373,7 +396,7 @@
} else if (pass_name == "replace-invalid-opcode") {
RegisterPass(CreateReplaceInvalidOpcodePass());
} else if (pass_name == "inst-bindless-check") {
- RegisterPass(CreateInstBindlessCheckPass(7, 23));
+ RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true));
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
@@ -434,12 +457,20 @@
}
} else if (pass_name == "ccp") {
RegisterPass(CreateCCPPass());
+ } else if (pass_name == "code-sink") {
+ RegisterPass(CreateCodeSinkingPass());
+ } else if (pass_name == "fix-storage-class") {
+ RegisterPass(CreateFixStorageClassPass());
} else if (pass_name == "O") {
RegisterPerformancePasses();
} else if (pass_name == "Os") {
RegisterSizePasses();
} else if (pass_name == "legalize-hlsl") {
RegisterLegalizationPasses();
+ } else if (pass_name == "generate-webgpu-initializers") {
+ RegisterPass(CreateGenerateWebGPUInitializersPass());
+ } else if (pass_name == "legalize-vector-shuffle") {
+ RegisterPass(CreateLegalizeVectorShufflePass());
} else {
Errorf(consumer(), nullptr, {},
"Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -491,11 +522,25 @@
context->set_max_id_bound(opt_options->max_id_bound_);
+ impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
+ impl_->pass_manager.SetTargetEnv(impl_->target_env);
auto status = impl_->pass_manager.Run(context.get());
- if (status == opt::Pass::Status::SuccessWithChange ||
- (status == opt::Pass::Status::SuccessWithoutChange &&
- (optimized_binary->data() != original_binary ||
- optimized_binary->size() != original_binary_size))) {
+
+ bool binary_changed = false;
+ if (status == opt::Pass::Status::SuccessWithChange) {
+ binary_changed = true;
+ } else if (status == opt::Pass::Status::SuccessWithoutChange) {
+ if (optimized_binary->size() != original_binary_size ||
+ (memcmp(optimized_binary->data(), original_binary,
+ original_binary_size) != 0)) {
+ binary_changed = true;
+ Log(consumer(), SPV_MSG_WARNING, nullptr, {},
+ "Binary unexpectedly changed despite optimizer saying there was no "
+ "change");
+ }
+ }
+
+ if (binary_changed) {
optimized_binary->clear();
context->module()->ToBinary(optimized_binary, /* skip_nop = */ true);
}
@@ -513,10 +558,20 @@
return *this;
}
+Optimizer& Optimizer::SetValidateAfterAll(bool validate) {
+ impl_->pass_manager.SetValidateAfterAll(validate);
+ return *this;
+}
+
Optimizer::PassToken CreateNullPass() {
return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
}
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::StripAtomicCounterMemoryPass>());
+}
+
Optimizer::PassToken CreateStripDebugInfoPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::StripDebugInfoPass>());
@@ -532,6 +587,11 @@
MakeUnique<opt::EliminateDeadFunctionsPass>());
}
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
const std::unordered_map<uint32_t, std::string>& id_value_map) {
return MakeUnique<Optimizer::PassToken::Impl>(
@@ -780,9 +840,37 @@
}
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
- uint32_t shader_id) {
+ uint32_t shader_id,
+ bool input_length_enable,
+ bool input_init_enable) {
return MakeUnique<Optimizer::PassToken::Impl>(
- MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+ MakeUnique<opt::InstBindlessCheckPass>(
+ desc_set, shader_id, input_length_enable, input_init_enable));
+}
+
+Optimizer::PassToken CreateCodeSinkingPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::CodeSinkingPass>());
+}
+
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::GenerateWebGPUInitializersPass>());
+}
+
+Optimizer::PassToken CreateFixStorageClassPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::FixStorageClass>());
+}
+
+Optimizer::PassToken CreateLegalizeVectorShufflePass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::LegalizeVectorShufflePass>());
+}
+
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::DecomposeInitializedVariablesPass>());
}
} // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index edcd245..80075db 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -16,6 +16,7 @@
#include "source/opt/pass.h"
+#include "source/opt/ir_builder.h"
#include "source/opt/iterator.h"
namespace spvtools {
@@ -52,5 +53,72 @@
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
+uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
+ Instruction* insertion_position) {
+ analysis::TypeManager* type_mgr = context()->get_type_mgr();
+ analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+ uint32_t original_type_id = object_inst->type_id();
+ if (original_type_id == new_type_id) {
+ return object_inst->result_id();
+ }
+
+ InstructionBuilder ir_builder(
+ context(), insertion_position,
+ IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
+
+ analysis::Type* original_type = type_mgr->GetType(original_type_id);
+ analysis::Type* new_type = type_mgr->GetType(new_type_id);
+
+ if (const analysis::Array* original_array_type = original_type->AsArray()) {
+ uint32_t original_element_type_id =
+ type_mgr->GetId(original_array_type->element_type());
+
+ analysis::Array* new_array_type = new_type->AsArray();
+ assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
+ uint32_t new_element_type_id =
+ type_mgr->GetId(new_array_type->element_type());
+
+ std::vector<uint32_t> element_ids;
+ const analysis::Constant* length_const =
+ const_mgr->FindDeclaredConstant(original_array_type->LengthId());
+ assert(length_const->AsIntConstant());
+ uint32_t array_length = length_const->AsIntConstant()->GetU32();
+ for (uint32_t i = 0; i < array_length; i++) {
+ Instruction* extract = ir_builder.AddCompositeExtract(
+ original_element_type_id, object_inst->result_id(), {i});
+ element_ids.push_back(
+ GenerateCopy(extract, new_element_type_id, insertion_position));
+ }
+
+ return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+ ->result_id();
+ } else if (const analysis::Struct* original_struct_type =
+ original_type->AsStruct()) {
+ analysis::Struct* new_struct_type = new_type->AsStruct();
+
+ const std::vector<const analysis::Type*>& original_types =
+ original_struct_type->element_types();
+ const std::vector<const analysis::Type*>& new_types =
+ new_struct_type->element_types();
+ std::vector<uint32_t> element_ids;
+ for (uint32_t i = 0; i < original_types.size(); i++) {
+ Instruction* extract = ir_builder.AddCompositeExtract(
+ type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
+ element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
+ insertion_position));
+ }
+ return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+ ->result_id();
+ } else {
+ // If we do not have an aggregate type, then we have a problem. Either we
+ // found multiple instances of the same type, or we are copying to an
+ // incompatible type. Either way the code is illegal.
+ assert(false &&
+ "Don't know how to copy this type. Code is likely illegal.");
+ }
+ return 0;
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/pass.h b/source/opt/pass.h
index c95f502..0667c3d 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -125,6 +125,12 @@
// TODO(1841): Handle id overflow.
uint32_t TakeNextId() { return context_->TakeNextId(); }
+ // Returns the id whose value is the same as |object_to_copy| except its type
+ // is |new_type_id|. Any instructions needed to generate this value will be
+ // inserted before |insertion_position|.
+ uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
+ Instruction* insertion_position);
+
private:
MessageConsumer consumer_; // Message consumer.
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index fa1e1d8..be53d34 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -51,6 +51,20 @@
if (one_status == Pass::Status::Failure) return one_status;
if (one_status == Pass::Status::SuccessWithChange) status = one_status;
+ if (validate_after_all_) {
+ spvtools::SpirvTools tools(target_env_);
+ tools.SetMessageConsumer(consumer());
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, true);
+ if (!tools.Validate(binary.data(), binary.size(), val_options_)) {
+ std::string msg = "Validation failed after pass ";
+ msg += pass->name();
+ spv_position_t null_pos{0, 0, 0};
+ consumer()(SPV_MSG_INTERNAL_ERROR, "", null_pos, msg.c_str());
+ return Pass::Status::Failure;
+ }
+ }
+
// Reset the pass to free any memory used by the pass.
pass.reset(nullptr);
}
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index ed88aa1..9686ddd 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -43,7 +43,10 @@
PassManager()
: consumer_(nullptr),
print_all_stream_(nullptr),
- time_report_stream_(nullptr) {}
+ time_report_stream_(nullptr),
+ target_env_(SPV_ENV_UNIVERSAL_1_2),
+ val_options_(nullptr),
+ validate_after_all_(false) {}
// Sets the message consumer to the given |consumer|.
void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
@@ -89,6 +92,24 @@
return *this;
}
+ // Sets the target environment for validation.
+ PassManager& SetTargetEnv(spv_target_env env) {
+ target_env_ = env;
+ return *this;
+ }
+
+ // Sets the validation options.
+ PassManager& SetValidatorOptions(spv_validator_options options) {
+ val_options_ = options;
+ return *this;
+ }
+
+ // Sets the option to validate after each pass.
+ PassManager& SetValidateAfterAll(bool validate) {
+ validate_after_all_ = validate;
+ return *this;
+ }
+
private:
// Consumer for messages.
MessageConsumer consumer_;
@@ -100,6 +121,12 @@
// The output stream to write the resource utilization of each pass. If this
// is null, no output is generated.
std::ostream* time_report_stream_;
+ // The target environment.
+ spv_target_env target_env_;
+ // The validator options (used when validating each pass).
+ spv_validator_options val_options_;
+ // Controls whether validation occurs after every pass.
+ bool validate_after_all_;
};
inline void PassManager::AddPass(std::unique_ptr<Pass> pass) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index b9f30ae..06464b0 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -21,6 +21,7 @@
#include "source/opt/block_merge_pass.h"
#include "source/opt/ccp_pass.h"
#include "source/opt/cfg_cleanup_pass.h"
+#include "source/opt/code_sink.h"
#include "source/opt/combine_access_chains.h"
#include "source/opt/common_uniform_elim_pass.h"
#include "source/opt/compact_ids_pass.h"
@@ -28,15 +29,20 @@
#include "source/opt/dead_branch_elim_pass.h"
#include "source/opt/dead_insert_elim_pass.h"
#include "source/opt/dead_variable_elimination.h"
+#include "source/opt/decompose_initialized_variables_pass.h"
#include "source/opt/eliminate_dead_constant_pass.h"
#include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_members_pass.h"
+#include "source/opt/fix_storage_class.h"
#include "source/opt/flatten_decoration_pass.h"
#include "source/opt/fold_spec_constant_op_and_composite_pass.h"
#include "source/opt/freeze_spec_constant_value_pass.h"
+#include "source/opt/generate_webgpu_initializers_pass.h"
#include "source/opt/if_conversion.h"
#include "source/opt/inline_exhaustive_pass.h"
#include "source/opt/inline_opaque_pass.h"
#include "source/opt/inst_bindless_check_pass.h"
+#include "source/opt/legalize_vector_shuffle_pass.h"
#include "source/opt/licm_pass.h"
#include "source/opt/local_access_chain_convert_pass.h"
#include "source/opt/local_redundancy_elimination.h"
@@ -61,6 +67,7 @@
#include "source/opt/simplification_pass.h"
#include "source/opt/ssa_rewrite_pass.h"
#include "source/opt/strength_reduction_pass.h"
+#include "source/opt/strip_atomic_counter_memory_pass.h"
#include "source/opt/strip_debug_info_pass.h"
#include "source/opt/strip_reflect_info_pass.h"
#include "source/opt/unify_const_pass.h"
diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp
index 02909a7..d41d8f2 100644
--- a/source/opt/private_to_local_pass.cpp
+++ b/source/opt/private_to_local_pass.cpp
@@ -19,6 +19,7 @@
#include <vector>
#include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
@@ -38,6 +39,7 @@
return Status::SuccessWithoutChange;
std::vector<std::pair<Instruction*, Function*>> variables_to_move;
+ std::unordered_set<uint32_t> localized_variables;
for (auto& inst : context()->types_values()) {
if (inst.opcode() != SpvOpVariable) {
continue;
@@ -57,6 +59,27 @@
modified = !variables_to_move.empty();
for (auto p : variables_to_move) {
MoveVariable(p.first, p.second);
+ localized_variables.insert(p.first->result_id());
+ }
+
+ if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+ // In SPIR-V 1.4 and later entry points must list private storage class
+ // variables that are statically used by the entry point. Go through the
+ // entry points and remove any references to variables that were localized.
+ for (auto& entry : get_module()->entry_points()) {
+ std::vector<Operand> new_operands;
+ for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+ // Execution model, function id and name are always kept.
+ if (i < 3 ||
+ !localized_variables.count(entry.GetSingleWordInOperand(i))) {
+ new_operands.push_back(entry.GetInOperand(i));
+ }
+ }
+ if (new_operands.size() != entry.NumInOperands()) {
+ entry.SetInOperands(std::move(new_operands));
+ context()->AnalyzeUses(&entry);
+ }
+ }
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
@@ -165,6 +188,7 @@
UpdateUses(inst->result_id());
break;
case SpvOpName:
+ case SpvOpEntryPoint: // entry points will be updated separately.
break;
default:
assert(spvOpcodeIsDecoration(inst->opcode()) &&
diff --git a/source/opt/private_to_local_pass.h b/source/opt/private_to_local_pass.h
index f706e6e..4678530 100644
--- a/source/opt/private_to_local_pass.h
+++ b/source/opt/private_to_local_pass.h
@@ -35,7 +35,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/process_lines_pass.h b/source/opt/process_lines_pass.h
index 91b2136..c988bfd 100644
--- a/source/opt/process_lines_pass.h
+++ b/source/opt/process_lines_pass.h
@@ -51,7 +51,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index b692c6b..7b5a015 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -139,7 +139,8 @@
all_elements_used =
!def_use_mgr->WhileEachUser(op_inst, [&elements_used](Instruction* use) {
- if (use->opcode() != SpvOpCompositeExtract) {
+ if (use->opcode() != SpvOpCompositeExtract ||
+ use->NumInOperands() == 1) {
return false;
}
elements_used.insert(use->GetSingleWordInOperand(1));
diff --git a/source/opt/reduce_load_size.h b/source/opt/reduce_load_size.h
index 724a430..ccac49b 100644
--- a/source/opt/reduce_load_size.h
+++ b/source/opt/reduce_load_size.h
@@ -36,7 +36,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 79d90bd..8106442 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -45,7 +45,8 @@
inline bool IsTypeInst(SpvOp opcode) {
return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
- opcode == SpvOpTypeAccelerationStructureNV;
+ opcode == SpvOpTypeAccelerationStructureNV ||
+ opcode == SpvOpTypeCooperativeMatrixNV;
}
inline bool IsConstantInst(SpvOp opcode) {
return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 66a97bd..bfa8598 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -225,12 +225,12 @@
// indexes) or a direct use of the replacement variable.
uint32_t indexId = chain->GetSingleWordInOperand(1u);
const Instruction* index = get_def_use_mgr()->GetDef(indexId);
- size_t indexValue = GetConstantInteger(index);
+ uint64_t indexValue = GetConstantInteger(index);
if (indexValue > replacements.size()) {
// Out of bounds access, this is illegal IR.
return false;
} else {
- const Instruction* var = replacements[indexValue];
+ const Instruction* var = replacements[static_cast<size_t>(indexValue)];
if (chain->NumInOperands() > 2) {
// Replace input access chain with another access chain.
BasicBlock::iterator chainIter(chain);
@@ -363,7 +363,7 @@
context()->get_type_mgr()->GetTypeAndPointerType(id,
SpvStorageClassFunction);
uint32_t ptrId = 0;
- if (id == context()->get_type_mgr()->GetId(pointeeTy)) {
+ if (pointeeTy->IsUniqueType()) {
// Non-ambiguous type, just ask the type manager for an id.
ptrId = context()->get_type_mgr()->GetTypeInstruction(pointerTy.get());
pointee_to_pointer_[id] = ptrId;
@@ -457,16 +457,16 @@
}
}
-size_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
+uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
assert(op.words.size() <= 2);
- size_t len = 0;
+ uint64_t len = 0;
for (uint32_t i = 0; i != op.words.size(); ++i) {
len |= (op.words[i] << (32 * i));
}
return len;
}
-size_t ScalarReplacementPass::GetConstantInteger(
+uint64_t ScalarReplacementPass::GetConstantInteger(
const Instruction* constant) const {
assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() ==
SpvOpTypeInt);
@@ -480,7 +480,7 @@
return GetIntegerLiteral(op);
}
-size_t ScalarReplacementPass::GetArrayLength(
+uint64_t ScalarReplacementPass::GetArrayLength(
const Instruction* arrayType) const {
assert(arrayType->opcode() == SpvOpTypeArray);
const Instruction* length =
@@ -488,14 +488,14 @@
return GetConstantInteger(length);
}
-size_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
+uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
assert(type->opcode() == SpvOpTypeVector ||
type->opcode() == SpvOpTypeMatrix);
const Operand& op = type->GetInOperand(1u);
assert(op.words.size() <= 2);
- size_t len = 0;
- for (uint32_t i = 0; i != op.words.size(); ++i) {
- len |= (op.words[i] << (32 * i));
+ uint64_t len = 0;
+ for (size_t i = 0; i != op.words.size(); ++i) {
+ len |= (static_cast<uint64_t>(op.words[i]) << (32ull * i));
}
return len;
}
@@ -638,8 +638,8 @@
switch (user->opcode()) {
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
- if (index == 2u) {
- uint32_t id = user->GetSingleWordOperand(3u);
+ if (index == 2u && user->NumInOperands() > 1) {
+ uint32_t id = user->GetSingleWordInOperand(1u);
const Instruction* opInst = get_def_use_mgr()->GetDef(id);
if (!IsCompileTimeConstantInst(opInst->opcode())) {
ok = false;
@@ -717,7 +717,7 @@
return false;
return true;
}
-bool ScalarReplacementPass::IsLargerThanSizeLimit(size_t length) const {
+bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
if (max_num_elements_ == 0) {
return false;
}
@@ -751,8 +751,10 @@
return false;
}
}
+ case SpvOpName:
+ case SpvOpMemberName:
case SpvOpStore:
- // No components are used. Things are just stored to.
+ // No components are used.
return true;
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain: {
@@ -781,16 +783,6 @@
return false;
}
}
- case SpvOpCopyObject: {
- // Follow the copy to see which components are used.
- auto t = GetUsedComponents(use);
- if (!t) {
- result.reset(nullptr);
- return false;
- }
- result->insert(t->begin(), t->end());
- return true;
- }
default:
// We do not know what is happening. Have to assume the worst.
result.reset(nullptr);
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index a82abf4..0ff9947 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -52,7 +52,8 @@
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
- IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
@@ -156,18 +157,18 @@
// Returns the value of an OpConstant of integer type.
//
// |constant| must use two or fewer words to generate the value.
- size_t GetConstantInteger(const Instruction* constant) const;
+ uint64_t GetConstantInteger(const Instruction* constant) const;
// Returns the integer literal for |op|.
- size_t GetIntegerLiteral(const Operand& op) const;
+ uint64_t GetIntegerLiteral(const Operand& op) const;
// Returns the array length for |arrayInst|.
- size_t GetArrayLength(const Instruction* arrayInst) const;
+ uint64_t GetArrayLength(const Instruction* arrayInst) const;
// Returns the number of elements in |type|.
//
// |type| must be a vector or matrix type.
- size_t GetNumElements(const Instruction* type) const;
+ uint64_t GetNumElements(const Instruction* type) const;
// Returns true if |id| is a specialization constant.
//
@@ -227,7 +228,7 @@
// Limit on the number of members in an object that will be replaced.
// 0 means there is no limit.
uint32_t max_num_elements_;
- bool IsLargerThanSizeLimit(size_t length) const;
+ bool IsLargerThanSizeLimit(uint64_t length) const;
char name_[55];
};
diff --git a/source/opt/simplification_pass.h b/source/opt/simplification_pass.h
index 348c96a..bcb88bf 100644
--- a/source/opt/simplification_pass.h
+++ b/source/opt/simplification_pass.h
@@ -33,7 +33,8 @@
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+ IRContext::kAnalysisTypes;
}
private:
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 0a5d390..f2cb2da 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -435,12 +435,22 @@
pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id()));
std::vector<Operand> phi_operands;
uint32_t arg_ix = 0;
+ std::unordered_map<uint32_t, uint32_t> already_seen;
for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) {
uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++);
- phi_operands.push_back(
- {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
- phi_operands.push_back(
- {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+ if (already_seen.count(pred_label) == 0) {
+ phi_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
+ phi_operands.push_back(
+ {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+ already_seen[pred_label] = op_val_id;
+ } else {
+ // It is possible that there are two edges from the same parent block.
+ // Since the OpPhi can have only one entry for each parent, we have to
+ // make sure the two edges are consistent with each other.
+ assert(already_seen[pred_label] == op_val_id &&
+ "Inconsistent value for duplicate edges.");
+ }
}
// Generate a new OpPhi instruction and insert it in its basic
@@ -453,7 +463,6 @@
pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb());
auto insert_it = phi_candidate->bb()->begin();
insert_it.InsertBefore(std::move(phi_inst));
-
pass_->context()->get_decoration_mgr()->CloneDecorations(
phi_candidate->var_id(), phi_candidate->result_id(),
{SpvDecorationRelaxedPrecision});
diff --git a/source/opt/strip_atomic_counter_memory_pass.cpp b/source/opt/strip_atomic_counter_memory_pass.cpp
new file mode 100644
index 0000000..47714b7
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/strip_atomic_counter_memory_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StripAtomicCounterMemoryPass::Process() {
+ bool changed = false;
+ context()->module()->ForEachInst([this, &changed](Instruction* inst) {
+ auto indices = spvOpcodeMemorySemanticsOperandIndices(inst->opcode());
+ if (indices.empty()) return;
+
+ for (auto idx : indices) {
+ auto mem_sem_id = inst->GetSingleWordOperand(idx);
+ const auto& mem_sem_inst =
+ context()->get_def_use_mgr()->GetDef(mem_sem_id);
+ // The spec explicitly says that this id must be an OpConstant
+ auto mem_sem_val = mem_sem_inst->GetSingleWordOperand(2);
+ if (!(mem_sem_val & SpvMemorySemanticsAtomicCounterMemoryMask)) {
+ continue;
+ }
+ mem_sem_val &= ~SpvMemorySemanticsAtomicCounterMemoryMask;
+
+ analysis::Integer int_type(32, false);
+ const analysis::Type* uint32_type =
+ context()->get_type_mgr()->GetRegisteredType(&int_type);
+ auto* new_const = context()->get_constant_mgr()->GetConstant(
+ uint32_type, {mem_sem_val});
+ auto* new_const_inst =
+ context()->get_constant_mgr()->GetDefiningInstruction(new_const);
+ auto new_const_id = new_const_inst->result_id();
+
+ inst->SetOperand(idx, {new_const_id});
+ context()->UpdateDefUse(inst);
+ changed = true;
+ }
+ });
+
+ return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/strip_atomic_counter_memory_pass.h b/source/opt/strip_atomic_counter_memory_pass.h
new file mode 100644
index 0000000..62e274a
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+#define SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Removes the AtomicCounterMemory bit from the value being passed into memory
+// semantics. This bit being set is ignored in Vulkan environments and
+// forbidden WebGPU ones.
+class StripAtomicCounterMemoryPass : public Pass {
+ public:
+ const char* name() const override { return "strip-atomic-counter-memory"; }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisScalarEvolution |
+ IRContext::kAnalysisRegisterPressure |
+ IRContext::kAnalysisValueNumberTable |
+ IRContext::kAnalysisStructuredCFG |
+ IRContext::kAnalysisBuiltinVarId |
+ IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+ }
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
diff --git a/source/opt/strip_reflect_info_pass.h b/source/opt/strip_reflect_info_pass.h
index 935a605..4e1999e 100644
--- a/source/opt/strip_reflect_info_pass.h
+++ b/source/opt/strip_reflect_info_pass.h
@@ -33,7 +33,8 @@
return IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
IRContext::kAnalysisDominatorAnalysis |
- IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
};
diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp
index d78ec56..dcfd4a5 100644
--- a/source/opt/struct_cfg_analysis.cpp
+++ b/source/opt/struct_cfg_analysis.cpp
@@ -19,7 +19,7 @@
namespace {
const uint32_t kMergeNodeIndex = 0;
const uint32_t kContinueNodeIndex = 1;
-}
+} // namespace
namespace spvtools {
namespace opt {
@@ -37,6 +37,8 @@
}
void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) {
+ if (func->begin() == func->end()) return;
+
std::list<BasicBlock*> order;
context_->cfg()->ComputeStructuredOrder(func, &*func->begin(), &order);
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 722b9b9..001883c 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -744,6 +744,9 @@
case SpvOpTypeNamedBarrier:
type = new NamedBarrier();
break;
+ case SpvOpTypeAccelerationStructureNV:
+ type = new AccelerationStructureNV();
+ break;
default:
SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
break;
diff --git a/source/opt/types.h b/source/opt/types.h
index d77117d..fe0f39a 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -598,7 +598,7 @@
DefineParameterlessType(Queue, queue);
DefineParameterlessType(PipeStorage, pipe_storage);
DefineParameterlessType(NamedBarrier, named_barrier);
-DefineParameterlessType(AccelerationStructureNV, acceleration_structure);
+DefineParameterlessType(AccelerationStructureNV, accelerationStructureNV);
#undef DefineParameterlessType
} // namespace analysis
diff --git a/source/opt/upgrade_memory_model.cpp b/source/opt/upgrade_memory_model.cpp
index 8def55f..d8836a4 100644
--- a/source/opt/upgrade_memory_model.cpp
+++ b/source/opt/upgrade_memory_model.cpp
@@ -16,6 +16,7 @@
#include <utility>
+#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
#include "source/util/make_unique.h"
@@ -68,6 +69,22 @@
// instructions. Additionally, Workgroup storage class variables and function
// parameters are implicitly coherent in GLSL450.
+ // Upgrade modf and frexp first since they generate new stores.
+ for (auto& func : *get_module()) {
+ func.ForEachInst([this](Instruction* inst) {
+ if (inst->opcode() == SpvOpExtInst) {
+ auto ext_inst = inst->GetSingleWordInOperand(1u);
+ if (ext_inst == GLSLstd450Modf || ext_inst == GLSLstd450Frexp) {
+ auto import =
+ get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
+ if (reinterpret_cast<char*>(import->GetInOperand(0u).words.data()) ==
+ std::string("GLSL.std.450")) {
+ UpgradeExtInst(inst);
+ }
+ }
+ }
+ });
+ }
for (auto& func : *get_module()) {
func.ForEachInst([this](Instruction* inst) {
bool is_coherent = false;
@@ -104,11 +121,11 @@
switch (inst->opcode()) {
case SpvOpLoad:
- UpgradeFlags(inst, 1u, is_coherent, is_volatile, kAvailability,
+ UpgradeFlags(inst, 1u, is_coherent, is_volatile, kVisibility,
kMemory);
break;
case SpvOpStore:
- UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility,
+ UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
kMemory);
break;
case SpvOpCopyMemory:
@@ -125,11 +142,11 @@
break;
case SpvOpImageRead:
case SpvOpImageSparseRead:
- UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
- kImage);
+ UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility, kImage);
break;
case SpvOpImageWrite:
- UpgradeFlags(inst, 3u, is_coherent, is_volatile, kVisibility, kImage);
+ UpgradeFlags(inst, 3u, is_coherent, is_volatile, kAvailability,
+ kImage);
break;
default:
break;
@@ -143,15 +160,15 @@
}
// According to SPV_KHR_vulkan_memory_model, if both available and
// visible flags are used the first scope operand is for availability
- // (reads) and the second is for visibiity (writes).
- if (src_coherent) {
- inst->AddOperand(
- {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
- }
+ // (writes) and the second is for visibility (reads).
if (dst_coherent) {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
}
+ if (src_coherent) {
+ inst->AddOperand(
+ {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+ }
});
}
}
@@ -581,5 +598,43 @@
return false;
}
+void UpgradeMemoryModel::UpgradeExtInst(Instruction* ext_inst) {
+ const bool is_modf = ext_inst->GetSingleWordInOperand(1u) == GLSLstd450Modf;
+ auto ptr_id = ext_inst->GetSingleWordInOperand(3u);
+ auto ptr_type_id = get_def_use_mgr()->GetDef(ptr_id)->type_id();
+ auto pointee_type_id =
+ get_def_use_mgr()->GetDef(ptr_type_id)->GetSingleWordInOperand(1u);
+ auto element_type_id = ext_inst->type_id();
+ std::vector<const analysis::Type*> element_types(2);
+ element_types[0] = context()->get_type_mgr()->GetType(element_type_id);
+ element_types[1] = context()->get_type_mgr()->GetType(pointee_type_id);
+ analysis::Struct struct_type(element_types);
+ uint32_t struct_id =
+ context()->get_type_mgr()->GetTypeInstruction(&struct_type);
+ // Change the operation
+ GLSLstd450 new_op = is_modf ? GLSLstd450ModfStruct : GLSLstd450FrexpStruct;
+ ext_inst->SetOperand(3u, {static_cast<uint32_t>(new_op)});
+ // Remove the pointer argument
+ ext_inst->RemoveOperand(5u);
+ // Set the type id to the new struct.
+ ext_inst->SetResultType(struct_id);
+
+ // The result is now a struct of the original result. The zero'th element is
+ // old result and should replace the old result. The one'th element needs to
+ // be stored via a new instruction.
+ auto where = ext_inst->NextNode();
+ InstructionBuilder builder(
+ context(), where,
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ auto extract_0 =
+ builder.AddCompositeExtract(element_type_id, ext_inst->result_id(), {0});
+ context()->ReplaceAllUsesWith(ext_inst->result_id(), extract_0->result_id());
+ // The extract's input was just changed to itself, so fix that.
+ extract_0->SetInOperand(0u, {ext_inst->result_id()});
+ auto extract_1 =
+ builder.AddCompositeExtract(pointee_type_id, ext_inst->result_id(), {1});
+ builder.AddStore(ptr_id, extract_1->result_id());
+}
+
} // namespace opt
} // namespace spvtools
diff --git a/source/opt/upgrade_memory_model.h b/source/opt/upgrade_memory_model.h
index 29c801c..9adc33b 100644
--- a/source/opt/upgrade_memory_model.h
+++ b/source/opt/upgrade_memory_model.h
@@ -118,6 +118,11 @@
// Returns true if |scope_id| is SpvScopeDevice.
bool IsDeviceScope(uint32_t scope_id);
+ // Upgrades GLSL.std.450 modf and frexp. Both instructions are replaced with
+ // their struct versions. New extracts and a store are added in order to
+ // facilitate adding memory model flags.
+ void UpgradeExtInst(Instruction* modf);
+
// Caches the result of TraceInstruction. For a given result id and set of
// indices, stores whether that combination is coherent and/or volatile.
std::unordered_map<std::pair<uint32_t, std::vector<uint32_t>>,
diff --git a/source/opt/vector_dce.cpp b/source/opt/vector_dce.cpp
index 911242e..92532e3 100644
--- a/source/opt/vector_dce.cpp
+++ b/source/opt/vector_dce.cpp
@@ -66,7 +66,8 @@
switch (current_inst->opcode()) {
case SpvOpCompositeExtract:
- MarkExtractUseAsLive(current_inst, live_components, &work_list);
+ MarkExtractUseAsLive(current_inst, current_item.components,
+ live_components, &work_list);
break;
case SpvOpCompositeInsert:
MarkInsertUsesAsLive(current_item, live_components, &work_list);
@@ -92,6 +93,7 @@
}
void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst,
+ const utils::BitVector& live_elements,
LiveComponentMap* live_components,
std::vector<WorkListItem>* work_list) {
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -102,7 +104,11 @@
if (HasVectorOrScalarResult(operand_inst)) {
WorkListItem new_item;
new_item.instruction = operand_inst;
- new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+ if (current_inst->NumInOperands() < 2) {
+ new_item.components = live_elements;
+ } else {
+ new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+ }
AddItemToWorkListIfNeeded(new_item, live_components, work_list);
}
}
@@ -113,30 +119,44 @@
std::vector<VectorDCE::WorkListItem>* work_list) {
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
- uint32_t insert_position =
- current_item.instruction->GetSingleWordInOperand(2);
+ if (current_item.instruction->NumInOperands() > 2) {
+ uint32_t insert_position =
+ current_item.instruction->GetSingleWordInOperand(2);
- // Add the elements of the composite object that are used.
- uint32_t operand_id =
- current_item.instruction->GetSingleWordInOperand(kInsertCompositeIdInIdx);
- Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
+ // Add the elements of the composite object that are used.
+ uint32_t operand_id = current_item.instruction->GetSingleWordInOperand(
+ kInsertCompositeIdInIdx);
+ Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
- WorkListItem new_item;
- new_item.instruction = operand_inst;
- new_item.components = current_item.components;
- new_item.components.Clear(insert_position);
+ WorkListItem new_item;
+ new_item.instruction = operand_inst;
+ new_item.components = current_item.components;
+ new_item.components.Clear(insert_position);
- AddItemToWorkListIfNeeded(new_item, live_components, work_list);
+ AddItemToWorkListIfNeeded(new_item, live_components, work_list);
- // Add the element being inserted if it is used.
- if (current_item.components.Get(insert_position)) {
- uint32_t obj_operand_id =
+ // Add the element being inserted if it is used.
+ if (current_item.components.Get(insert_position)) {
+ uint32_t obj_operand_id =
+ current_item.instruction->GetSingleWordInOperand(
+ kInsertObjectIdInIdx);
+ Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
+ WorkListItem new_item_for_obj;
+ new_item_for_obj.instruction = obj_operand_inst;
+ new_item_for_obj.components.Set(0);
+ AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+ }
+ } else {
+ // If there are no indices, then this is a copy of the object being
+ // inserted.
+ uint32_t object_id =
current_item.instruction->GetSingleWordInOperand(kInsertObjectIdInIdx);
- Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
- WorkListItem new_item_for_obj;
- new_item_for_obj.instruction = obj_operand_inst;
- new_item_for_obj.components.Set(0);
- AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+ Instruction* object_inst = def_use_mgr->GetDef(object_id);
+
+ WorkListItem new_item;
+ new_item.instruction = object_inst;
+ new_item.components = current_item.components;
+ AddItemToWorkListIfNeeded(new_item, live_components, work_list);
}
}
@@ -323,14 +343,23 @@
bool VectorDCE::RewriteInsertInstruction(
Instruction* current_inst, const utils::BitVector& live_components) {
// If the value being inserted is not live, then we can skip the insert.
- bool modified = false;
+
+ if (current_inst->NumInOperands() == 2) {
+ // If there are no indices, then this is the same as a copy.
+ context()->KillNamesAndDecorates(current_inst->result_id());
+ uint32_t object_id =
+ current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx);
+ context()->ReplaceAllUsesWith(current_inst->result_id(), object_id);
+ return true;
+ }
+
uint32_t insert_index = current_inst->GetSingleWordInOperand(2);
if (!live_components.Get(insert_index)) {
- modified = true;
context()->KillNamesAndDecorates(current_inst->result_id());
uint32_t composite_id =
current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
context()->ReplaceAllUsesWith(current_inst->result_id(), composite_id);
+ return true;
}
// If the values already in the composite are not used, then replace it with
@@ -342,9 +371,10 @@
uint32_t undef_id = Type2Undef(current_inst->type_id());
current_inst->SetInOperand(kInsertCompositeIdInIdx, {undef_id});
context()->AnalyzeUses(current_inst);
+ return true;
}
- return modified;
+ return false;
}
void VectorDCE::AddItemToWorkListIfNeeded(
diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h
index 4888699..4f039c5 100644
--- a/source/opt/vector_dce.h
+++ b/source/opt/vector_dce.h
@@ -53,7 +53,8 @@
return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
- IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+ IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
@@ -128,6 +129,7 @@
// live. If anything becomes live they are added to |work_list| and
// |live_components| is updated accordingly.
void MarkExtractUseAsLive(const Instruction* current_inst,
+ const utils::BitVector& live_elements,
LiveComponentMap* live_components,
std::vector<WorkListItem>* work_list);
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 57475ac..def4d21 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -13,29 +13,60 @@
# limitations under the License.
set(SPIRV_TOOLS_REDUCE_SOURCES
change_operand_reduction_opportunity.h
- operand_to_const_reduction_pass.h
- operand_to_dominating_id_reduction_pass.h
+ change_operand_to_undef_reduction_opportunity.h
+ merge_blocks_reduction_opportunity.h
+ merge_blocks_reduction_opportunity_finder.h
+ operand_to_const_reduction_opportunity_finder.h
+ operand_to_undef_reduction_opportunity_finder.h
+ operand_to_dominating_id_reduction_opportunity_finder.h
reducer.h
reduction_opportunity.h
+ reduction_opportunity_finder.h
reduction_pass.h
+ reduction_util.h
+ remove_block_reduction_opportunity.h
+ remove_block_reduction_opportunity_finder.h
remove_instruction_reduction_opportunity.h
- remove_opname_instruction_reduction_pass.h
- remove_unreferenced_instruction_reduction_pass.h
+ remove_function_reduction_opportunity.h
+ remove_function_reduction_opportunity_finder.h
+ remove_opname_instruction_reduction_opportunity_finder.h
+ remove_selection_reduction_opportunity.h
+ remove_selection_reduction_opportunity_finder.h
+ remove_unreferenced_instruction_reduction_opportunity_finder.h
structured_loop_to_selection_reduction_opportunity.h
- structured_loop_to_selection_reduction_pass.h
+ structured_loop_to_selection_reduction_opportunity_finder.h
+ conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+ conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
+ simple_conditional_branch_to_branch_opportunity_finder.h
+ simple_conditional_branch_to_branch_reduction_opportunity.h
change_operand_reduction_opportunity.cpp
- operand_to_const_reduction_pass.cpp
- operand_to_dominating_id_reduction_pass.cpp
+ change_operand_to_undef_reduction_opportunity.cpp
+ merge_blocks_reduction_opportunity.cpp
+ merge_blocks_reduction_opportunity_finder.cpp
+ operand_to_const_reduction_opportunity_finder.cpp
+ operand_to_undef_reduction_opportunity_finder.cpp
+ operand_to_dominating_id_reduction_opportunity_finder.cpp
reducer.cpp
reduction_opportunity.cpp
reduction_pass.cpp
+ reduction_util.cpp
+ remove_block_reduction_opportunity.cpp
+ remove_block_reduction_opportunity_finder.cpp
+ remove_function_reduction_opportunity.cpp
+ remove_function_reduction_opportunity_finder.cpp
remove_instruction_reduction_opportunity.cpp
- remove_unreferenced_instruction_reduction_pass.cpp
- remove_opname_instruction_reduction_pass.cpp
+ remove_selection_reduction_opportunity.cpp
+ remove_selection_reduction_opportunity_finder.cpp
+ remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+ remove_opname_instruction_reduction_opportunity_finder.cpp
structured_loop_to_selection_reduction_opportunity.cpp
- structured_loop_to_selection_reduction_pass.cpp
- )
+ structured_loop_to_selection_reduction_opportunity_finder.cpp
+ conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+ conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+ simple_conditional_branch_to_branch_opportunity_finder.cpp
+ simple_conditional_branch_to_branch_reduction_opportunity.cpp
+)
if(MSVC)
# Enable parallel builds across four cores for this lib
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
index 5430d3e..c3f6fd7 100644
--- a/source/reduce/change_operand_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
index 9e43ec9..18e6ca1 100644
--- a/source/reduce/change_operand_reduction_opportunity.h
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -15,40 +15,33 @@
#ifndef SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
-#include "reduction_opportunity.h"
#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
-
-// Captures the opportunity to change an id operand of an instruction to some
-// other id.
+// An opportunity to replace an id operand of an instruction with some other id.
class ChangeOperandReductionOpportunity : public ReductionOpportunity {
public:
// Constructs the opportunity to replace operand |operand_index| of |inst|
// with |new_id|.
- ChangeOperandReductionOpportunity(Instruction* inst, uint32_t operand_index,
- uint32_t new_id)
+ ChangeOperandReductionOpportunity(opt::Instruction* inst,
+ uint32_t operand_index, uint32_t new_id)
: inst_(inst),
operand_index_(operand_index),
original_id_(inst->GetOperand(operand_index).words[0]),
original_type_(inst->GetOperand(operand_index).type),
new_id_(new_id) {}
- // Determines whether the opportunity can be applied; it may have been viable
- // when discovered but later disabled by the application of some other
- // reduction opportunity.
bool PreconditionHolds() override;
protected:
- // Apply the change of operand.
void Apply() override;
private:
- Instruction* const inst_;
+ opt::Instruction* const inst_;
const uint32_t operand_index_;
const uint32_t original_id_;
const spv_operand_type_t original_type_;
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.cpp b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
new file mode 100644
index 0000000..8e33da6
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool ChangeOperandToUndefReductionOpportunity::PreconditionHolds() {
+ // Check that the instruction still has the original operand.
+ return inst_->NumOperands() > operand_index_ &&
+ inst_->GetOperand(operand_index_).words[0] == original_id_;
+}
+
+void ChangeOperandToUndefReductionOpportunity::Apply() {
+ auto operand = inst_->GetOperand(operand_index_);
+ auto operand_id = operand.words[0];
+ auto operand_id_def = context_->get_def_use_mgr()->GetDef(operand_id);
+ auto operand_type_id = operand_id_def->type_id();
+ // The opportunity should not exist unless this holds.
+ assert(operand_type_id);
+ auto undef_id = FindOrCreateGlobalUndef(context_, operand_type_id);
+ inst_->SetOperand(operand_index_, {undef_id});
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.h b/source/reduce/change_operand_to_undef_reduction_opportunity.h
new file mode 100644
index 0000000..ffd3155
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to replace an id operand of an instruction with undef.
+class ChangeOperandToUndefReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Constructs the opportunity to replace operand |operand_index| of |inst|
+ // with undef.
+ ChangeOperandToUndefReductionOpportunity(opt::IRContext* context,
+ opt::Instruction* inst,
+ uint32_t operand_index)
+ : context_(context),
+ inst_(inst),
+ operand_index_(operand_index),
+ original_id_(inst->GetOperand(operand_index).words[0]) {}
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ opt::IRContext* context_;
+ opt::Instruction* const inst_;
+ const uint32_t operand_index_;
+ const uint32_t original_id_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..2feca4a
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -0,0 +1,89 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
+ GetAvailableOpportunities(IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ // Find the opportunities for redirecting all false targets before the
+ // opportunities for redirecting all true targets because the former
+ // opportunities disable the latter, and vice versa, and the efficiency of the
+ // reducer is improved by avoiding contiguous opportunities that disable one
+ // another.
+ for (bool redirect_to_true : {true, false}) {
+ // Consider every function.
+ for (auto& function : *context->module()) {
+ // Consider every block in the function.
+ for (auto& block : function) {
+ // The terminator must be SpvOpBranchConditional.
+ Instruction* terminator = block.terminator();
+ if (terminator->opcode() != SpvOpBranchConditional) {
+ continue;
+ }
+
+ uint32_t true_block_id =
+ terminator->GetSingleWordInOperand(kTrueBranchOperandIndex);
+ uint32_t false_block_id =
+ terminator->GetSingleWordInOperand(kFalseBranchOperandIndex);
+
+ // The conditional branch must not already be simplified.
+ if (true_block_id == false_block_id) {
+ continue;
+ }
+
+ // The redirected target must not be a back-edge to a structured loop
+ // header.
+ uint32_t redirected_block_id =
+ redirect_to_true ? false_block_id : true_block_id;
+ uint32_t containing_loop_header =
+ context->GetStructuredCFGAnalysis()->ContainingLoop(block.id());
+ // The structured CFG analysis does not include a loop header as part
+ // of the loop construct, but we want to include it, so handle this
+ // special case:
+ if (block.GetLoopMergeInst() != nullptr) {
+ containing_loop_header = block.id();
+ }
+ if (redirected_block_id == containing_loop_header) {
+ continue;
+ }
+
+ result.push_back(
+ MakeUnique<
+ ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
+ block.terminator(), redirect_to_true));
+ }
+ }
+ }
+ return result;
+}
+
+std::string
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::GetName() const {
+ return "ConditionalBranchToSimpleConditionalBranchOpportunityFinder";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
new file mode 100644
index 0000000..c582a88
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to simplify conditional branches into simple
+// conditional branches (conditional branches with one target).
+class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const override;
+
+ std::string GetName() const override;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..92c621e
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Instruction;
+
+ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+ ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+ Instruction* conditional_branch_instruction, bool redirect_to_true)
+ : conditional_branch_instruction_(conditional_branch_instruction),
+ redirect_to_true_(redirect_to_true) {}
+
+bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+ PreconditionHolds() {
+ // Another opportunity may have already simplified this conditional branch,
+ // which should disable this opportunity.
+ return conditional_branch_instruction_->GetSingleWordInOperand(
+ kTrueBranchOperandIndex) !=
+ conditional_branch_instruction_->GetSingleWordInOperand(
+ kFalseBranchOperandIndex);
+}
+
+void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
+ uint32_t operand_to_modify =
+ redirect_to_true_ ? kFalseBranchOperandIndex : kTrueBranchOperandIndex;
+ uint32_t operand_to_copy =
+ redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
+
+ // Do the branch redirection.
+ conditional_branch_instruction_->SetInOperand(
+ operand_to_modify,
+ {conditional_branch_instruction_->GetSingleWordInOperand(
+ operand_to_copy)});
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
new file mode 100644
index 0000000..421906b
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to simplify a conditional branch to a simple conditional
+// branch (a conditional branch with one target).
+class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
+ : public ReductionOpportunity {
+ public:
+ // Constructs an opportunity to simplify |conditional_branch_instruction|. If
+ // |redirect_to_true| is true, the false target will be changed to also point
+ // to the true target; otherwise, the true target will be changed to also
+ // point to the false target.
+ explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+ opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ opt::Instruction* conditional_branch_instruction_;
+
+ // If true, the false target will be changed to point to the true target;
+ // otherwise, the true target will be changed to point to the false target.
+ bool redirect_to_true_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp
new file mode 100644
index 0000000..42c7843
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.cpp
@@ -0,0 +1,83 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+#include "source/opt/block_merge_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+using opt::IRContext;
+
+MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
+ IRContext* context, Function* function, BasicBlock* block) {
+ // Precondition: the terminator has to be OpBranch.
+ assert(block->terminator()->opcode() == SpvOpBranch);
+ context_ = context;
+ function_ = function;
+ // Get the successor block associated with the OpBranch.
+ successor_block_ =
+ context->cfg()->block(block->terminator()->GetSingleWordInOperand(0));
+}
+
+bool MergeBlocksReductionOpportunity::PreconditionHolds() {
+ // Merge block opportunities can disable each other.
+ // Example: Given blocks: A->B->C.
+ // A is a loop header; B and C are blocks in the loop; C ends with OpReturn.
+ // There are two opportunities: B and C can be merged with their predecessors.
+ // Merge C. B now ends with OpReturn. We now just have: A->B.
+ // Merge B is now disabled, as this would lead to A, a loop header, ending
+ // with an OpReturn, which is invalid.
+
+ const auto predecessors = context_->cfg()->preds(successor_block_->id());
+ assert(1 == predecessors.size() &&
+ "For a successor to be merged into its predecessor, exactly one "
+ "predecessor must be present.");
+ const uint32_t predecessor_id = predecessors[0];
+ BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+ return opt::blockmergeutil::CanMergeWithSuccessor(context_,
+ predecessor_block);
+}
+
+void MergeBlocksReductionOpportunity::Apply() {
+ // While the original block that targeted the successor may not exist anymore
+ // (it might have been merged with another block), some block must exist that
+ // targets the successor. Find it.
+
+ const auto predecessors = context_->cfg()->preds(successor_block_->id());
+ assert(1 == predecessors.size() &&
+ "For a successor to be merged into its predecessor, exactly one "
+ "predecessor must be present.");
+ const uint32_t predecessor_id = predecessors[0];
+
+ // We need an iterator pointing to the predecessor, hence the loop.
+ for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+ if (bi->id() == predecessor_id) {
+ opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
+ // Block merging changes the control flow graph, so invalidate it.
+ context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+ return;
+ }
+ }
+
+ assert(false &&
+ "Unreachable: we should have found a block with the desired id.");
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity.h b/source/reduce/merge_blocks_reduction_opportunity.h
new file mode 100644
index 0000000..5c9180b
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to merge two blocks into one.
+class MergeBlocksReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Creates the opportunity to merge |block| with its successor, where |block|
+ // is inside |function|, and |context| is the enclosing IR context.
+ MergeBlocksReductionOpportunity(opt::IRContext* context,
+ opt::Function* function,
+ opt::BasicBlock* block);
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ opt::IRContext* context_;
+ opt::Function* function_;
+
+ // Rather than holding on to the block that can be merged with its successor,
+ // we hold on to its successor. This is because the predecessor block might
+ // get merged with *its* predecessor, and so will no longer exist, while the
+ // successor will continue to exist until this opportunity gets applied.
+ opt::BasicBlock* successor_block_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..89d6263
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/opt/block_merge_util.h"
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::string MergeBlocksReductionOpportunityFinder::GetName() const {
+ return "MergeBlocksReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ // Consider every block in every function.
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ // See whether it is possible to merge this block with its successor.
+ if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
+ // It is, so record an opportunity to do this.
+ result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
+ context, &function, &block));
+ }
+ }
+ }
+ return result;
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h
new file mode 100644
index 0000000..dbf82fe
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to merge blocks together.
+class MergeBlocksReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ MergeBlocksReductionOpportunityFinder() = default;
+
+ ~MergeBlocksReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
similarity index 88%
rename from source/reduce/operand_to_const_reduction_pass.cpp
rename to source/reduce/operand_to_const_reduction_opportunity_finder.cpp
index 35c0f4b..3e0a224 100644
--- a/source/reduce/operand_to_const_reduction_pass.cpp
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
@@ -12,18 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "operand_to_const_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+
#include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
+using opt::IRContext;
std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToConstReductionPass::GetAvailableOpportunities(
- opt::IRContext* context) const {
+OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
assert(result.empty());
@@ -74,8 +75,8 @@
return result;
}
-std::string OperandToConstReductionPass::GetName() const {
- return "OperandToConstReductionPass";
+std::string OperandToConstReductionOpportunityFinder::GetName() const {
+ return "OperandToConstReductionOpportunityFinder";
}
} // namespace reduce
diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h
new file mode 100644
index 0000000..93c0dcd
--- /dev/null
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to replace id operands of instructions with ids of
+// constants. This reduces the extent to which ids of non-constants are used,
+// paving the way for instructions that generate them to be eliminated.
+class OperandToConstReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ OperandToConstReductionOpportunityFinder() = default;
+
+ ~OperandToConstReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.h b/source/reduce/operand_to_const_reduction_pass.h
deleted file mode 100644
index fd7cfa0..0000000
--- a/source/reduce/operand_to_const_reduction_pass.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for turning id operands of instructions into ids of
-// constants. This reduces the extent to which ids of non-constants are used,
-// paving the way for instructions that generate them to be eliminated by other
-// passes.
-class OperandToConstReductionPass : public ReductionPass {
- public:
- // Creates the reduction pass in the context of the given target environment
- // |target_env|
- explicit OperandToConstReductionPass(const spv_target_env target_env)
- : ReductionPass(target_env) {}
-
- ~OperandToConstReductionPass() override = default;
-
- // The name of this pass.
- std::string GetName() const final;
-
- protected:
- // Finds all opportunities for replacing an operand with a constant in the
- // given module.
- std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
- opt::IRContext* context) const final;
-
- private:
-};
-
-} // namespace reduce
-} // namespace spvtools
-
-#endif // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
similarity index 84%
rename from source/reduce/operand_to_dominating_id_reduction_pass.cpp
rename to source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
index 9280a41..13beb89 100644
--- a/source/reduce/operand_to_dominating_id_reduction_pass.cpp
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
@@ -12,18 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "operand_to_dominating_id_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
#include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToDominatingIdReductionPass::GetAvailableOpportunities(
- opt::IRContext* context) const {
+OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
// Go through every instruction in every block, considering it as a potential
@@ -55,11 +58,12 @@
return result;
}
-void OperandToDominatingIdReductionPass::GetOpportunitiesForDominatingInst(
- std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
- opt::Instruction* candidate_dominator,
- opt::Function::iterator candidate_dominator_block, opt::Function* function,
- opt::IRContext* context) const {
+void OperandToDominatingIdReductionOpportunityFinder::
+ GetOpportunitiesForDominatingInst(
+ std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+ Instruction* candidate_dominator,
+ Function::iterator candidate_dominator_block, Function* function,
+ IRContext* context) const {
assert(candidate_dominator->HasResultId());
assert(candidate_dominator->type_id());
auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -106,8 +110,8 @@
}
}
-std::string OperandToDominatingIdReductionPass::GetName() const {
- return "OperandToDominatingIdReductionPass";
+std::string OperandToDominatingIdReductionOpportunityFinder::GetName() const {
+ return "OperandToDominatingIdReductionOpportunityFinder";
}
} // namespace reduce
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7745ff7
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder that aims to bring to SPIR-V (and generalize) the idea from
+// human-readable languages of e.g. finding opportunities to replace an
+// expression with one of its arguments, (x + y) -> x, or with a reference to an
+// identifier that was assigned to higher up in the program. The generalization
+// of this is to replace an id with a different id of the same type defined in
+// some dominating instruction.
+//
+// If id x is defined and then used several times, changing each use of x to
+// some dominating definition may eventually allow the statement defining x
+// to be eliminated by another pass.
+class OperandToDominatingIdReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ OperandToDominatingIdReductionOpportunityFinder() = default;
+
+ ~OperandToDominatingIdReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+ void GetOpportunitiesForDominatingInst(
+ std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+ opt::Instruction* dominating_instruction,
+ opt::Function::iterator candidate_dominator_block,
+ opt::Function* function, opt::IRContext* context) const;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.h b/source/reduce/operand_to_dominating_id_reduction_pass.h
deleted file mode 100644
index b62b6ae..0000000
--- a/source/reduce/operand_to_dominating_id_reduction_pass.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass that aims to bring to SPIR-V (and generalize) the idea from
-// human-readable languages of e.g. replacing an expression with one of its
-// arguments, (x + y) -> x, or with a reference to an identifier that was
-// assigned to higher up in the program. The generalization of this is to
-// replace an id with a different id of the same type defined in some
-// dominating instruction.
-//
-// If id x is defined and then used several times, changing each use of x to
-// some dominating definition may eventually allow the statement defining x
-// to be eliminated by another pass.
-class OperandToDominatingIdReductionPass : public ReductionPass {
- public:
- // Creates the reduction pass in the context of the given target environment
- // |target_env|
- explicit OperandToDominatingIdReductionPass(const spv_target_env target_env)
- : ReductionPass(target_env) {}
-
- ~OperandToDominatingIdReductionPass() override = default;
-
- // The name of this pass.
- std::string GetName() const final;
-
- protected:
- // Finds all opportunities for replacing an operand with a dominating
- // instruction in a given module.
- std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
- opt::IRContext* context) const final;
-
- private:
- void GetOpportunitiesForDominatingInst(
- std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
- opt::Instruction* dominating_instruction,
- opt::Function::iterator candidate_dominator_block,
- opt::Function* function, opt::IRContext* context) const;
-};
-
-} // namespace reduce
-} // namespace spvtools
-
-#endif // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..579b7df
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+
+#include "source/opt/instruction.h"
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ for (auto& inst : block) {
+ // Skip instructions that result in a pointer type.
+ auto type_id = inst.type_id();
+ if (type_id) {
+ auto type_id_def = context->get_def_use_mgr()->GetDef(type_id);
+ if (type_id_def->opcode() == SpvOpTypePointer) {
+ continue;
+ }
+ }
+
+ // We iterate through the operands using an explicit index (rather
+ // than using a lambda) so that we use said index in the construction
+ // of a ChangeOperandToUndefReductionOpportunity
+ for (uint32_t index = 0; index < inst.NumOperands(); index++) {
+ const auto& operand = inst.GetOperand(index);
+
+ if (spvIsInIdType(operand.type)) {
+ const auto operand_id = operand.words[0];
+ auto operand_id_def =
+ context->get_def_use_mgr()->GetDef(operand_id);
+
+ // Skip constant and undef operands.
+ // We always want the reducer to make the module "smaller", which
+ // ensures termination.
+ // Therefore, we assume: id > undef id > constant id.
+ if (spvOpcodeIsConstantOrUndef(operand_id_def->opcode())) {
+ continue;
+ }
+
+ // Don't replace function operands with undef.
+ if (operand_id_def->opcode() == SpvOpFunction) {
+ continue;
+ }
+
+ // Only consider operands that have a type.
+ auto operand_type_id = operand_id_def->type_id();
+ if (operand_type_id) {
+ auto operand_type_id_def =
+ context->get_def_use_mgr()->GetDef(operand_type_id);
+
+ // Skip pointer operands.
+ if (operand_type_id_def->opcode() == SpvOpTypePointer) {
+ continue;
+ }
+
+ result.push_back(
+ MakeUnique<ChangeOperandToUndefReductionOpportunity>(
+ context, &inst, index));
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+std::string OperandToUndefReductionOpportunityFinder::GetName() const {
+ return "OperandToUndefReductionOpportunityFinder";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
new file mode 100644
index 0000000..9cdd8cd
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to replace id operands of instructions with ids of
+// undef.
+class OperandToUndefReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ OperandToUndefReductionOpportunityFinder() = default;
+
+ ~OperandToUndefReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/pch_source_reduce.h b/source/reduce/pch_source_reduce.h
index 823b55a..6c0da0c 100644
--- a/source/reduce/pch_source_reduce.h
+++ b/source/reduce/pch_source_reduce.h
@@ -16,8 +16,8 @@
#include <functional>
#include <string>
#include "source/reduce/change_operand_reduction_opportunity.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/reduction_pass.h"
#include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 4f4429a..a677be3 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -12,14 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "source/reduce/reducer.h"
+
#include <cassert>
#include <sstream>
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
#include "source/spirv_reducer_options.h"
-#include "reducer.h"
-#include "reduction_pass.h"
-
namespace spvtools {
namespace reduce {
@@ -53,13 +64,25 @@
Reducer::ReductionResultStatus Reducer::Run(
std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
- spv_const_reducer_options options) const {
- std::vector<uint32_t> current_binary = binary_in;
+ spv_const_reducer_options options,
+ spv_validator_options validator_options) const {
+ std::vector<uint32_t> current_binary(std::move(binary_in));
+
+ spvtools::SpirvTools tools(impl_->target_env);
+ assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
// Keeps track of how many reduction attempts have been tried. Reduction
// bails out if this reaches a given limit.
uint32_t reductions_applied = 0;
+ // Initial state should be valid.
+ if (!tools.Validate(¤t_binary[0], current_binary.size(),
+ validator_options)) {
+ impl_->consumer(SPV_MSG_INFO, nullptr, {},
+ "Initial binary is invalid; stopping.");
+ return Reducer::ReductionResultStatus::kInitialStateInvalid;
+ }
+
// Initial state should be interesting.
if (!impl_->interestingness_function(current_binary, reductions_applied)) {
impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -93,25 +116,31 @@
do {
auto maybe_result = pass->TryApplyReduction(current_binary);
if (maybe_result.empty()) {
- // This pass did not have any impact, so move on to the next pass.
+ // For this round, the pass has no more opportunities (chunks) to
+ // apply, so move on to the next pass.
impl_->consumer(
SPV_MSG_INFO, nullptr, {},
("Pass " + pass->GetName() + " did not make a reduction step.")
.c_str());
break;
}
+ bool interesting = false;
std::stringstream stringstream;
reductions_applied++;
stringstream << "Pass " << pass->GetName() << " made reduction step "
<< reductions_applied << ".";
impl_->consumer(SPV_MSG_INFO, nullptr, {},
(stringstream.str().c_str()));
- if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+ if (!tools.Validate(&maybe_result[0], maybe_result.size(),
+ validator_options)) {
// The reduction step went wrong and an invalid binary was produced.
// By design, this shouldn't happen; this is a safeguard to stop an
// invalid binary from being regarded as interesting.
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step produced an invalid binary.");
+ if (options->fail_on_validation_error) {
+ return Reducer::ReductionResultStatus::kStateInvalid;
+ }
} else if (impl_->interestingness_function(maybe_result,
reductions_applied)) {
// Success! The binary produced by this reduction step is
@@ -120,8 +149,11 @@
impl_->consumer(SPV_MSG_INFO, nullptr, {},
"Reduction step succeeded.");
current_binary = std::move(maybe_result);
+ interesting = true;
another_round_worthwhile = true;
}
+ // We must call this before the next call to TryApplyReduction.
+ pass->NotifyInteresting(interesting);
// Bail out if the reduction step limit has been reached.
} while (!impl_->ReachedStepLimit(reductions_applied, options));
}
@@ -140,9 +172,38 @@
return Reducer::ReductionResultStatus::kComplete;
}
+void Reducer::AddDefaultReductionPasses() {
+ AddReductionPass(spvtools::MakeUnique<
+ RemoveOpNameInstructionReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
+ AddReductionPass(spvtools::MakeUnique<
+ RemoveUnreferencedInstructionReductionOpportunityFinder>());
+ AddReductionPass(spvtools::MakeUnique<
+ StructuredLoopToSelectionReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<
+ ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
+ AddReductionPass(
+ spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+}
+
void Reducer::AddReductionPass(
- std::unique_ptr<ReductionPass>&& reduction_pass) {
- impl_->passes.push_back(std::move(reduction_pass));
+ std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+ impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
+ impl_->target_env, std::move(finder)));
}
bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index 3a4c26c..a9b28c3 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -18,10 +18,9 @@
#include <functional>
#include <string>
+#include "source/reduce/reduction_pass.h"
#include "spirv-tools/libspirv.hpp"
-#include "reduction_pass.h"
-
namespace spvtools {
namespace reduce {
@@ -33,7 +32,12 @@
enum ReductionResultStatus {
kInitialStateNotInteresting,
kReachedStepLimit,
- kComplete
+ kComplete,
+ kInitialStateInvalid,
+
+ // Returned when the fail-on-validation-error option is set and a
+ // reduction step yields a state that fails validation.
+ kStateInvalid,
};
// The type for a function that will take a binary and return true if and
@@ -75,16 +79,20 @@
void SetInterestingnessFunction(
InterestingnessFunction interestingness_function);
- // Adds a reduction pass to the sequence of passes that will be iterated
- // over.
- void AddReductionPass(std::unique_ptr<ReductionPass>&& reduction_pass);
+ // Adds all default reduction passes.
+ void AddDefaultReductionPasses();
+
+ // Adds a reduction pass based on the given finder to the sequence of passes
+ // that will be iterated over.
+ void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
// Reduces the given SPIR-V module |binary_out|.
// The reduced binary ends up in |binary_out|.
// A status is returned.
ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
std::vector<uint32_t>* binary_out,
- spv_const_reducer_options options) const;
+ spv_const_reducer_options options,
+ spv_validator_options validator_options) const;
private:
struct Impl; // Opaque struct for holding internal data.
diff --git a/source/reduce/reduction_opportunity.cpp b/source/reduce/reduction_opportunity.cpp
index f562678..77be784 100644
--- a/source/reduce/reduction_opportunity.cpp
+++ b/source/reduce/reduction_opportunity.cpp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "reduction_opportunity.h"
+#include "source/reduce/reduction_opportunity.h"
namespace spvtools {
namespace reduce {
diff --git a/source/reduce/reduction_opportunity.h b/source/reduce/reduction_opportunity.h
index da382a2..703a50a 100644
--- a/source/reduce/reduction_opportunity.h
+++ b/source/reduce/reduction_opportunity.h
@@ -20,23 +20,24 @@
namespace spvtools {
namespace reduce {
-// Abstract class capturing an opportunity to apply a reducing transformation.
+// Abstract class: an opportunity to apply a reducing transformation.
class ReductionOpportunity {
public:
ReductionOpportunity() = default;
virtual ~ReductionOpportunity() = default;
- // Determines whether the opportunity can be applied; it may have been viable
- // when discovered but later disabled by the application of some other
- // reduction opportunity.
+ // Returns true if this opportunity has not been disabled by the application
+ // of another conflicting opportunity.
virtual bool PreconditionHolds() = 0;
- // A no-op if PreconditoinHolds() returns false; otherwise applies the
- // opportunity.
+ // Applies the opportunity, mutating the module from which the opportunity was
+ // created. It is a no-op if PreconditionHolds() returns false.
void TryToApply();
protected:
- // Apply the reduction opportunity.
+ // Applies the opportunity, mutating the module from which the opportunity was
+ // created.
+ // Precondition: PreconditionHolds() must return true.
virtual void Apply() = 0;
};
diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h
new file mode 100644
index 0000000..1837484
--- /dev/null
+++ b/source/reduce/reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Abstract class for finding opportunities for reducing a SPIR-V module.
+class ReductionOpportunityFinder {
+ public:
+ ReductionOpportunityFinder() = default;
+
+ virtual ~ReductionOpportunityFinder() = default;
+
+ // Finds and returns the reduction opportunities relevant to this pass that
+ // could be applied to the given SPIR-V module.
+ virtual std::vector<std::unique_ptr<ReductionOpportunity>>
+ GetAvailableOpportunities(opt::IRContext* context) const = 0;
+
+ // Provides a name for the finder.
+ virtual std::string GetName() const = 0;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
index befba8b..2cb986d 100644
--- a/source/reduce/reduction_pass.cpp
+++ b/source/reduce/reduction_pass.cpp
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <algorithm>
+#include "source/reduce/reduction_pass.h"
-#include "reduction_pass.h"
+#include <algorithm>
#include "source/opt/build_module.h"
@@ -34,22 +34,21 @@
assert(context);
std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
- GetAvailableOpportunities(context.get());
+ finder_->GetAvailableOpportunities(context.get());
- if (!is_initialized_) {
- is_initialized_ = true;
- index_ = 0;
- granularity_ = (uint32_t)opportunities.size();
- }
-
- if (opportunities.empty()) {
- granularity_ = 1;
- return std::vector<uint32_t>();
+ // There is no point in having a granularity larger than the number of
+ // opportunities, so reduce the granularity in this case.
+ if (granularity_ > opportunities.size()) {
+ granularity_ = std::max((uint32_t)1, (uint32_t)opportunities.size());
}
assert(granularity_ > 0);
if (index_ >= opportunities.size()) {
+ // We have reached the end of the available opportunities and, therefore,
+ // the end of the round for this pass, so reset the index and decrease the
+ // granularity for the next round. Return an empty vector to signal the end
+ // of the round.
index_ = 0;
granularity_ = std::max((uint32_t)1, granularity_ / 2);
return std::vector<uint32_t>();
@@ -61,8 +60,6 @@
opportunities[i]->TryToApply();
}
- index_ += granularity_;
-
std::vector<uint32_t> result;
context->module()->ToBinary(&result, false);
return result;
@@ -73,14 +70,17 @@
}
bool ReductionPass::ReachedMinimumGranularity() const {
- if (!is_initialized_) {
- // Conceptually we can think that if the pass has not yet been initialized,
- // it is operating at unbounded granularity.
- return false;
- }
assert(granularity_ != 0);
return granularity_ == 1;
}
+std::string ReductionPass::GetName() const { return finder_->GetName(); }
+
+void ReductionPass::NotifyInteresting(bool interesting) {
+ if (!interesting) {
+ index_ += granularity_;
+ }
+}
+
} // namespace reduce
} // namespace spvtools
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
index afb95cc..f2d937b 100644
--- a/source/reduce/reduction_pass.h
+++ b/source/reduce/reduction_pass.h
@@ -15,10 +15,11 @@
#ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
#define SOURCE_REDUCE_REDUCTION_PASS_H_
-#include "spirv-tools/libspirv.hpp"
+#include <limits>
-#include "reduction_opportunity.h"
#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+#include "spirv-tools/libspirv.hpp"
namespace spvtools {
namespace reduce {
@@ -32,37 +33,44 @@
// again, until the minimum granularity is reached.
class ReductionPass {
public:
- // Constructs a reduction pass with a given target environment, |target_env|.
- // Initially the pass is uninitialized.
- explicit ReductionPass(const spv_target_env target_env)
- : target_env_(target_env), is_initialized_(false) {}
+ // Constructs a reduction pass with a given target environment, |target_env|,
+ // and a given finder of reduction opportunities, |finder|.
+ explicit ReductionPass(const spv_target_env target_env,
+ std::unique_ptr<ReductionOpportunityFinder> finder)
+ : target_env_(target_env),
+ finder_(std::move(finder)),
+ index_(0),
+ granularity_(std::numeric_limits<uint32_t>::max()) {}
- virtual ~ReductionPass() = default;
-
- // Apply the reduction pass to the given binary.
+ // Applies the reduction pass to the given binary by applying a "chunk" of
+ // reduction opportunities. Returns the new binary if a chunk was applied; in
+ // this case, before the next call the caller must invoke
+ // NotifyInteresting(...) to indicate whether the new binary is interesting.
+ // Returns an empty vector if there are no more chunks left to apply; in this
+ // case, the index will be reset and the granularity lowered for the next
+ // round.
std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
- // Set a consumer to which relevant messages will be directed.
+ // Notifies the reduction pass whether the binary returned from
+ // TryApplyReduction is interesting, so that the next call to
+ // TryApplyReduction will avoid applying the same chunk of opportunities.
+ void NotifyInteresting(bool interesting);
+
+ // Sets a consumer to which relevant messages will be directed.
void SetMessageConsumer(MessageConsumer consumer);
- // Determines whether the granularity with which reduction opportunities are
+ // Returns true if the granularity with which reduction opportunities are
// applied has reached a minimum.
bool ReachedMinimumGranularity() const;
- // Returns the name of the reduction pass (useful for monitoring reduction
- // progress).
- virtual std::string GetName() const = 0;
-
- protected:
- // Finds the reduction opportunities relevant to this pass that could be
- // applied to a given SPIR-V module.
- virtual std::vector<std::unique_ptr<ReductionOpportunity>>
- GetAvailableOpportunities(opt::IRContext* context) const = 0;
+ // Returns the name associated with this reduction pass (based on its
+ // associated finder).
+ std::string GetName() const;
private:
const spv_target_env target_env_;
+ const std::unique_ptr<ReductionOpportunityFinder> finder_;
MessageConsumer consumer_;
- bool is_initialized_;
uint32_t index_;
uint32_t granularity_;
};
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
new file mode 100644
index 0000000..2b2b7e6
--- /dev/null
+++ b/source/reduce/reduction_util.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/reduction_util.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+const uint32_t kTrueBranchOperandIndex = 1;
+const uint32_t kFalseBranchOperandIndex = 2;
+
+uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
+ for (auto& inst : context->module()->types_values()) {
+ if (inst.opcode() != SpvOpUndef) {
+ continue;
+ }
+ if (inst.type_id() == type_id) {
+ return inst.result_id();
+ }
+ }
+ // TODO(2182): this is adapted from MemPass::Type2Undef. In due course it
+ // would be good to factor out this duplication.
+ const uint32_t undef_id = context->TakeNextId();
+ std::unique_ptr<Instruction> undef_inst(
+ new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
+ assert(undef_id == undef_inst->result_id());
+ context->module()->AddGlobalValue(std::move(undef_inst));
+ return undef_id;
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
new file mode 100644
index 0000000..b8ffb6e
--- /dev/null
+++ b/source/reduce/reduction_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_UTIL_H_
+#define SOURCE_REDUCE_REDUCTION_UTIL_H_
+
+#include "spirv-tools/libspirv.hpp"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+extern const uint32_t kTrueBranchOperandIndex;
+extern const uint32_t kFalseBranchOperandIndex;
+
+// Returns an OpUndef id from the global value list that is of the given type,
+// adding one if it does not exist.
+uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REDUCTION_UTIL_H_
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..3ad7f72
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+
+RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
+ Function* function, BasicBlock* block)
+ : function_(function), block_(block) {
+ // precondition:
+ assert(block_->begin() != block_->end() &&
+ block_->begin()->context()->get_def_use_mgr()->NumUsers(
+ block_->id()) == 0 &&
+ "RemoveBlockReductionOpportunity block must have 0 references");
+}
+
+bool RemoveBlockReductionOpportunity::PreconditionHolds() {
+ // Removing other blocks cannot disable this opportunity.
+ return true;
+}
+
+void RemoveBlockReductionOpportunity::Apply() {
+ // We need an iterator pointing to the block, hence the loop.
+ for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+ if (bi->id() == block_->id()) {
+ bi->KillAllInsts(true);
+ bi.Erase();
+ // Block removal changes the function, but we don't use analyses, so no
+ // need to invalidate them.
+ return;
+ }
+ }
+
+ assert(false &&
+ "Unreachable: we should have found a block with the desired id.");
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
new file mode 100644
index 0000000..4b358ab
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced block.
+// See RemoveBlockReductionOpportunityFinder.
+class RemoveBlockReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Creates the opportunity to remove |block| in |function| in |context|.
+ RemoveBlockReductionOpportunity(opt::Function* function,
+ opt::BasicBlock* block);
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ opt::Function* function_;
+ opt::BasicBlock* block_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..a3f873f
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
+
+std::string RemoveBlockReductionOpportunityFinder::GetName() const {
+ return "RemoveBlockReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ // Consider every block in every function.
+ for (auto& function : *context->module()) {
+ for (auto bi = function.begin(); bi != function.end(); ++bi) {
+ if (IsBlockValidOpportunity(context, function, bi)) {
+ result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
+ &function, &*bi));
+ }
+ }
+ }
+ return result;
+}
+
+bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
+ IRContext* context, Function& function, Function::iterator& bi) {
+ assert(bi != function.end() && "Block iterator was out of bounds");
+
+ // Don't remove first block; we don't want to end up with no blocks.
+ if (bi == function.begin()) {
+ return false;
+ }
+
+ // Don't remove blocks with references.
+ if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+ return false;
+ }
+
+ // Don't remove blocks whose instructions have outside references.
+ if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool RemoveBlockReductionOpportunityFinder::
+ BlockInstructionsHaveNoOutsideReferences(IRContext* context,
+ const Function::iterator& bi) {
+ // Get all instructions in block.
+ std::unordered_set<uint32_t> instructions_in_block;
+ for (const Instruction& instruction : *bi) {
+ instructions_in_block.insert(instruction.unique_id());
+ }
+
+ // For each instruction...
+ for (const Instruction& instruction : *bi) {
+ // For each use of the instruction...
+ bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
+ &instruction, [&instructions_in_block](Instruction* user) -> bool {
+ // If the use is in this block, continue (return true). Otherwise, we
+ // found an outside use; return false (and stop).
+ return instructions_in_block.find(user->unique_id()) !=
+ instructions_in_block.end();
+ });
+
+ if (!no_uses_outside_block) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..83cd04b
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove a block. The optimizer can remove dead
+// code. However, the reducer needs to be able to remove at a fine-grained
+// level.
+class RemoveBlockReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ RemoveBlockReductionOpportunityFinder() = default;
+
+ ~RemoveBlockReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+ // Returns true if the block |bi| in function |function| is a valid
+ // opportunity according to various restrictions.
+ static bool IsBlockValidOpportunity(opt::IRContext* context,
+ opt::Function& function,
+ opt::Function::iterator& bi);
+
+ // Returns true if the instructions (definitions) in block |bi| have no
+ // references, except for references from inside the block itself.
+ static bool BlockInstructionsHaveNoOutsideReferences(
+ opt::IRContext* context, const opt::Function::iterator& bi);
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_function_reduction_opportunity.cpp b/source/reduce/remove_function_reduction_opportunity.cpp
new file mode 100644
index 0000000..ecad670
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+#include "source/opt/eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
+ // Removing one function cannot influence whether another function can be
+ // removed.
+ return true;
+}
+
+void RemoveFunctionReductionOpportunity::Apply() {
+ for (opt::Module::iterator function_it = context_->module()->begin();
+ function_it != context_->module()->end(); ++function_it) {
+ if (&*function_it == function_) {
+ opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
+ &function_it);
+ return;
+ }
+ }
+ assert(0 && "Function to be removed was not found.");
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity.h b/source/reduce/remove_function_reduction_opportunity.h
new file mode 100644
index 0000000..d8c57db
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced function.
+class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Creates an opportunity to remove |function| from the module represented by
+ // |context|.
+ RemoveFunctionReductionOpportunity(opt::IRContext* context,
+ opt::Function* function)
+ : context_(context), function_(function) {}
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ // The IR context for the module under analysis.
+ opt::IRContext* context_;
+
+ // The function that can be removed.
+ opt::Function* function_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..1edb973
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
+ opt::IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+ // Consider each function.
+ for (auto& function : *context->module()) {
+ if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
+ // If the function is referenced, ignore it.
+ continue;
+ }
+ result.push_back(
+ MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
+ }
+ return result;
+}
+
+std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
+ return "RemoveFunctionReductionOpportunityFinder";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7952a22
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove unreferenced functions.
+class RemoveFunctionReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ RemoveFunctionReductionOpportunityFinder() = default;
+
+ ~RemoveFunctionReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
index 7b7a74e..9ca093b 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.cpp
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "source/opt/ir_context.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/opt/ir_context.h"
namespace spvtools {
namespace reduce {
diff --git a/source/reduce/remove_instruction_reduction_opportunity.h b/source/reduce/remove_instruction_reduction_opportunity.h
index 471ff15..07bef50 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.h
+++ b/source/reduce/remove_instruction_reduction_opportunity.h
@@ -15,30 +15,27 @@
#ifndef SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
-#include "reduction_opportunity.h"
#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
-
-// Captures the opportunity to remove an instruction from the SPIR-V module.
+// An opportunity to remove an instruction from the SPIR-V module.
class RemoveInstructionReductionOpportunity : public ReductionOpportunity {
public:
// Constructs the opportunity to remove |inst|.
- explicit RemoveInstructionReductionOpportunity(Instruction* inst)
+ explicit RemoveInstructionReductionOpportunity(opt::Instruction* inst)
: inst_(inst) {}
- // This kind of opportunity can be unconditionally applied.
+ // Always returns true, as this opportunity can always be applied.
bool PreconditionHolds() override;
protected:
- // Remove the instruction.
void Apply() override;
private:
- Instruction* inst_;
+ opt::Instruction* inst_;
};
} // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
similarity index 72%
rename from source/reduce/remove_opname_instruction_reduction_pass.cpp
rename to source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
index bf99bc5..f687d71 100644
--- a/source/reduce/remove_opname_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "remove_opname_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+
#include "source/opcode.h"
#include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
+using opt::IRContext;
std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
- opt::IRContext* context) const {
+RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& inst : context->module()->debugs2()) {
@@ -36,8 +37,8 @@
return result;
}
-std::string RemoveOpNameInstructionReductionPass::GetName() const {
- return "RemoveOpNameInstructionReductionPass";
+std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
+ return "RemoveOpNameInstructionReductionOpportunityFinder";
}
} // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
new file mode 100644
index 0000000..8b9fd6f
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove OpName instructions. As well as making
+// the module smaller, removing an OpName instruction may create opportunities
+// for subsequently removing the instructions that create the ids to which the
+// OpName applies.
+class RemoveOpNameInstructionReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ RemoveOpNameInstructionReductionOpportunityFinder() = default;
+
+ ~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ private:
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
deleted file mode 100644
index d20b6e1..0000000
--- a/source/reduce/remove_opname_instruction_reduction_pass.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for removing OpName instructions. As well as making the
-// module smaller, removing an OpName instruction may create opportunities to
-// remove the instruction that create the id to which the OpName applies.
-class RemoveOpNameInstructionReductionPass : public ReductionPass {
- public:
- // Creates the reduction pass in the context of the given target environment
- // |target_env|
- explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
- : ReductionPass(target_env) {}
-
- ~RemoveOpNameInstructionReductionPass() override = default;
-
- // The name of this pass.
- std::string GetName() const final;
-
- protected:
- // Finds all opportunities for removing opName instructions in the
- // given module.
- std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
- opt::IRContext* context) const final;
-
- private:
-};
-
-} // namespace reduce
-} // namespace spvtools
-
-#endif // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity.cpp b/source/reduce/remove_selection_reduction_opportunity.cpp
new file mode 100644
index 0000000..96f0147
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.cpp
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveSelectionReductionOpportunity::PreconditionHolds() { return true; }
+
+void RemoveSelectionReductionOpportunity::Apply() {
+ auto merge_instruction = header_block_->GetMergeInst();
+ merge_instruction->context()->KillInst(merge_instruction);
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity.h b/source/reduce/remove_selection_reduction_opportunity.h
new file mode 100644
index 0000000..892618e
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity for removing a selection construct by simply removing the
+// OpSelectionMerge instruction; thus, the selection must have already been
+// simplified to a point where the instruction can be trivially removed.
+class RemoveSelectionReductionOpportunity : public ReductionOpportunity {
+ public:
+ // Constructs a reduction opportunity from the selection header |block| in
+ // |function|.
+ RemoveSelectionReductionOpportunity(opt::BasicBlock* header_block)
+ : header_block_(header_block) {}
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ // The header block of the selection.
+ opt::BasicBlock* header_block_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..45821e2
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+} // namespace
+
+std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
+ return "RemoveSelectionReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
+ // Get all loop merge and continue blocks so we can check for these later.
+ std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ if (auto merge_instruction = block.GetMergeInst()) {
+ if (merge_instruction->opcode() == SpvOpLoopMerge) {
+ uint32_t merge_block_id =
+ merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+ uint32_t continue_block_id =
+ merge_instruction->GetSingleWordOperand(kContinueNodeIndex);
+ merge_and_continue_blocks_from_loops.insert(merge_block_id);
+ merge_and_continue_blocks_from_loops.insert(continue_block_id);
+ }
+ }
+ }
+ }
+
+ // Return all selection headers where the OpSelectionMergeInstruction can be
+ // removed.
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+ for (auto& function : *context->module()) {
+ for (auto& block : function) {
+ if (auto merge_instruction = block.GetMergeInst()) {
+ if (merge_instruction->opcode() == SpvOpSelectionMerge) {
+ if (CanOpSelectionMergeBeRemoved(
+ context, block, merge_instruction,
+ merge_and_continue_blocks_from_loops)) {
+ result.push_back(
+ MakeUnique<RemoveSelectionReductionOpportunity>(&block));
+ }
+ }
+ }
+ }
+ }
+ return result;
+}
+
+bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
+ IRContext* context, const BasicBlock& header_block,
+ Instruction* merge_instruction,
+ std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
+ assert(header_block.GetMergeInst() == merge_instruction &&
+ "CanOpSelectionMergeBeRemoved(...): header block and merge "
+ "instruction mismatch");
+
+ // The OpSelectionMerge instruction is needed if either of the following are
+ // true.
+ //
+ // 1. The header block has at least two (unique) successors that are not
+ // merge or continue blocks of a loop.
+ //
+ // 2. The predecessors of the merge block are "using" the merge block to avoid
+ // divergence. In other words, there exists a predecessor of the merge block
+ // that has a successor that is not the merge block of this construct and not
+ // a merge or continue block of a loop.
+
+ // 1.
+ {
+ uint32_t divergent_successor_count = 0;
+
+ std::unordered_set<uint32_t> seen_successors;
+
+ header_block.ForEachSuccessorLabel(
+ [&seen_successors, &merge_and_continue_blocks_from_loops,
+ &divergent_successor_count](uint32_t successor) {
+ // Not already seen.
+ if (seen_successors.find(successor) == seen_successors.end()) {
+ seen_successors.insert(successor);
+ // Not a loop continue or merge.
+ if (merge_and_continue_blocks_from_loops.find(successor) ==
+ merge_and_continue_blocks_from_loops.end()) {
+ ++divergent_successor_count;
+ }
+ }
+ });
+
+ if (divergent_successor_count > 1) {
+ return false;
+ }
+ }
+
+ // 2.
+ {
+ uint32_t merge_block_id =
+ merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+ for (uint32_t predecessor_block_id :
+ context->cfg()->preds(merge_block_id)) {
+ const BasicBlock* predecessor_block =
+ context->cfg()->block(predecessor_block_id);
+ assert(predecessor_block);
+ bool found_divergent_successor = false;
+ predecessor_block->ForEachSuccessorLabel(
+ [&found_divergent_successor, merge_block_id,
+ &merge_and_continue_blocks_from_loops](uint32_t successor_id) {
+ // The successor is not the merge block, nor a loop merge or
+ // continue.
+ if (successor_id != merge_block_id &&
+ merge_and_continue_blocks_from_loops.find(successor_id) ==
+ merge_and_continue_blocks_from_loops.end()) {
+ found_divergent_successor = true;
+ }
+ });
+ if (found_divergent_successor) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..848122b
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities for removing a selection construct by simply
+// removing the OpSelectionMerge instruction; thus, the selections must have
+// already been simplified to a point where they can be trivially removed.
+class RemoveSelectionReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ RemoveSelectionReductionOpportunityFinder() = default;
+
+ ~RemoveSelectionReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+
+ // Returns true if the OpSelectionMerge instruction |merge_instruction| in
+ // block |header_block| can be removed.
+ static bool CanOpSelectionMergeBeRemoved(
+ opt::IRContext* context, const opt::BasicBlock& header_block,
+ opt::Instruction* merge_instruction,
+ std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops);
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
similarity index 79%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index bc4998d..8f32435 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "remove_unreferenced_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
#include "source/opcode.h"
#include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
+using opt::IRContext;
std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveUnreferencedInstructionReductionPass::GetAvailableOpportunities(
- opt::IRContext* context) const {
+RemoveUnreferencedInstructionReductionOpportunityFinder::
+ GetAvailableOpportunities(IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
for (auto& function : *context->module()) {
@@ -52,8 +53,9 @@
return result;
}
-std::string RemoveUnreferencedInstructionReductionPass::GetName() const {
- return "RemoveUnreferencedInstructionReductionPass";
+std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName()
+ const {
+ return "RemoveUnreferencedInstructionReductionOpportunityFinder";
}
} // namespace reduce
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
similarity index 60%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.h
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
index 27c3fc2..30f460b 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.h
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -12,35 +12,28 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
-#include "reduction_pass.h"
+#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
-// A reduction pass for removing non-control-flow instructions in blocks in
-// cases where the instruction's id is not referenced. As well as making the
-// module smaller, removing an instruction that referenced particular ids may
+// A finder for opportunities to remove non-control-flow instructions in blocks
+// in cases where the instruction's id is not referenced. As well as making the
+// module smaller, removing an instruction that references particular ids may
// create opportunities for subsequently removing the instructions that
// generated those ids.
-class RemoveUnreferencedInstructionReductionPass : public ReductionPass {
+class RemoveUnreferencedInstructionReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
public:
- // Creates the reduction pass in the context of the given target environment
- // |target_env|
- explicit RemoveUnreferencedInstructionReductionPass(
- const spv_target_env target_env)
- : ReductionPass(target_env) {}
+ RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
- ~RemoveUnreferencedInstructionReductionPass() override = default;
+ ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
- // The name of this pass.
std::string GetName() const final;
- protected:
- // Finds all opportunities for removing unreferenced instructions in the
- // given module.
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final;
@@ -50,4 +43,4 @@
} // namespace reduce
} // namespace spvtools
-#endif // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#endif // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..17a5c7e
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/reduce/reduction_util.h"
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
+ std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+ // Consider every function.
+ for (auto& function : *context->module()) {
+ // Consider every block in the function.
+ for (auto& block : function) {
+ // The terminator must be SpvOpBranchConditional.
+ Instruction* terminator = block.terminator();
+ if (terminator->opcode() != SpvOpBranchConditional) {
+ continue;
+ }
+ // It must not be a selection header, as these cannot be followed by
+ // OpBranch.
+ if (block.GetMergeInst() &&
+ block.GetMergeInst()->opcode() == SpvOpSelectionMerge) {
+ continue;
+ }
+ // The conditional branch must be simplified.
+ if (terminator->GetSingleWordInOperand(kTrueBranchOperandIndex) !=
+ terminator->GetSingleWordInOperand(kFalseBranchOperandIndex)) {
+ continue;
+ }
+
+ result.push_back(
+ MakeUnique<SimpleConditionalBranchToBranchReductionOpportunity>(
+ block.terminator()));
+ }
+ }
+ return result;
+}
+
+std::string SimpleConditionalBranchToBranchOpportunityFinder::GetName() const {
+ return "SimpleConditionalBranchToBranchOpportunityFinder";
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
new file mode 100644
index 0000000..10b9dce
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to change simple conditional branches (conditional
+// branches with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const override;
+
+ std::string GetName() const override;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..8968b96
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+SimpleConditionalBranchToBranchReductionOpportunity::
+ SimpleConditionalBranchToBranchReductionOpportunity(
+ Instruction* conditional_branch_instruction)
+ : conditional_branch_instruction_(conditional_branch_instruction) {}
+
+bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
+ // We find at most one opportunity per conditional branch and simplifying
+ // another branch cannot disable this opportunity.
+ return true;
+}
+
+void SimpleConditionalBranchToBranchReductionOpportunity::Apply() {
+ assert(conditional_branch_instruction_->opcode() == SpvOpBranchConditional &&
+ "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+ "a conditional branch");
+
+ assert(conditional_branch_instruction_->GetSingleWordInOperand(
+ kTrueBranchOperandIndex) ==
+ conditional_branch_instruction_->GetSingleWordInOperand(
+ kFalseBranchOperandIndex) &&
+ "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+ "simple");
+
+ // OpBranchConditional %condition %block_id %block_id ...
+ // ->
+ // OpBranch %block_id
+
+ conditional_branch_instruction_->SetOpcode(SpvOpBranch);
+ conditional_branch_instruction_->ReplaceOperands(
+ {{SPV_OPERAND_TYPE_ID,
+ {conditional_branch_instruction_->GetSingleWordInOperand(
+ kTrueBranchOperandIndex)}}});
+}
+
+} // namespace reduce
+} // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
new file mode 100644
index 0000000..eddb464
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to change simple conditional branches (conditional branches
+// with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchReductionOpportunity
+ : public ReductionOpportunity {
+ public:
+ // Constructs an opportunity to simplify |conditional_branch_instruction|.
+ explicit SimpleConditionalBranchToBranchReductionOpportunity(
+ opt::Instruction* conditional_branch_instruction);
+
+ bool PreconditionHolds() override;
+
+ protected:
+ void Apply() override;
+
+ private:
+ opt::Instruction* conditional_branch_instruction_;
+};
+
+} // namespace reduce
+} // namespace spvtools
+
+#endif // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 679cfc1..afc1298 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -12,16 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
+
#include "source/opt/aggressive_dead_code_elim_pass.h"
#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
namespace spvtools {
namespace reduce {
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+using opt::Operand;
+
namespace {
const uint32_t kMergeNodeIndex = 0;
-const uint32_t kContinueNodeIndex = 1;
} // namespace
bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
@@ -41,15 +47,11 @@
// (1) Redirect edges that point to the loop's continue target to their
// closest merge block.
- RedirectToClosestMergeBlock(
- loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
- kContinueNodeIndex));
+ RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
// (2) Redirect edges that point to the loop's merge block to their closest
// merge block (which might be that of an enclosing selection, for instance).
- RedirectToClosestMergeBlock(
- loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
- kMergeNodeIndex));
+ RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
// (3) Turn the loop construct header into a selection.
ChangeLoopToSelection();
@@ -125,12 +127,8 @@
// original_target_id must either be the merge target or continue construct
// for the loop being operated on.
- assert(original_target_id ==
- loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
- kMergeNodeIndex) ||
- original_target_id ==
- loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
- kContinueNodeIndex));
+ assert(original_target_id == loop_construct_header_->MergeBlockId() ||
+ original_target_id == loop_construct_header_->ContinueBlockId());
auto terminator = context_->cfg()->block(source_id)->terminator();
@@ -191,7 +189,7 @@
to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
// Add to the phi operand an (undef, from_id) pair to reflect the added
// edge.
- auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id());
+ auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
});
@@ -215,11 +213,11 @@
// the "else" branch be the merge block.
auto terminator = loop_construct_header_->terminator();
if (terminator->opcode() == SpvOpBranch) {
- analysis::Bool temp;
- const analysis::Bool* bool_type =
+ opt::analysis::Bool temp;
+ const opt::analysis::Bool* bool_type =
context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
auto const_mgr = context_->get_constant_mgr();
- auto true_const = const_mgr->GetConstant(bool_type, {true});
+ auto true_const = const_mgr->GetConstant(bool_type, {1});
auto true_const_result_id =
const_mgr->GetDefiningInstruction(true_const)->result_id();
auto original_branch_id = terminator->GetSingleWordOperand(0);
@@ -248,6 +246,10 @@
context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
Instruction* use,
uint32_t index) {
+ // Ignore uses outside of blocks, such as in OpDecorate.
+ if (context_->get_instr_block(use) == nullptr) {
+ return;
+ }
// If a use is not appropriately dominated by its definition,
// replace the use with an OpUndef, unless the definition is an
// access chain, in which case replace it with some (possibly fresh)
@@ -273,7 +275,8 @@
break;
}
} else {
- use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())});
+ use->SetOperand(index,
+ {FindOrCreateGlobalUndef(context_, def.type_id())});
}
}
});
@@ -296,26 +299,6 @@
->Dominates(def, use);
}
-uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef(
- uint32_t type_id) {
- for (auto& inst : context_->module()->types_values()) {
- if (inst.opcode() != SpvOpUndef) {
- continue;
- }
- if (inst.type_id() == type_id) {
- return inst.result_id();
- }
- }
- // TODO(2182): this is adapted from MemPass::Type2Undef. In due course it
- // would be good to factor out this duplication.
- const uint32_t undef_id = context_->TakeNextId();
- std::unique_ptr<Instruction> undef_inst(
- new Instruction(context_, SpvOpUndef, type_id, undef_id, {}));
- assert(undef_id == undef_inst->result_id());
- context_->module()->AddGlobalValue(std::move(undef_inst));
- return undef_id;
-}
-
uint32_t
StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
uint32_t pointer_type_id) {
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index b139016..f6c065b 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -15,36 +15,33 @@
#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
-#include <source/opt/def_use_manager.h>
-#include "reduction_opportunity.h"
+#include "source/opt/def_use_manager.h"
#include "source/opt/dominator_analysis.h"
#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
-
-// Captures an opportunity to replace a structured loop with a selection.
+// An opportunity to replace a structured loop with a selection.
class StructuredLoopToSelectionReductionOpportunity
: public ReductionOpportunity {
public:
// Constructs an opportunity from a loop header block and the function that
// encloses it.
explicit StructuredLoopToSelectionReductionOpportunity(
- IRContext* context, BasicBlock* loop_construct_header,
- Function* enclosing_function)
+ opt::IRContext* context, opt::BasicBlock* loop_construct_header,
+ opt::Function* enclosing_function)
: context_(context),
loop_construct_header_(loop_construct_header),
enclosing_function_(enclosing_function) {}
- // We require the loop header to be reachable. A structured loop might
+ // Returns true if the loop header is reachable. A structured loop might
// become unreachable as a result of turning another structured loop into
// a selection.
bool PreconditionHolds() override;
protected:
- // Perform the structured loop to selection transformation.
void Apply() override;
private:
@@ -68,11 +65,12 @@
// Removes any components of |to_block|'s phi instructions relating to
// |from_id|.
void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
- BasicBlock* to_block);
+ opt::BasicBlock* to_block);
// Adds components to |to_block|'s phi instructions to account for a new
// incoming edge from |from_id|.
- void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+ void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+ opt::BasicBlock* to_block);
// Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
// following branch instruction accordingly.
@@ -88,17 +86,10 @@
// 2) |def| is an OpVariable
// 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
// dominates b.
- bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+ bool DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+ opt::Instruction* use,
uint32_t use_index,
- BasicBlock& def_block);
-
- // Checks whether the global value list has an OpUndef of the given type,
- // adding one if not, and returns the id of such an OpUndef.
- //
- // TODO(2184): This will likely be used by other reduction passes, so should
- // be factored out in due course. Parts of the spirv-opt framework provide
- // similar functionality, so there may be a case for further refactoring.
- uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+ opt::BasicBlock& def_block);
// Checks whether the global value list has an OpVariable of the given pointer
// type, adding one if not, and returns the id of such an OpVariable.
@@ -114,9 +105,9 @@
// be factored out in due course.
uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
- IRContext* context_;
- BasicBlock* loop_construct_header_;
- Function* enclosing_function_;
+ opt::IRContext* context_;
+ opt::BasicBlock* loop_construct_header_;
+ opt::Function* enclosing_function_;
};
} // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
similarity index 73%
rename from source/reduce/structured_loop_to_selection_reduction_pass.cpp
rename to source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
index 768a2e8..085b267 100644
--- a/source/reduce/structured_loop_to_selection_reduction_pass.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -12,13 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "structured_loop_to_selection_reduction_pass.h"
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
-using namespace opt;
+using opt::IRContext;
namespace {
const uint32_t kMergeNodeIndex = 0;
@@ -26,17 +27,16 @@
} // namespace
std::vector<std::unique_ptr<ReductionOpportunity>>
-StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
- opt::IRContext* context) const {
+StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+ IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
std::set<uint32_t> merge_block_ids;
for (auto& function : *context->module()) {
for (auto& block : function) {
- auto merge_inst = block.GetMergeInst();
- if (merge_inst) {
- merge_block_ids.insert(
- merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+ auto merge_block_id = block.MergeBlockIdIfAny();
+ if (merge_block_id) {
+ merge_block_ids.insert(merge_block_id);
}
}
}
@@ -50,11 +50,19 @@
continue;
}
+ uint32_t continue_block_id =
+ loop_merge_inst->GetSingleWordOperand(kContinueNodeIndex);
+
// Check whether the loop construct's continue target is the merge block
// of some structured control flow construct. If it is, we cautiously do
// not consider applying a transformation.
- if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
- kContinueNodeIndex)) != merge_block_ids.end()) {
+ if (merge_block_ids.find(continue_block_id) != merge_block_ids.end()) {
+ continue;
+ }
+
+ // Check whether the loop header block is also the continue target. If it
+ // is, we cautiously do not consider applying a transformation.
+ if (block.id() == continue_block_id) {
continue;
}
@@ -87,8 +95,9 @@
return result;
}
-std::string StructuredLoopToSelectionReductionPass::GetName() const {
- return "StructuredLoopToSelectionReductionPass";
+std::string StructuredLoopToSelectionReductionOpportunityFinder::GetName()
+ const {
+ return "StructuredLoopToSelectionReductionOpportunityFinder";
}
} // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..d63d434
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to turn structured loops into selections,
+// generalizing from a human-writable language the idea of turning a loop:
+//
+// while (c) {
+// body;
+// }
+//
+// into:
+//
+// if (c) {
+// body;
+// }
+//
+// Applying such opportunities results in continue constructs of transformed
+// loops becoming unreachable, so that it may be possible to remove them
+// subsequently.
+class StructuredLoopToSelectionReductionOpportunityFinder
+ : public ReductionOpportunityFinder {
+ public:
+ StructuredLoopToSelectionReductionOpportunityFinder() = default;
+
+ ~StructuredLoopToSelectionReductionOpportunityFinder() override = default;
+
+ std::string GetName() const final;
+
+ std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+ opt::IRContext* context) const final;
+