diff options
author | David Neto <dneto@google.com> | 2020-09-28 10:29:14 -0400 |
---|---|---|
committer | David Neto <dneto@google.com> | 2020-09-28 10:29:14 -0400 |
commit | 59c2b3935ce57a20a9cc0cc5e0a2f5b0075ba9fe (patch) | |
tree | 9a4b3ff0c457ea62961e72c112586bfe347e10ff | |
parent | a38c7b3dd8657c6fc3fe0a19f1973a7f7813606b (diff) | |
parent | b27e039c68dd9eb959bb9249fcb2c9a54841474b (diff) | |
download | spirv-tools-ndk-release-r22.tar.gz |
Merge commit 'aosp v2020.5' into update-shadercndk-r22-beta1ndk-r22ndk-release-r22
Includes:
b27e039c Finalize SPIRV-Tools v2020.5
a5903a96 Update CHANGES
330c7254 spirv-fuzz: Support dead blocks in TransformationAddSynonym (#3832)
36185f8b spirv-fuzz: Move IRContext parameter into constructor (#3837)
0e7fe4d3 Add missing backticks around <result-id> (#3840)
d1bb98fd Validate SPIRV Version number when parsing binary header (#3834)
67525bde spirv-fuzz: Create synonym of int constant using a loop (#3790)
7cc4b4d2 Fix compiler error on macOS with XCode12 (#3836)
5a5b750a spirv-fuzz: Handle OpPhis in TransformationInlineFunction (#3833)
0a1fb588 Update CHANGES
125b6424 spirv-fuzz: Refactor fuzzer, replayer and shrinker (#3818)
60ce96e2 spirv-fuzz: Add pass recommendations (#3757)
2945963c spirv-fuzz: Consider all ids from dead blocks irrelevant (#3795)
50ae4c5f Fix header guard macros (#3811)
296e9c7b spirv-fuzz: Fix TransformationDuplicateRegionWithSelection (#3815)
937a757f spirv-val: Add DeviceIndex (#3812)
34ef0c3f Fix missed modification flagging (#3814)
748edbf8 spirv-fuzz: Use an irrelevant id for the unused components (#3810)
8d49fb2f spirv-fuzz: Improvements to random number generation (#3809)
7e28d809 Add buffer oob check to bindless instrumentation (#3800)
8fc50411 spirv-fuzz: Remove CanFindOrCreateZeroConstant (#3807)
e8ce4355 spirv-fuzz: Add bit instruction synonym transformation (#3775)
e7c84fed spirv-fuzz: Skip unreachable blocks (#3729)
f20b523c Fix build errors (#3804)
3131686d spirv-fuzz: Handle invalid ids in fact manager (#3742)
4c239bd8 spirv-fuzz: Support memory instructions MoveInstructionDown (#3700)
1e1c308d spirv-fuzz: Pass submanagers to other submanagers when necessary (#3796)
f62357e7 spirv-fuzz: Transformation to flatten conditional branch (#3667)
5df93005 spirv-val: Add BaseInstance, BaseVertex, DrawIndex, and ViewIndex (#3782)
286b3095 Properly mark IR changed if instruction folder creates more than one constant. (#3799)
726af6f7 Add missing file to BUILD.gn (#3798)
244e6c1b spirv-fuzz: Add TransformationDuplicateRegionWithSelection (#3773)
5dcb576b spirv-reduce: Support reducing a specific function (#3774)
de7d5798 spirv-reduce: Refactoring (#3793)
ed9863e4 Favour 'integrity' over 'coherence' as a replacement for 'sanity'. (#3619)
8743d385 spirv-fuzz: Fix header guards in transformations/fuzzer passes (#3784)
2de7d3af spirv-fuzz: Add SPIRV_FUZZ_PROTOC_COMMAND (#3789)
e589d0d5 Add missing include (#3788)
a715b1b4 Improve spirv-fuzz CMake code (#3781)
a187dd58 Allow SPV_KHR_8bit_storage extension. (#3780)
1ab52e54 spirv-opt: Add function to compute nesting depth of a block (#3771)
fd05605b spirv-fuzz: Transformation to convert OpSelect to conditional branch (#3681)
2c60d16a spirv-val: Add Vulkan VUID labels to BuiltIn (#3756)
c341f7a6 spirv-fuzz: Add support for BuiltIn decoration (#3736)
c278dada spirv-fuzz: Fix GetIdEquivalenceClasses (#3767)
78846840 spirv-fuzz: Replace id in OpPhi coming from a dead predecessor (#3744)
3daabd32 spirv-fuzz: Transformation to replace the use of an irrelevant id (#3697)
d7f078f2 spirv-fuzz: TransformationMutatePointer (#3737)
43a51860 spirv-fuzz: Compute interprocedural loop nesting depth of blocks (#3753)
8a0ebd40 Correctly replace debug lexical scope of instruction (#3718)
f428aa39 spirv-fuzz: Remove opaque pointer design pattern (#3755)
08291a3a spirv-fuzz: Create synonym via OpPhi and existing synonyms (#3701)
7e4948b2 Add LoopNestingDepth function to StructuredCFGAnalysis (#3754)
50cf38b8 spirv-fuzz: Do not make synonyms of void result ids (#3747)
bceab9fa Do not register DebugFunction for functions optimized away. (#3749)
e02f178a Handle DebugScope in compact-ids pass (#3724)
9e26ae04 spirv-fuzz: Overflow ids (#3734)
2205254c Fix DebugNoScope to not output InlinedAt operand. (#3748)
230f363e spirv-fuzz: Split the fact manager into multiple files (#3699)
5adc5ae6 spirv-fuzz: Add inline function transformation (#3517)
1341b58a spirv-fuzz: Fix MaybeGetZeroConstant (#3740)
12df3caf Fix SSA-rewrite to remove DebugDeclare for variables without loads (#3719)
3f8501de Add undef for inlined void function (#3720)
4dd12239 spirv-fuzz: Add words instead of logical operands (#3728)
b79773a3 CCP should mark IR changed if it created new constants. (#3732)
a711c594 spirv-fuzz: add FuzzerPassAddCompositeInserts (#3606)
582c276d spirv-fuzz: Support pointer types in FuzzerPassAddParameters (#3627)
3434cb0b Let ADCE pass check DebugScope (#3703)
ee7f0c88 spirv-opt: Implement opt::Function::HasEarlyReturn function (#3711)
e28436f2 spirv-fuzz: Check termination instructions when donating modules (#3710)
1023dd7a Fix -Wrange-loop-analysis warning (#3712)
82f4bf12 spirv-fuzz: Check header dominance when adding dead block (#3694)
b8de4f57 Allow DebugTypeTemplate for Type operand (#3702)
c20995ef spirv-fuzz: Improve code coverage of tests (#3686)
eade36db spirv-fuzz: Fuzzer pass to randomly apply loop preheaders (#3668)
72ea7bec spirv-fuzz: Support identical predecessors in TransformationPropagateInstructionUp (#3689)
b4c4da3e Improve non-semantic instruction handling in the optimizer (#3693)
948577c5 Fix the bug (#3680)
df859f77 spirv-fuzz: Check integer and float width capabilities (#3670)
2641d335 spirv-fuzz: consider additional access chain instructions (#3672)
5e592945 spirv-fuzz: Ignore specialization constants (#3664)
1435e427 Fix the bug (#3683)
be099cde spirv-fuzz: Fix width in FuzzerPassAddEquationInstructions (#3685)
f0ca96d1 Preserve debug info in dead-insert-elim pass (#3652)
0d629b90 Validate more OpenCL.DebugInfo.100 instructions (#3684)
13a65b1a Only validation locations for appropriate execution models (#3656)
fd3cabd8 spirv-fuzz: Fix in operand type assertion (#3666)
f5055386 spirv-opt: Add spvOpcodeIsAccessChain (#3682)
b7056e7e spirv-fuzz: FuzzerPassPropagateInstructionsUp (#3478)
8e138099 Handle no index access chain in local access chain convert (#3678)
bdeeae78 Roll 2 dependencies (#3677)
2990a219 Avoid using /MP4 for clang on windows. (#3662)
7b2dd11d spirv-fuzz: TransformationReplaceAddSubMulWithCarryingExtended (#3598)
6d7f34fb spirv-fuzz: Add TransformationMakeVectorOperationDynamic (#3597)
d29eac95 spirv-fuzz: iterate over blocks in replace linear algebra pass (#3654)
efc85ff6 spirv-fuzz: make outliner pass use additional transformations (#3604)
5fd92a7e OpenCL.DebugInfo.100 DebugTypeArray with variable size (#3549)
3f33a9aa spirv-opt: Improve the code of the Instruction class (#3610)
0419751b spirv-fuzz: Handle OpPhis in livesafe functions (#3642)
a10e7605 spirv-fuzz: Handle OpPhi during constant obfuscation (#3640)
28f32ca5 spirv-fuzz: Fix FuzzerPassCopyObjects (#3638)
8bc27a1c spirv-fuzz: Remove OpFunctionCall operands in correct order (#3630)
d9c73ebd spirv-fuzz: Handle capabilities during module donation (#3651)
9f222360 spirv-fuzz: Refactor boilerplate in TransformationAddParameter (#3625)
92a71657 spirv-fuzz: TransformationMoveInstructionDown (#3477)
b78f4b15 Remove DebugDeclare only for target variables in ssa-rewrite (#3511)
91cea06a Fix typo in ASAN CI build (#3623)
2aaa8653 spirv-fuzz: Transformation to add loop preheader (#3599)
96bcc827 spirv-fuzz: Pass to replace int operands with ints of opposite signedness (#3612)
ebaefda6 Debug info preservation in loop-unroll pass (#3548)
50300450 Validator support for non-semantic clspv reflection (#3618)
ab4fe12a spirv-fuzz: Fix memory bugs (#3622)
c6e6597c spirv-fuzz: Implement the OpOuterProduct linear algebra case (#3617)
054f034e spirv-fuzz: Compute corollary facts from OpBitcast (#3538)
a1ea15c9 Update some language usage. (#3611)
863b8e3d spirv-fuzz: Relax type constraints in DataSynonym facts (#3602)
7e75fea9 spirv-fuzz: Remove non-deterministic behaviour (#3608)
f9b088fe Avoid use of 'sanity' and 'sanity check' in the code base (#3585)
150be20d spirv-fuzz: Add condition to make functions livesafe (#3587)
ce16ccf3 Rolling 4 dependencies (#3601)
1dfc6fc7 spirv-fuzz: Implement the OpTranspose linear algebra case (#3589)
b63f0e5e Fix SyntaxWarning in Python 3.8 (#3388)
6aed7ffb CMake: Enable building with BUILD_SHARED_LIBS=1 (#3490)
31c82139 Avoid operand type range checks (#3379)
6a3eb679 Preserve debug info in scalar replacement pass (#3461)
2796840d Update OpenCL capabilities validation (#3149)
b25ee93c build(deps): bump lodash from 4.17.15 to 4.17.19 in /tools/sva (#3596)
8a550065 spirv-fuzz: adds TransformationReplaceLoadStoreWithCopyMemory (#3586)
7c901a49 Preserve OpenCL.DebugInfo.100 through private-to-local pass (#3571)
767518e8 spirv-fuzz: Relax type checking for int contants (#3573)
f8920bcf spirv-fuzz: Generalise transformation access chain (#3546)
98ac9fd6 spirv-fuzz: Split blocks starting with OpPhi before trying to outline (#3581)
059ab081 spirv-fuzz: Set message consumer in replayer when shrinking (#3591)
d6306537 spirv-fuzz: Don't use default parameters (#3583)
969f0286 Change DEPS rolling script to point at external/ (#3584)
1aaf5c61 spirv-fuzz: Create a helper in fuzzerutil to reuse function type (#3572)
89b3bc5a spirv-fuzz: Test usages of IdIsIrrelevant fact (#3578)
9dc1bfa3 spirv-fuzz: adds TransformationReplaceCopyMemoryWithLoadStore (#3575)
586a12b9 spirv-fuzz: adds TransformationReplaceCopyObjectWithStoreLoad (#3567)
0b84727f Start SPIRV-Tools v2020.5
Testing: checkbuild.py on Linux; unit tests on Windows
Change-Id: I64fbe29fd1e9b05d10ae358a25a207bbdebb715c
508 files changed, 45188 insertions, 7180 deletions
@@ -181,11 +181,10 @@ SPVTOOLS_OPT_SRC_FILES := \ # Locations of grammar files. # SPV_COREUNIFIED1_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/spirv.core.grammar.json -SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.glsl.std.450.grammar.json -SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.opencl.std.100.grammar.json -# TODO(dneto): I expect the DebugInfo grammar file to eventually migrate to SPIRV-Headers -SPV_DEBUGINFO_GRAMMAR=$(LOCAL_PATH)/source/extinst.debuginfo.grammar.json -SPV_CLDEBUGINFO100_GRAMMAR=$(LOCAL_PATH)/source/extinst.opencl.debuginfo.100.grammar.json +SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.glsl.std.450.grammar.json +SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.std.100.grammar.json +SPV_DEBUGINFO_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.debuginfo.grammar.json +SPV_CLDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json define gen_spvtools_grammar_tables $(call generate-file-dir,$(1)/core.insts-unified1.inc) @@ -252,9 +251,9 @@ define gen_spvtools_vendor_tables $(call generate-file-dir,$(1)/$(2).insts.inc) $(1)/$(2).insts.inc : \ $(LOCAL_PATH)/utils/generate_grammar_tables.py \ - $(LOCAL_PATH)/source/extinst.$(2).grammar.json + $(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.$(2).grammar.json @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ - --extinst-vendor-grammar=$(LOCAL_PATH)/source/extinst.$(2).grammar.json \ + --extinst-vendor-grammar=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.$(2).grammar.json \ --vendor-insts-output=$(1)/$(2).insts.inc \ --vendor-operand-kind-prefix=$(3) @echo "[$(TARGET_ARCH_ABI)] Vendor extended instruction set: $(2) tables <= grammar" @@ -267,6 +266,7 @@ $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-gcn-shader $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-ballot,"")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-explicit-vertex-parameter,"")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-trinary-minmax,"")) +$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),nonsemantic.clspvreflection,"")) define gen_spvtools_enum_string_mapping $(call generate-file-dir,$(1)/extension_enum.inc.inc) diff --git a/BUILD.bazel b/BUILD.bazel index 3046781f..52290cfb 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -59,6 +59,8 @@ generate_vendor_tables("debuginfo") generate_vendor_tables("opencl.debuginfo.100", "CLDEBUG100_") +generate_vendor_tables("nonsemantic.clspvreflection") + generate_extinst_lang_headers("DebugInfo", DEBUGINFO_GRAMMAR_JSON_FILE) generate_extinst_lang_headers("OpenCLDebugInfo100", CLDEBUGINFO100_GRAMMAR_JSON_FILE) @@ -103,6 +105,7 @@ cc_library( ":gen_opencl_tables_unified1", ":gen_registry_tables", ":gen_vendor_tables_debuginfo", + ":gen_vendor_tables_nonsemantic_clspvreflection", ":gen_vendor_tables_opencl_debuginfo_100", ":gen_vendor_tables_spv_amd_gcn_shader", ":gen_vendor_tables_spv_amd_shader_ballot", @@ -32,8 +32,8 @@ template("spvtools_core_tables") { "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json" core_insts_file = "${target_gen_dir}/core.insts-$version.inc" operand_kinds_file = "${target_gen_dir}/operand.kinds-$version.inc" - debuginfo_insts_file = "source/extinst.debuginfo.grammar.json" - cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.grammar.json" + debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" + cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" sources = [ core_json_file, @@ -69,8 +69,8 @@ template("spvtools_core_enums") { core_json_file = "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json" - debuginfo_insts_file = "source/extinst.debuginfo.grammar.json" - cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.grammar.json" + debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" + cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" extension_enum_file = "${target_gen_dir}/extension_enum.inc" extension_map_file = "${target_gen_dir}/enum_string_mapping.inc" @@ -110,8 +110,8 @@ template("spvtools_glsl_tables") { core_json_file = "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json" glsl_json_file = "${spirv_headers}/include/spirv/${version}/extinst.glsl.std.450.grammar.json" - debuginfo_insts_file = "source/extinst.debuginfo.grammar.json" - cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.grammar.json" + debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" + cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" glsl_insts_file = "${target_gen_dir}/glsl.std.450.insts.inc" @@ -150,8 +150,8 @@ template("spvtools_opencl_tables") { core_json_file = "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json" opencl_json_file = "${spirv_headers}/include/spirv/${version}/extinst.opencl.std.100.grammar.json" - debuginfo_insts_file = "source/extinst.debuginfo.grammar.json" - cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.grammar.json" + debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" + cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" opencl_insts_file = "${target_gen_dir}/opencl.std.insts.inc" @@ -210,7 +210,7 @@ template("spvtools_vendor_table") { script = "utils/generate_grammar_tables.py" name = invoker.name - extinst_vendor_grammar = "source/extinst.${name}.grammar.json" + extinst_vendor_grammar = "${spirv_headers}/include/spirv/unified1/extinst.${name}.grammar.json" extinst_file = "${target_gen_dir}/${name}.insts.inc" args = [ @@ -280,11 +280,11 @@ spvtools_opencl_tables("opencl1-0") { } spvtools_language_header("debuginfo") { name = "DebugInfo" - grammar_file = "source/extinst.debuginfo.grammar.json" + grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json" } spvtools_language_header("cldebuginfo100") { name = "OpenCLDebugInfo100" - grammar_file = "source/extinst.opencl.debuginfo.100.grammar.json" + grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" } spvtools_vendor_tables = [ @@ -294,6 +294,7 @@ spvtools_vendor_tables = [ ["spv-amd-shader-ballot", "...nil..."], ["debuginfo", "...nil..."], ["opencl.debuginfo.100", "CLDEBUG100_"], + ["nonsemantic.clspvreflection", "...nil..."], ] foreach(table_def, spvtools_vendor_tables) { @@ -773,6 +774,7 @@ static_library("spvtools_reduce") { "source/reduce/reducer.h", "source/reduce/reduction_opportunity.cpp", "source/reduce/reduction_opportunity.h", + "source/reduce/reduction_opportunity_finder.cpp", "source/reduce/reduction_opportunity_finder.h", "source/reduce/reduction_pass.cpp", "source/reduce/reduction_pass.h", @@ -1,5 +1,54 @@ Revision history for SPIRV-Tools +v2020.5 2020-09-22 + - General + - Enable building with BUILD_SHARED_LIBS=1 (#3490) + - Avoid using /MP4 for clang on windows. (#3662) + - Fix compiler error on macOS with XCode12. (#3836) + - Optimizer + - Preserve OpenCL.DebugInfo.100 through private-to-local pass (#3571) + - Preserve debug info in scalar replacement pass (#3461) + - Debug info preservation in loop-unroll pass (#3548) + - Preserve debug info in dead-insert-elim pass (#3652) + - Improve non-semantic instruction handling in the optimizer (#3693) + - Let ADCE pass check DebugScope (#3703) + - Add undef for inlined void function (#3720) + - Fix SSA-rewrite to remove DebugDeclare for variables without loads (#3719) + - Handle DebugScope in compact-ids pass (#3724) + - Add buffer oob check to bindless instrumentation (#3800) + - Validator + - Update OpenCL capabilities validation (#3149) + - Validator support for non-semantic clspv reflection (#3618) + - OpenCL.DebugInfo.100 DebugTypeArray with variable size (#3549) + - Only validation locations for appropriate execution models (#3656) + - Validate more OpenCL.DebugInfo.100 instructions (#3684) + - Allow DebugTypeTemplate for Type operand (#3702) + - spirv-val: Add Vulkan VUID labels to BuiltIn (#3756) + - Allow SPV_KHR_8bit_storage extension. (#3780) + - Validate SPIRV Version number when parsing binary header (#3834) + - Reduce + - Support reducing a specific function (#3774) + - Fuzz + - adds TransformationReplaceCopyObjectWithStoreLoad (#3567) + - adds TransformationReplaceCopyMemoryWithLoadStore (#3575) + - adds TransformationReplaceLoadStoreWithCopyMemory (#3586) + - Implement the OpOuterProduct linear algebra case (#3617) + - Pass to replace int operands with ints of opposite signedness (#3612) + - TransformationMoveInstructionDown (#3477) + - Add TransformationMakeVectorOperationDynamic (#3597) + - TransformationReplaceAddSubMulWithCarryingExtended (#3598) + - FuzzerPassPropagateInstructionsUp (#3478) + - add FuzzerPassAddCompositeInserts (#3606) + - Add inline function transformation (#3517) + - Transformation to replace the use of an irrelevant id (#3697) + - Add SPIRV_FUZZ_PROTOC_COMMAND (#3789) + - Add TransformationDuplicateRegionWithSelection (#3773) + - Transformation to flatten conditional branch (#3667) + - Handle OpPhis in TransformationInlineFunction (#3833) + - Create synonym of int constant using a loop (#3790) + - Support dead blocks in TransformationAddSynonym (#3832) + - Linker + v2020.4 2020-07-22 - General - Changed variable names to be more descriptive (#3433) @@ -36,7 +85,7 @@ v2020.4 2020-07-22 v2020.3 2020-05-27 - General - - Prevent Effcee install his things when build spirv-tools with testing enabled (#3256) + - Prevent Effcee from installing things when building spirv-tools with testing enabled (#3256) - Update acorn version (#3294) - If SPIRV-Headers is in our tree, include it as subproject (#3299) - allow cross compiling for Windows Store, UWP, etc. (#3330) @@ -108,7 +157,7 @@ v2020.1 2020-02-03 - Optimizer - Change default version for CreatInstBindlessCheckPass to 2 (#3096, #3119) - Better handling of OpLine on merge blocks (#3130) - - Use dummy switch instead of dummy loop in MergeReturn pass. (#3151) + - Use placeholder switch instead of placeholder loop in MergeReturn pass. (#3151) - Handle TimeAMD in AmdExtensionToKhrPass. (#3168) - Validator - Fix structured exit validation (#3141) @@ -435,7 +484,7 @@ v2018.6 2018-11-07 - Optimizer - Unrolling loops marked for unrolling in the legalization passes. - Improved the compile time of loop unrolling. - - Changee merge-return to create a dummy loop around the function. + - Changee merge-return to create a placeholder loop around the function. - Small improvement to merge-blocks to allow it to merge more often. - Enforce an upper bound for the ids, and add option to set it. - #1966: Report error if there are unreachable block before running merge return diff --git a/CMakeLists.txt b/CMakeLists.txt index 73248f23..30dde20a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,8 @@ if(SPIRV_BUILD_COMPRESSION) "Please remove SPIRV_BUILD_COMPRESSION from your build options.") endif(SPIRV_BUILD_COMPRESSION) +option(SPIRV_BUILD_FUZZER "Build spirv-fuzz" OFF) + option(SPIRV_WERROR "Enable error on warning" ON) if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC"))) set(COMPILER_IS_LIKE_GNU TRUE) @@ -3,10 +3,10 @@ use_relative_paths = True vars = { 'github': 'https://github.com', - 'effcee_revision': '5af957bbfc7da4e9f7aa8cac11379fa36dd79b84', - 'googletest_revision': '011959aafddcd30611003de96cfd8d7a7685c700', - 're2_revision': 'aecba11114cf1fac5497aeb844b6966106de3eb6', - 'spirv_headers_revision': 'ac638f1815425403e946d0ab78bac71d2bdbf3be', + 'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659', + 'googletest_revision': '3af06fe1664d30f98de1e78c53a7087e842a2547', + 're2_revision': 'ca11026a032ce2a3de4b3c389ee53d2bdc8794d6', + 'spirv_headers_revision': '3fdabd0da2932c276b25b9b4a988ba134eba1aa6', } deps = { @@ -349,10 +349,7 @@ option, like so: ```sh # In <spirv-dir> (the SPIRV-Tools repo root): -git clone https://github.com/protocolbuffers/protobuf external/protobuf -pushd external/protobuf -git checkout v3.7.1 -popd +git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf # In your build directory: cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON diff --git a/build_defs.bzl b/build_defs.bzl index 15b70c73..30af3bd6 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -39,8 +39,8 @@ TEST_COPTS = COMMON_COPTS + select({ ], }) -DEBUGINFO_GRAMMAR_JSON_FILE = "source/extinst.debuginfo.grammar.json" -CLDEBUGINFO100_GRAMMAR_JSON_FILE = "source/extinst.opencl.debuginfo.100.grammar.json" +DEBUGINFO_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_debuginfo_grammar_unified1" +CLDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_opencl_debuginfo_100_grammar_unified1" def generate_core_tables(version = None): if not version: @@ -146,7 +146,7 @@ def generate_vendor_tables(extension, operand_kind_prefix = ""): if not extension: fail("Must specify extension", "extension") extension_rule = extension.replace("-", "_").replace(".", "_") - grammars = ["source/extinst.{}.grammar.json".format(extension)] + grammars = ["@spirv_headers//:spirv_ext_inst_{}_grammar_unified1".format(extension_rule)] outs = ["{}.insts.inc".format(extension)] prefices = [operand_kind_prefix] fmtargs = grammars + outs + prefices diff --git a/docs/syntax.md b/docs/syntax.md index be3a5d5a..c135d010 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -166,9 +166,9 @@ with `!<integer>` in them: When a token in the assembly program is a `!<integer>`, that integer value is emitted into the binary output, and parsing proceeds differently than before: -each subsequent token not recognized as an OpCode or a <result-id> is emitted +each subsequent token not recognized as an OpCode or a `<result-id>` is emitted into the binary output without any checking; when a recognizable OpCode or a -<result-id> is eventually encountered, it begins a new instruction and parsing +`<result-id>` is eventually encountered, it begins a new instruction and parsing returns to normal. (If a subsequent OpCode is never found, then this alternate parsing mode handles all the remaining tokens in the program.) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 5b341598..179a4012 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -13,6 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Utility functions for pushing & popping variables. +function(push_variable var val) + set("${var}_SAVE_STACK" "${${var}}" "${${var}_SAVE_STACK}" PARENT_SCOPE) + set(${var} ${val} PARENT_SCOPE) +endfunction() +function(pop_variable var) + set(save_stack "${${var}_SAVE_STACK}") + list(GET save_stack 0 val) + list(REMOVE_AT save_stack 0) + set("${var}_SAVE_STACK" "${save_stack}" PARENT_SCOPE) + set(${var} ${val} PARENT_SCOPE) +endfunction() + if (DEFINED SPIRV-Headers_SOURCE_DIR) # This allows flexible position of the SPIRV-Headers repo. set(SPIRV_HEADER_DIR ${SPIRV-Headers_SOURCE_DIR}) @@ -61,7 +74,11 @@ if (NOT ${SPIRV_SKIP_TESTS}) "Use shared (DLL) run-time lib even when Google Test is built as static lib." ON) endif() + # gtest requires special defines for building as a shared + # library, simply always build as static. + push_variable(BUILD_SHARED_LIBS 0) add_subdirectory(${GMOCK_DIR} EXCLUDE_FROM_ALL) + pop_variable(BUILD_SHARED_LIBS) endif() endif() if (TARGET gmock) @@ -108,7 +125,9 @@ if (NOT ${SPIRV_SKIP_TESTS}) if (NOT TARGET effcee) set(EFFCEE_BUILD_TESTING OFF CACHE BOOL "Do not build Effcee test suite") endif() + push_variable(BUILD_SHARED_LIBS 0) # effcee does not export any symbols for building as a DLL. Always build as static. add_subdirectory(effcee EXCLUDE_FROM_ALL) + pop_variable(BUILD_SHARED_LIBS) set_property(TARGET effcee PROPERTY FOLDER Effcee) # Turn off warnings for effcee and re2 set_property(TARGET effcee APPEND PROPERTY COMPILE_OPTIONS -w) @@ -118,16 +137,43 @@ if (NOT ${SPIRV_SKIP_TESTS}) endif() if(SPIRV_BUILD_FUZZER) - set(PROTOBUF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake) - set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests") - set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime") - if (IS_DIRECTORY ${PROTOBUF_DIR}) + + function(backup_compile_options) + get_property( + SPIRV_TOOLS_BACKUP_EXTERNAL_COMPILE_OPTIONS + DIRECTORY + PROPERTY COMPILE_OPTIONS + ) + endfunction() + + function(restore_compile_options) + set_property( + DIRECTORY + PROPERTY COMPILE_OPTIONS + ${SPIRV_TOOLS_BACKUP_EXTERNAL_COMPILE_OPTIONS} + ) + endfunction() + + if(NOT TARGET protobuf::libprotobuf OR NOT TARGET protobuf::protoc) + + set(SPIRV_TOOLS_PROTOBUF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake) + if (NOT IS_DIRECTORY ${SPIRV_TOOLS_PROTOBUF_DIR}) + message( + FATAL_ERROR + "protobuf not found - please checkout a copy under external/.") + endif() + set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests") + set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime") + + backup_compile_options() + if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang) - add_definitions(-Wno-inconsistent-missing-override) + add_compile_options(-Wno-inconsistent-missing-override) endif() - add_subdirectory(${PROTOBUF_DIR} EXCLUDE_FROM_ALL) - else() - message(FATAL_ERROR - "protobuf not found - please checkout a copy under external/.") + + add_subdirectory(${SPIRV_TOOLS_PROTOBUF_DIR} EXCLUDE_FROM_ALL) + + restore_compile_options() + endif() -endif(SPIRV_BUILD_FUZZER) +endif() diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp index b4f33554..9c01cb69 100644 --- a/include/spirv-tools/instrument.hpp +++ b/include/spirv-tools/instrument.hpp @@ -24,6 +24,7 @@ // // CreateInstBindlessCheckPass // CreateInstBuffAddrCheckPass +// CreateInstDebugPrintfPass // // More detailed documentation of these routines can be found in optimizer.hpp @@ -33,7 +34,7 @@ namespace spvtools { // // The following values provide offsets into the output buffer struct // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized -// by InstBindlessCheckPass. +// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass. // // 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 @@ -138,12 +139,21 @@ static const int kInstValidationOutError = kInstStageOutCnt; // 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; +static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 3; +static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 4; -// A bindless uninitialized error will output the index. +// A descriptor uninitialized error will output the index. static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1; static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2; -static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3; +static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 3; +static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 4; + +// A buffer out-of-bounds error will output the descriptor +// index, the buffer offset and the buffer size +static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 1; +static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 2; +static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 3; +static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 4; // A buffer address unalloc error will output the 64-bit pointer in // two 32-bit pieces, lower bits first. @@ -152,7 +162,7 @@ static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2; static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3; // Maximum Output Record Member Count -static const int kInstMaxOutCnt = kInstStageOutCnt + 3; +static const int kInstMaxOutCnt = kInstStageOutCnt + 4; // Validation Error Codes // @@ -160,6 +170,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3; static const int kInstErrorBindlessBounds = 0; static const int kInstErrorBindlessUninit = 1; static const int kInstErrorBuffAddrUnallocRef = 2; +static const int kInstErrorBindlessBuffOOB = 3; // Direct Input Buffer Offsets // @@ -197,7 +208,10 @@ static const int kDebugOutputPrintfStream = 3; // 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: +// (set = s, binding = b, index = i) is not initialized; if the value is +// non-zero, and the descriptor points to a buffer, the value is the length of +// the buffer in bytes and can be used to check for out-of-bounds buffer +// references: // Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ] static const int kDebugInputBindlessInitOffset = 0; diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index 68afd649..16cdd1b1 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -48,8 +48,8 @@ extern "C" { #define SPV_BIT(shift) (1 << (shift)) -#define SPV_FORCE_16_BIT_ENUM(name) _##name = 0x7fff -#define SPV_FORCE_32_BIT_ENUM(name) _##name = 0x7fffffff +#define SPV_FORCE_16_BIT_ENUM(name) SPV_FORCE_16BIT_##name = 0x7fff +#define SPV_FORCE_32_BIT_ENUM(name) SPV_FORCE_32BIT_##name = 0x7fffffff // Enumerations @@ -189,8 +189,17 @@ typedef enum spv_operand_type_t { // Variable : expands to 0, 1 or many operands or pairs of operands. // This is similar to * in regular expressions. +// NOTE: These FIRST_* and LAST_* enum values are DEPRECATED. +// The concept of "optional" and "variable" operand types are only intended +// for use as an implementation detail of parsing SPIR-V, either in text or +// binary form. Instead of using enum ranges, use characteristic function +// spvOperandIsConcrete. +// The use of enum value ranges in a public API makes it difficult to insert +// new values into a range without also breaking binary compatibility. +// // Macros for defining bounds on optional and variable operand types. // Any variable operand type is also optional. +// TODO(dneto): Remove SPV_OPERAND_TYPE_FIRST_* and SPV_OPERAND_TYPE_LAST_* #define FIRST_OPTIONAL(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE = ENUM #define FIRST_VARIABLE(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE = ENUM #define LAST_VARIABLE(ENUM) \ @@ -257,6 +266,12 @@ typedef enum spv_operand_type_t { SPV_FORCE_32_BIT_ENUM(spv_operand_type_t) } spv_operand_type_t; +// Returns true if the given type is concrete. +bool spvOperandIsConcrete(spv_operand_type_t type); + +// Returns true if the given type is concrete and also a mask. +bool spvOperandIsConcreteMask(spv_operand_type_t type); + typedef enum spv_ext_inst_type_t { SPV_EXT_INST_TYPE_NONE = 0, SPV_EXT_INST_TYPE_GLSL_STD_450, @@ -267,6 +282,7 @@ typedef enum spv_ext_inst_type_t { SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT, SPV_EXT_INST_TYPE_DEBUGINFO, SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, + SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION, // Multiple distinct extended instruction set types could return this // value, if they are prefixed with NonSemantic. and are otherwise @@ -658,6 +674,13 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit( SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error); +// Sets the function that the reducer should target. If set to zero the reducer +// will target all functions as well as parts of the module that lie outside +// functions. Otherwise the reducer will restrict reduction to the function +// with result id |target_function|, which is required to exist. +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function); + // Creates a fuzzer options object with default options. Returns a valid // options object. The object remains valid until it is passed into // |spvFuzzerOptionsDestroy|. @@ -692,6 +715,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit( SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options); +// Enables all fuzzer passes during a fuzzing run (instead of a random subset +// of passes). +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options); + // Encodes the given SPIR-V assembly text to its binary representation. The // length parameter specifies the number of bytes for text. Encoded binary will // be stored into *binary. Any error will be written into *diagnostic if diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp index 6b31a07e..2018e461 100644 --- a/include/spirv-tools/libspirv.hpp +++ b/include/spirv-tools/libspirv.hpp @@ -202,6 +202,11 @@ class ReducerOptions { fail_on_validation_error); } + // See spvReducerOptionsSetTargetFunction. + void set_target_function(uint32_t target_function) { + spvReducerOptionsSetTargetFunction(options_, target_function); + } + private: spv_reducer_options options_; }; @@ -242,6 +247,9 @@ class FuzzerOptions { spvFuzzerOptionsEnableFuzzerPassValidation(options_); } + // See spvFuzzerOptionsEnableAllPasses. + void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); } + private: spv_fuzzer_options options_; }; diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 741f9476..d89d3b6f 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -764,7 +764,7 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // 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); + bool input_init_enable = false, bool input_buff_oob_enable = false); // Create a pass to instrument physical buffer address checking // This pass instruments all physical buffer address references to check that diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh index 8fb7bddd..606051f2 100644 --- a/kokoro/scripts/linux/build.sh +++ b/kokoro/scripts/linux/build.sh @@ -46,7 +46,7 @@ fi ADDITIONAL_CMAKE_FLAGS="" if [ $CONFIG = "ASAN" ] then - ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address,bounds,null" + ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=address,bounds,null" [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; } elif [ $CONFIG = "COVERAGE" ] then @@ -71,11 +71,7 @@ git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv git clone --depth=1 https://github.com/google/googletest external/googletest git clone --depth=1 https://github.com/google/effcee external/effcee git clone --depth=1 https://github.com/google/re2 external/re2 -git clone --depth=1 https://github.com/protocolbuffers/protobuf external/protobuf -pushd external/protobuf -git fetch --all --tags --prune -git checkout v3.7.1 -popd +git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf mkdir build && cd $SRC/build diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh index 5a3af43b..659ba53a 100644 --- a/kokoro/scripts/macos/build.sh +++ b/kokoro/scripts/macos/build.sh @@ -35,11 +35,7 @@ git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv git clone --depth=1 https://github.com/google/googletest external/googletest git clone --depth=1 https://github.com/google/effcee external/effcee git clone --depth=1 https://github.com/google/re2 external/re2 -git clone --depth=1 https://github.com/protocolbuffers/protobuf external/protobuf -pushd external/protobuf -git fetch --all --tags --prune -git checkout v3.7.1 -popd +git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf mkdir build && cd $SRC/build diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat index a4f2bf00..9adf525c 100644 --- a/kokoro/scripts/windows/build.bat +++ b/kokoro/scripts/windows/build.bat @@ -29,11 +29,7 @@ git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv git clone --depth=1 https://github.com/google/googletest external/googletest git clone --depth=1 https://github.com/google/effcee external/effcee git clone --depth=1 https://github.com/google/re2 external/re2 -git clone --depth=1 https://github.com/protocolbuffers/protobuf external/protobuf -pushd external\protobuf -git fetch --all --tags --prune -git checkout v3.7.1 -popd +git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf :: ######################################### :: set up msvc build env diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 708ca848..fa900e03 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -19,8 +19,8 @@ set(LANG_HEADER_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_lang # For now, assume the DebugInfo grammar file is in the current directory. # It might migrate to SPIRV-Headers. -set(DEBUGINFO_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.debuginfo.grammar.json") -set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.opencl.debuginfo.100.grammar.json") +set(DEBUGINFO_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.debuginfo.grammar.json") +set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json") # macro() definitions are used in the following because we need to append .inc # file paths into some global lists (*_CPP_DEPENDS). And those global lists are @@ -112,7 +112,7 @@ endmacro(spvtools_opencl_tables) macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME OPERAND_KIND_PREFIX) set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc") - set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json") + set(GRAMMAR_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.${VENDOR_TABLE}.grammar.json") add_custom_command(OUTPUT ${INSTS_FILE} COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} --extinst-vendor-grammar=${GRAMMAR_FILE} @@ -148,6 +148,7 @@ spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs" "") spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb" "") spvtools_vendor_tables("debuginfo" "debuginfo" "") spvtools_vendor_tables("opencl.debuginfo.100" "cldi100" "CLDEBUG100_") +spvtools_vendor_tables("nonsemantic.clspvreflection" "clspvreflection" "") spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE}) spvtools_extinst_lang_headers("OpenCLDebugInfo100" ${CLDEBUGINFO100_GRAMMAR_JSON_FILE}) @@ -345,18 +346,21 @@ set_source_files_properties( spvtools_pch(SPIRV_SOURCES pch_source) -add_library(${SPIRV_TOOLS} ${SPIRV_SOURCES}) -spvtools_default_compile_options(${SPIRV_TOOLS}) -target_include_directories(${SPIRV_TOOLS} +add_library(${SPIRV_TOOLS}-static STATIC ${SPIRV_SOURCES}) +spvtools_default_compile_options(${SPIRV_TOOLS}-static) +target_include_directories(${SPIRV_TOOLS}-static PUBLIC $<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> PRIVATE ${spirv-tools_BINARY_DIR} PRIVATE ${SPIRV_HEADER_INCLUDE_DIR} ) -set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries") -spvtools_check_symbol_exports(${SPIRV_TOOLS}) -add_dependencies( ${SPIRV_TOOLS} core_tables enum_string_mapping extinst_tables ) +set_property(TARGET ${SPIRV_TOOLS}-static PROPERTY FOLDER "SPIRV-Tools libraries") +spvtools_check_symbol_exports(${SPIRV_TOOLS}-static) +add_dependencies(${SPIRV_TOOLS}-static core_tables enum_string_mapping extinst_tables) + +# The static target does not have the '-static' suffix. +set_target_properties(${SPIRV_TOOLS}-static PROPERTIES OUTPUT_NAME "${SPIRV_TOOLS}") add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES}) spvtools_default_compile_options(${SPIRV_TOOLS}-shared) @@ -374,18 +378,26 @@ target_compile_definitions(${SPIRV_TOOLS}-shared PRIVATE SPIRV_TOOLS_IMPLEMENTATION PUBLIC SPIRV_TOOLS_SHAREDLIB ) -add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables ) +add_dependencies(${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables) + +# Create the "${SPIRV_TOOLS}" target as an alias to either "${SPIRV_TOOLS}-static" +# or "${SPIRV_TOOLS}-shared" depending on the value of BUILD_SHARED_LIBS. +if(BUILD_SHARED_LIBS) + add_library(${SPIRV_TOOLS} ALIAS ${SPIRV_TOOLS}-shared) +else() + add_library(${SPIRV_TOOLS} ALIAS ${SPIRV_TOOLS}-static) +endif() if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") find_library(LIBRT rt) if(LIBRT) - target_link_libraries(${SPIRV_TOOLS} ${LIBRT}) + target_link_libraries(${SPIRV_TOOLS}-static ${LIBRT}) target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT}) endif() endif() if(ENABLE_SPIRV_TOOLS_INSTALL) - install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared EXPORT ${SPIRV_TOOLS}Targets + install(TARGETS ${SPIRV_TOOLS}-static ${SPIRV_TOOLS}-shared EXPORT ${SPIRV_TOOLS}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) @@ -402,7 +414,7 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR}) endif(ENABLE_SPIRV_TOOLS_INSTALL) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() diff --git a/source/binary.cpp b/source/binary.cpp index f16bf522..75a997d3 100644 --- a/source/binary.cpp +++ b/source/binary.cpp @@ -45,6 +45,14 @@ spv_result_t spvBinaryHeaderGet(const spv_const_binary binary, // TODO: Validation checking? pHeader->magic = spvFixWord(binary->code[SPV_INDEX_MAGIC_NUMBER], endian); pHeader->version = spvFixWord(binary->code[SPV_INDEX_VERSION_NUMBER], endian); + // Per 2.3.1 version's high and low bytes are 0 + if ((pHeader->version & 0x000000ff) || pHeader->version & 0xff000000) + return SPV_ERROR_INVALID_BINARY; + // Minimum version was 1.0 and max version is defined by SPV_VERSION. + if (pHeader->version < SPV_SPIRV_VERSION_WORD(1, 0) || + pHeader->version > SPV_VERSION) + return SPV_ERROR_INVALID_BINARY; + pHeader->generator = spvFixWord(binary->code[SPV_INDEX_GENERATOR_NUMBER], endian); pHeader->bound = spvFixWord(binary->code[SPV_INDEX_BOUND], endian); diff --git a/source/cfa.h b/source/cfa.h index 97ef398d..17a4ea5b 100644 --- a/source/cfa.h +++ b/source/cfa.h @@ -127,7 +127,7 @@ class CFA { template <class BB> bool CFA<BB>::FindInWorkList(const std::vector<block_info>& work_list, uint32_t id) { - for (const auto b : work_list) { + for (auto& b : work_list) { if (b.block->id() == id) return true; } return false; diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp index e69c3c9b..3471ebe0 100644 --- a/source/ext_inst.cpp +++ b/source/ext_inst.cpp @@ -28,6 +28,7 @@ #include "debuginfo.insts.inc" #include "glsl.std.450.insts.inc" +#include "nonsemantic.clspvreflection.insts.inc" #include "opencl.debuginfo.100.insts.inc" #include "opencl.std.insts.inc" @@ -54,6 +55,9 @@ static const spv_ext_inst_group_t kGroups_1_0[] = { debuginfo_entries}, {SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, ARRAY_SIZE(opencl_debuginfo_100_entries), opencl_debuginfo_100_entries}, + {SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION, + ARRAY_SIZE(nonsemantic_clspvreflection_entries), + nonsemantic_clspvreflection_entries}, }; static const spv_ext_inst_table_t kTable_1_0 = {ARRAY_SIZE(kGroups_1_0), @@ -123,6 +127,9 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { if (!strcmp("OpenCL.DebugInfo.100", name)) { return SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100; } + if (!strncmp("NonSemantic.ClspvReflection.", name, 28)) { + return SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION; + } // ensure to add any known non-semantic extended instruction sets // above this point, and update spvExtInstIsNonSemantic() if (!strncmp("NonSemantic.", name, 12)) { @@ -132,7 +139,8 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { } bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) { - if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN) { + if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN || + type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) { return true; } return false; diff --git a/source/extinst.debuginfo.grammar.json b/source/extinst.debuginfo.grammar.json deleted file mode 100644 index 9212f6f4..00000000 --- a/source/extinst.debuginfo.grammar.json +++ /dev/null @@ -1,568 +0,0 @@ -{ - "copyright" : [ - "Copyright (c) 2017 The Khronos Group Inc.", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and/or associated documentation files (the \"Materials\"),", - "to deal in the Materials without restriction, including without limitation", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,", - "and/or sell copies of the Materials, and to permit persons to whom the", - "Materials are furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Materials.", - "", - "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS", - "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND", - "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ ", - "", - "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS", - "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING", - "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS", - "IN THE MATERIALS." - ], - "version" : 100, - "revision" : 1, - "instructions" : [ - { - "opname" : "DebugInfoNone", - "opcode" : 0 - }, - { - "opname" : "DebugCompilationUnit", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Version'" }, - { "kind" : "LiteralInteger", "name" : "'DWARF Version'" } - ] - }, - { - "opname" : "DebugTypeBasic", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugBaseTypeAttributeEncoding", "name" : "'Encoding'" } - ] - }, - { - "opname" : "DebugTypePointer", - "opcode" : 3, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "StorageClass", "name" : "'Storage Class'" }, - { "kind" : "DebugInfoFlags", "name" : "'Literal Flags'" } - ] - }, - { - "opname" : "DebugTypeQualifier", - "opcode" : 4, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "DebugTypeQualifier", "name" : "'Type Qualifier'" } - ] - }, - { - "opname" : "DebugTypeArray", - "opcode" : 5, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Component Counts'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeVector", - "opcode" : 6, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "LiteralInteger", "name" : "'Component Count'" } - ] - }, - { - "opname" : "DebugTypedef", - "opcode" : 7, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugTypeFunction", - "opcode" : 8, - "operands" : [ - { "kind" : "IdRef", "name" : "'Return Type'" }, - { "kind" : "IdRef", "name" : "'Paramter Types'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeEnum", - "opcode" : 9, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Underlying Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeComposite", - "opcode" : 10, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "DebugCompositeType", "name" : "'Tag'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Members'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeMember", - "opcode" : 11, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugTypeInheritance", - "opcode" : 12, - "operands" : [ - { "kind" : "IdRef", "name" : "'Child'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugTypePtrToMember", - "opcode" : 13, - "operands" : [ - { "kind" : "IdRef", "name" : "'Member Type'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugTypeTemplate", - "opcode" : 14, - "operands" : [ - { "kind" : "IdRef", "name" : "'Target'" }, - { "kind" : "IdRef", "name" : "'Parameters'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeTemplateParameter", - "opcode" : 15, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Actual Type'" }, - { "kind" : "IdRef", "name" : "'Value'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateTemplateParameter", - "opcode" : 16, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Template Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateParameterPack", - "opcode" : 17, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Template Parameters'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugGlobalVariable", - "opcode" : 18, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Static Member Declaration'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugFunctionDeclaration", - "opcode" : 19, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugFunction", - "opcode" : 20, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "LiteralInteger", "name" : "'Scope Line'" }, - { "kind" : "IdRef", "name" : "'Function'" }, - { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlock", - "opcode" : 21, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlockDiscriminator", - "opcode" : 22, - "operands" : [ - { "kind" : "IdRef", "name" : "'Scope'" }, - { "kind" : "LiteralInteger", "name" : "'Discriminator'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugScope", - "opcode" : 23, - "operands" : [ - { "kind" : "IdRef", "name" : "'Scope'" }, - { "kind" : "IdRef", "name" : "'Inlined At'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugNoScope", - "opcode" : 24 - }, - { - "opname" : "DebugInlinedAt", - "opcode" : 25, - "operands" : [ - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Scope'" }, - { "kind" : "IdRef", "name" : "'Inlined'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLocalVariable", - "opcode" : 26, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "LiteralInteger", "name" : "'Arg Number'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugInlinedVariable", - "opcode" : 27, - "operands" : [ - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "IdRef", "name" : "'Inlined'" } - ] - }, - { - "opname" : "DebugDeclare", - "opcode" : 28, - "operands" : [ - { "kind" : "IdRef", "name" : "'Local Variable'" }, - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "IdRef", "name" : "'Expression'" } - ] - }, - { - "opname" : "DebugValue", - "opcode" : 29, - "operands" : [ - { "kind" : "IdRef", "name" : "'Value'" }, - { "kind" : "IdRef", "name" : "'Expression'" }, - { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugOperation", - "opcode" : 30, - "operands" : [ - { "kind" : "DebugOperation", "name" : "'OpCode'" }, - { "kind" : "LiteralInteger", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugExpression", - "opcode" : 31, - "operands" : [ - { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugMacroDef", - "opcode" : 32, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugMacroUndef", - "opcode" : 33, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Macro'" } - ] - } - ], - "operand_kinds" : [ - { - "category" : "BitEnum", - "kind" : "DebugInfoFlags", - "enumerants" : [ - { - "enumerant" : "FlagIsProtected", - "value" : "0x01" - }, - { - "enumerant" : "FlagIsPrivate", - "value" : "0x02" - }, - { - "enumerant" : "FlagIsPublic", - "value" : "0x03" - }, - { - "enumerant" : "FlagIsLocal", - "value" : "0x04" - }, - { - "enumerant" : "FlagIsDefinition", - "value" : "0x08" - }, - { - "enumerant" : "FlagFwdDecl", - "value" : "0x10" - }, - { - "enumerant" : "FlagArtificial", - "value" : "0x20" - }, - { - "enumerant" : "FlagExplicit", - "value" : "0x40" - }, - { - "enumerant" : "FlagPrototyped", - "value" : "0x80" - }, - { - "enumerant" : "FlagObjectPointer", - "value" : "0x100" - }, - { - "enumerant" : "FlagStaticMember", - "value" : "0x200" - }, - { - "enumerant" : "FlagIndirectVariable", - "value" : "0x400" - }, - { - "enumerant" : "FlagLValueReference", - "value" : "0x800" - }, - { - "enumerant" : "FlagRValueReference", - "value" : "0x1000" - }, - { - "enumerant" : "FlagIsOptimized", - "value" : "0x2000" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugBaseTypeAttributeEncoding", - "enumerants" : [ - { - "enumerant" : "Unspecified", - "value" : "0" - }, - { - "enumerant" : "Address", - "value" : "1" - }, - { - "enumerant" : "Boolean", - "value" : "2" - }, - { - "enumerant" : "Float", - "value" : "4" - }, - { - "enumerant" : "Signed", - "value" : "5" - }, - { - "enumerant" : "SignedChar", - "value" : "6" - }, - { - "enumerant" : "Unsigned", - "value" : "7" - }, - { - "enumerant" : "UnsignedChar", - "value" : "8" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugCompositeType", - "enumerants" : [ - { - "enumerant" : "Class", - "value" : "0" - }, - { - "enumerant" : "Structure", - "value" : "1" - }, - { - "enumerant" : "Union", - "value" : "2" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugTypeQualifier", - "enumerants" : [ - { - "enumerant" : "ConstType", - "value" : "0" - }, - { - "enumerant" : "VolatileType", - "value" : "1" - }, - { - "enumerant" : "RestrictType", - "value" : "2" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugOperation", - "enumerants" : [ - { - "enumerant" : "Deref", - "value" : "0" - }, - { - "enumerant" : "Plus", - "value" : "1" - }, - { - "enumerant" : "Minus", - "value" : "2" - }, - { - "enumerant" : "PlusUconst", - "value" : "3", - "parameters" : [ - { "kind" : "LiteralInteger" } - ] - }, - { - "enumerant" : "BitPiece", - "value" : "4", - "parameters" : [ - { "kind" : "LiteralInteger" }, - { "kind" : "LiteralInteger" } - ] - }, - { - "enumerant" : "Swap", - "value" : "5" - }, - { - "enumerant" : "Xderef", - "value" : "6" - }, - { - "enumerant" : "StackValue", - "value" : "7" - }, - { - "enumerant" : "Constu", - "value" : "8", - "parameters" : [ - { "kind" : "LiteralInteger" } - ] - } - ] - } - ] -} diff --git a/source/extinst.opencl.debuginfo.100.grammar.json b/source/extinst.opencl.debuginfo.100.grammar.json deleted file mode 100644 index 08062be4..00000000 --- a/source/extinst.opencl.debuginfo.100.grammar.json +++ /dev/null @@ -1,632 +0,0 @@ -{ - "copyright" : [ - "Copyright (c) 2018 The Khronos Group Inc.", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and/or associated documentation files (the \"Materials\"),", - "to deal in the Materials without restriction, including without limitation", - "the rights to use, copy, modify, merge, publish, distribute, sublicense,", - "and/or sell copies of the Materials, and to permit persons to whom the", - "Materials are furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Materials.", - "", - "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS", - "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND", - "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ ", - "", - "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS", - "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL", - "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING", - "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS", - "IN THE MATERIALS." - ], - "version" : 200, - "revision" : 2, - "instructions" : [ - { - "opname" : "DebugInfoNone", - "opcode" : 0 - }, - { - "opname" : "DebugCompilationUnit", - "opcode" : 1, - "operands" : [ - { "kind" : "LiteralInteger", "name" : "'Version'" }, - { "kind" : "LiteralInteger", "name" : "'DWARF Version'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "SourceLanguage", "name" : "'Language'" } - ] - }, - { - "opname" : "DebugTypeBasic", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugBaseTypeAttributeEncoding", "name" : "'Encoding'" } - ] - }, - { - "opname" : "DebugTypePointer", - "opcode" : 3, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "StorageClass", "name" : "'Storage Class'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugTypeQualifier", - "opcode" : 4, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "DebugTypeQualifier", "name" : "'Type Qualifier'" } - ] - }, - { - "opname" : "DebugTypeArray", - "opcode" : 5, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Component Counts'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeVector", - "opcode" : 6, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "LiteralInteger", "name" : "'Component Count'" } - ] - }, - { - "opname" : "DebugTypedef", - "opcode" : 7, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugTypeFunction", - "opcode" : 8, - "operands" : [ - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Return Type'" }, - { "kind" : "IdRef", "name" : "'Parameter Types'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeEnum", - "opcode" : 9, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Underlying Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeComposite", - "opcode" : 10, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "DebugCompositeType", "name" : "'Tag'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Members'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeMember", - "opcode" : 11, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugTypeInheritance", - "opcode" : 12, - "operands" : [ - { "kind" : "IdRef", "name" : "'Child'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugTypePtrToMember", - "opcode" : 13, - "operands" : [ - { "kind" : "IdRef", "name" : "'Member Type'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugTypeTemplate", - "opcode" : 14, - "operands" : [ - { "kind" : "IdRef", "name" : "'Target'" }, - { "kind" : "IdRef", "name" : "'Parameters'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeTemplateParameter", - "opcode" : 15, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Actual Type'" }, - { "kind" : "IdRef", "name" : "'Value'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateTemplateParameter", - "opcode" : 16, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Template Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateParameterPack", - "opcode" : 17, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Template Parameters'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugGlobalVariable", - "opcode" : 18, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Static Member Declaration'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugFunctionDeclaration", - "opcode" : 19, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugFunction", - "opcode" : 20, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "LiteralInteger", "name" : "'Scope Line'" }, - { "kind" : "IdRef", "name" : "'Function'" }, - { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlock", - "opcode" : 21, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlockDiscriminator", - "opcode" : 22, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Discriminator'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugScope", - "opcode" : 23, - "operands" : [ - { "kind" : "IdRef", "name" : "'Scope'" }, - { "kind" : "IdRef", "name" : "'Inlined At'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugNoScope", - "opcode" : 24 - }, - { - "opname" : "DebugInlinedAt", - "opcode" : 25, - "operands" : [ - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Scope'" }, - { "kind" : "IdRef", "name" : "'Inlined'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLocalVariable", - "opcode" : 26, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, - { "kind" : "LiteralInteger", "name" : "'Arg Number'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugInlinedVariable", - "opcode" : 27, - "operands" : [ - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "IdRef", "name" : "'Inlined'" } - ] - }, - { - "opname" : "DebugDeclare", - "opcode" : 28, - "operands" : [ - { "kind" : "IdRef", "name" : "'Local Variable'" }, - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "IdRef", "name" : "'Expression'" } - ] - }, - { - "opname" : "DebugValue", - "opcode" : 29, - "operands" : [ - { "kind" : "IdRef", "name" : "'Local Variable'" }, - { "kind" : "IdRef", "name" : "'Value'" }, - { "kind" : "IdRef", "name" : "'Expression'" }, - { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugOperation", - "opcode" : 30, - "operands" : [ - { "kind" : "DebugOperation", "name" : "'OpCode'" }, - { "kind" : "LiteralInteger", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugExpression", - "opcode" : 31, - "operands" : [ - { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugMacroDef", - "opcode" : 32, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugMacroUndef", - "opcode" : 33, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Macro'" } - ] - }, - { - "opname" : "DebugImportedEntity", - "opcode" : 34, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "DebugImportedEntity", "name" : "'Tag'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Entity'" }, - { "kind" : "LiteralInteger", "name" : "'Line'" }, - { "kind" : "LiteralInteger", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugSource", - "opcode" : 35, - "operands" : [ - { "kind" : "IdRef", "name" : "'File'" }, - { "kind" : "IdRef", "name" : "'Text'", "quantifier" : "?" } - ] - } - ], - "operand_kinds" : [ - { - "category" : "BitEnum", - "kind" : "DebugInfoFlags", - "enumerants" : [ - { - "enumerant" : "FlagIsProtected", - "value" : "0x01" - }, - { - "enumerant" : "FlagIsPrivate", - "value" : "0x02" - }, - { - "enumerant" : "FlagIsPublic", - "value" : "0x03" - }, - { - "enumerant" : "FlagIsLocal", - "value" : "0x04" - }, - { - "enumerant" : "FlagIsDefinition", - "value" : "0x08" - }, - { - "enumerant" : "FlagFwdDecl", - "value" : "0x10" - }, - { - "enumerant" : "FlagArtificial", - "value" : "0x20" - }, - { - "enumerant" : "FlagExplicit", - "value" : "0x40" - }, - { - "enumerant" : "FlagPrototyped", - "value" : "0x80" - }, - { - "enumerant" : "FlagObjectPointer", - "value" : "0x100" - }, - { - "enumerant" : "FlagStaticMember", - "value" : "0x200" - }, - { - "enumerant" : "FlagIndirectVariable", - "value" : "0x400" - }, - { - "enumerant" : "FlagLValueReference", - "value" : "0x800" - }, - { - "enumerant" : "FlagRValueReference", - "value" : "0x1000" - }, - { - "enumerant" : "FlagIsOptimized", - "value" : "0x2000" - }, - { - "enumerant" : "FlagIsEnumClass", - "value" : "0x4000" - }, - { - "enumerant" : "FlagTypePassByValue", - "value" : "0x8000" - }, - { - "enumerant" : "FlagTypePassByReference", - "value" : "0x10000" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugBaseTypeAttributeEncoding", - "enumerants" : [ - { - "enumerant" : "Unspecified", - "value" : "0" - }, - { - "enumerant" : "Address", - "value" : "1" - }, - { - "enumerant" : "Boolean", - "value" : "2" - }, - { - "enumerant" : "Float", - "value" : "3" - }, - { - "enumerant" : "Signed", - "value" : "4" - }, - { - "enumerant" : "SignedChar", - "value" : "5" - }, - { - "enumerant" : "Unsigned", - "value" : "6" - }, - { - "enumerant" : "UnsignedChar", - "value" : "7" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugCompositeType", - "enumerants" : [ - { - "enumerant" : "Class", - "value" : "0" - }, - { - "enumerant" : "Structure", - "value" : "1" - }, - { - "enumerant" : "Union", - "value" : "2" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugTypeQualifier", - "enumerants" : [ - { - "enumerant" : "ConstType", - "value" : "0" - }, - { - "enumerant" : "VolatileType", - "value" : "1" - }, - { - "enumerant" : "RestrictType", - "value" : "2" - }, - { - "enumerant" : "AtomicType", - "value" : "3" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugOperation", - "enumerants" : [ - { - "enumerant" : "Deref", - "value" : "0" - }, - { - "enumerant" : "Plus", - "value" : "1" - }, - { - "enumerant" : "Minus", - "value" : "2" - }, - { - "enumerant" : "PlusUconst", - "value" : "3", - "parameters" : [ - { "kind" : "LiteralInteger" } - ] - }, - { - "enumerant" : "BitPiece", - "value" : "4", - "parameters" : [ - { "kind" : "LiteralInteger" }, - { "kind" : "LiteralInteger" } - ] - }, - { - "enumerant" : "Swap", - "value" : "5" - }, - { - "enumerant" : "Xderef", - "value" : "6" - }, - { - "enumerant" : "StackValue", - "value" : "7" - }, - { - "enumerant" : "Constu", - "value" : "8", - "parameters" : [ - { "kind" : "LiteralInteger" } - ] - }, - { - "enumerant" : "Fragment", - "value" : "9", - "parameters" : [ - { "kind" : "LiteralInteger" }, - { "kind" : "LiteralInteger" } - ] - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugImportedEntity", - "enumerants" : [ - { - "enumerant" : "ImportedModule", - "value" : "0" - }, - { - "enumerant" : "ImportedDeclaration", - "value" : "1" - } - ] - } - ] -} diff --git a/source/extinst.spv-amd-gcn-shader.grammar.json b/source/extinst.spv-amd-gcn-shader.grammar.json deleted file mode 100644 index e18251bb..00000000 --- a/source/extinst.spv-amd-gcn-shader.grammar.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "revision" : 2, - "instructions" : [ - { - "opname" : "CubeFaceIndexAMD", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'P'" } - ], - "extensions" : [ "SPV_AMD_gcn_shader" ] - }, - { - "opname" : "CubeFaceCoordAMD", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'P'" } - ], - "extensions" : [ "SPV_AMD_gcn_shader" ] - }, - { - "opname" : "TimeAMD", - "opcode" : 3, - "extensions" : [ "SPV_AMD_gcn_shader" ] - } - ] -} diff --git a/source/extinst.spv-amd-shader-ballot.grammar.json b/source/extinst.spv-amd-shader-ballot.grammar.json deleted file mode 100644 index 62a470ee..00000000 --- a/source/extinst.spv-amd-shader-ballot.grammar.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "revision" : 5, - "instructions" : [ - { - "opname" : "SwizzleInvocationsAMD", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'data'" }, - { "kind" : "IdRef", "name" : "'offset'" } - ], - "extensions" : [ "SPV_AMD_shader_ballot" ] - }, - { - "opname" : "SwizzleInvocationsMaskedAMD", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'data'" }, - { "kind" : "IdRef", "name" : "'mask'" } - ], - "extensions" : [ "SPV_AMD_shader_ballot" ] - }, - { - "opname" : "WriteInvocationAMD", - "opcode" : 3, - "operands" : [ - { "kind" : "IdRef", "name" : "'inputValue'" }, - { "kind" : "IdRef", "name" : "'writeValue'" }, - { "kind" : "IdRef", "name" : "'invocationIndex'" } - ], - "extensions" : [ "SPV_AMD_shader_ballot" ] - }, - { - "opname" : "MbcntAMD", - "opcode" : 4, - "operands" : [ - { "kind" : "IdRef", "name" : "'mask'" } - ], - "extensions" : [ "SPV_AMD_shader_ballot" ] - } - ] -} diff --git a/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json b/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json deleted file mode 100644 index e156b1b6..00000000 --- a/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "revision" : 4, - "instructions" : [ - { - "opname" : "InterpolateAtVertexAMD", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'interpolant'" }, - { "kind" : "IdRef", "name" : "'vertexIdx'" } - ], - "extensions" : [ "SPV_AMD_shader_explicit_vertex_parameter" ] - } - ] -} diff --git a/source/extinst.spv-amd-shader-trinary-minmax.grammar.json b/source/extinst.spv-amd-shader-trinary-minmax.grammar.json deleted file mode 100644 index c681976f..00000000 --- a/source/extinst.spv-amd-shader-trinary-minmax.grammar.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "revision" : 4, - "instructions" : [ - { - "opname" : "FMin3AMD", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "UMin3AMD", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "SMin3AMD", - "opcode" : 3, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "FMax3AMD", - "opcode" : 4, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "UMax3AMD", - "opcode" : 5, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "SMax3AMD", - "opcode" : 6, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "FMid3AMD", - "opcode" : 7, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "UMid3AMD", - "opcode" : 8, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - }, - { - "opname" : "SMid3AMD", - "opcode" : 9, - "operands" : [ - { "kind" : "IdRef", "name" : "'x'" }, - { "kind" : "IdRef", "name" : "'y'" }, - { "kind" : "IdRef", "name" : "'z'" } - ], - "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] - } - ] -} diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 66444daa..cea05cf3 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -18,9 +18,16 @@ if(SPIRV_BUILD_FUZZER) set(PROTOBUF_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/spvtoolsfuzz.proto) + set( + SPIRV_FUZZ_PROTOC_COMMAND + "protobuf::protoc" + CACHE + STRING + "The command to invoke the protobuf compiler (protoc). By default it is the protobufs::protoc CMake target. It should be overridden when cross-compiling, such as for Android.") + add_custom_command( OUTPUT protobufs/spvtoolsfuzz.pb.cc protobufs/spvtoolsfuzz.pb.h - COMMAND protobuf::protoc + COMMAND "${SPIRV_FUZZ_PROTOC_COMMAND}" -I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs --cpp_out=protobufs ${PROTOBUF_SOURCE} @@ -30,14 +37,23 @@ if(SPIRV_BUILD_FUZZER) set(SPIRV_TOOLS_FUZZ_SOURCES call_graph.h + comparator_deep_blocks_first.h + counter_overflow_id_source.h data_descriptor.h equivalence_relation.h - fact_manager.h + fact_manager/constant_uniform_facts.h + fact_manager/data_synonym_and_id_equation_facts.h + fact_manager/dead_block_facts.h + fact_manager/fact_manager.h + fact_manager/irrelevant_value_facts.h + fact_manager/livesafe_function_facts.h force_render_red.h fuzzer.h fuzzer_context.h fuzzer_pass.h fuzzer_pass_add_access_chains.h + fuzzer_pass_add_bit_instruction_synonyms.h + fuzzer_pass_add_composite_inserts.h fuzzer_pass_add_composite_types.h fuzzer_pass_add_copy_memory.h fuzzer_pass_add_dead_blocks.h @@ -47,13 +63,16 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_function_calls.h fuzzer_pass_add_global_variables.h fuzzer_pass_add_image_sample_unused_components.h - fuzzer_pass_add_synonyms.h fuzzer_pass_add_loads.h fuzzer_pass_add_local_variables.h + fuzzer_pass_add_loop_preheaders.h + fuzzer_pass_add_loops_to_create_int_constant_synonyms.h fuzzer_pass_add_no_contraction_decorations.h + fuzzer_pass_add_opphi_synonyms.h fuzzer_pass_add_parameters.h fuzzer_pass_add_relaxed_decorations.h fuzzer_pass_add_stores.h + fuzzer_pass_add_synonyms.h fuzzer_pass_add_vector_shuffle_instructions.h fuzzer_pass_adjust_branch_weights.h fuzzer_pass_adjust_function_controls.h @@ -64,16 +83,31 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.h fuzzer_pass_copy_objects.h fuzzer_pass_donate_modules.h + fuzzer_pass_duplicate_regions_with_selections.h + fuzzer_pass_flatten_conditional_branches.h + fuzzer_pass_inline_functions.h fuzzer_pass_invert_comparison_operators.h + fuzzer_pass_interchange_signedness_of_integer_operands.h fuzzer_pass_interchange_zero_like_constants.h + fuzzer_pass_make_vector_operations_dynamic.h fuzzer_pass_merge_blocks.h + fuzzer_pass_mutate_pointers.h fuzzer_pass_obfuscate_constants.h fuzzer_pass_outline_functions.h fuzzer_pass_permute_blocks.h fuzzer_pass_permute_function_parameters.h + fuzzer_pass_permute_instructions.h fuzzer_pass_permute_phi_operands.h + fuzzer_pass_propagate_instructions_up.h fuzzer_pass_push_ids_through_variables.h + fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h + fuzzer_pass_replace_copy_memories_with_loads_stores.h + fuzzer_pass_replace_copy_objects_with_stores_loads.h + fuzzer_pass_replace_irrelevant_ids.h fuzzer_pass_replace_linear_algebra_instructions.h + fuzzer_pass_replace_loads_stores_with_copy_memories.h + fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h + fuzzer_pass_replace_opselects_with_conditional_branches.h fuzzer_pass_replace_parameter_with_global.h fuzzer_pass_replace_params_with_struct.h fuzzer_pass_split_blocks.h @@ -84,6 +118,14 @@ if(SPIRV_BUILD_FUZZER) id_use_descriptor.h instruction_descriptor.h instruction_message.h + overflow_id_source.h + pass_management/repeated_pass_instances.h + pass_management/repeated_pass_manager.h + pass_management/repeated_pass_manager_looped_with_recommendations.h + pass_management/repeated_pass_manager_random_with_recommendations.h + pass_management/repeated_pass_manager_simple.h + pass_management/repeated_pass_recommender.h + pass_management/repeated_pass_recommender_standard.h protobufs/spirvfuzz_protobufs.h pseudo_random_generator.h random_generator.h @@ -91,6 +133,7 @@ if(SPIRV_BUILD_FUZZER) shrinker.h transformation.h transformation_access_chain.h + transformation_add_bit_instruction_synonym.h transformation_add_constant_boolean.h transformation_add_constant_composite.h transformation_add_constant_null.h @@ -104,7 +147,10 @@ if(SPIRV_BUILD_FUZZER) transformation_add_global_variable.h transformation_add_image_sample_unused_components.h transformation_add_local_variable.h + transformation_add_loop_preheader.h + transformation_add_loop_to_create_int_constant_synonym.h transformation_add_no_contraction_decoration.h + transformation_add_opphi_synonym.h transformation_add_parameter.h transformation_add_relaxed_decoration.h transformation_add_spec_constant_op.h @@ -121,23 +167,38 @@ if(SPIRV_BUILD_FUZZER) transformation_adjust_branch_weights.h transformation_composite_construct.h transformation_composite_extract.h + transformation_composite_insert.h transformation_compute_data_synonym_fact_closure.h transformation_context.h + transformation_duplicate_region_with_selection.h transformation_equation_instruction.h + transformation_flatten_conditional_branch.h transformation_function_call.h + transformation_inline_function.h transformation_invert_comparison_operator.h transformation_load.h + transformation_make_vector_operation_dynamic.h transformation_merge_blocks.h transformation_move_block_down.h + transformation_move_instruction_down.h + transformation_mutate_pointer.h transformation_outline_function.h transformation_permute_function_parameters.h transformation_permute_phi_operands.h + transformation_propagate_instruction_up.h transformation_push_id_through_variable.h transformation_record_synonymous_constants.h + transformation_replace_add_sub_mul_with_carrying_extended.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h + transformation_replace_copy_memory_with_load_store.h + transformation_replace_copy_object_with_store_load.h transformation_replace_id_with_synonym.h + transformation_replace_irrelevant_id.h transformation_replace_linear_algebra_instruction.h + transformation_replace_load_store_with_copy_memory.h + transformation_replace_opphi_id_from_dead_predecessor.h + transformation_replace_opselect_with_conditional_branch.h transformation_replace_parameter_with_global.h transformation_replace_params_with_struct.h transformation_set_function_control.h @@ -154,13 +215,21 @@ if(SPIRV_BUILD_FUZZER) ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h call_graph.cpp + counter_overflow_id_source.cpp data_descriptor.cpp - fact_manager.cpp + fact_manager/constant_uniform_facts.cpp + fact_manager/data_synonym_and_id_equation_facts.cpp + fact_manager/dead_block_facts.cpp + fact_manager/fact_manager.cpp + fact_manager/irrelevant_value_facts.cpp + fact_manager/livesafe_function_facts.cpp force_render_red.cpp fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp fuzzer_pass_add_access_chains.cpp + fuzzer_pass_add_bit_instruction_synonyms.cpp + fuzzer_pass_add_composite_inserts.cpp fuzzer_pass_add_composite_types.cpp fuzzer_pass_add_copy_memory.cpp fuzzer_pass_add_dead_blocks.cpp @@ -170,13 +239,16 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_add_function_calls.cpp fuzzer_pass_add_global_variables.cpp fuzzer_pass_add_image_sample_unused_components.cpp - fuzzer_pass_add_synonyms.cpp fuzzer_pass_add_loads.cpp fuzzer_pass_add_local_variables.cpp + fuzzer_pass_add_loop_preheaders.cpp + fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp fuzzer_pass_add_no_contraction_decorations.cpp + fuzzer_pass_add_opphi_synonyms.cpp fuzzer_pass_add_parameters.cpp fuzzer_pass_add_relaxed_decorations.cpp fuzzer_pass_add_stores.cpp + fuzzer_pass_add_synonyms.cpp fuzzer_pass_add_vector_shuffle_instructions.cpp fuzzer_pass_adjust_branch_weights.cpp fuzzer_pass_adjust_function_controls.cpp @@ -187,16 +259,31 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.cpp fuzzer_pass_copy_objects.cpp fuzzer_pass_donate_modules.cpp + fuzzer_pass_duplicate_regions_with_selections.cpp + fuzzer_pass_flatten_conditional_branches.cpp + fuzzer_pass_inline_functions.cpp fuzzer_pass_invert_comparison_operators.cpp + fuzzer_pass_interchange_signedness_of_integer_operands.cpp fuzzer_pass_interchange_zero_like_constants.cpp + fuzzer_pass_make_vector_operations_dynamic.cpp fuzzer_pass_merge_blocks.cpp + fuzzer_pass_mutate_pointers.cpp fuzzer_pass_obfuscate_constants.cpp fuzzer_pass_outline_functions.cpp fuzzer_pass_permute_blocks.cpp fuzzer_pass_permute_function_parameters.cpp + fuzzer_pass_permute_instructions.cpp fuzzer_pass_permute_phi_operands.cpp + fuzzer_pass_propagate_instructions_up.cpp fuzzer_pass_push_ids_through_variables.cpp + fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp + fuzzer_pass_replace_copy_memories_with_loads_stores.cpp + fuzzer_pass_replace_copy_objects_with_stores_loads.cpp + fuzzer_pass_replace_irrelevant_ids.cpp fuzzer_pass_replace_linear_algebra_instructions.cpp + fuzzer_pass_replace_loads_stores_with_copy_memories.cpp + fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp + fuzzer_pass_replace_opselects_with_conditional_branches.cpp fuzzer_pass_replace_parameter_with_global.cpp fuzzer_pass_replace_params_with_struct.cpp fuzzer_pass_split_blocks.cpp @@ -207,12 +294,20 @@ if(SPIRV_BUILD_FUZZER) id_use_descriptor.cpp instruction_descriptor.cpp instruction_message.cpp + overflow_id_source.cpp + pass_management/repeated_pass_manager.cpp + pass_management/repeated_pass_manager_looped_with_recommendations.cpp + pass_management/repeated_pass_manager_random_with_recommendations.cpp + pass_management/repeated_pass_manager_simple.cpp + pass_management/repeated_pass_recommender.cpp + pass_management/repeated_pass_recommender_standard.cpp pseudo_random_generator.cpp random_generator.cpp replayer.cpp shrinker.cpp transformation.cpp transformation_access_chain.cpp + transformation_add_bit_instruction_synonym.cpp transformation_add_constant_boolean.cpp transformation_add_constant_composite.cpp transformation_add_constant_null.cpp @@ -226,7 +321,10 @@ if(SPIRV_BUILD_FUZZER) transformation_add_global_variable.cpp transformation_add_image_sample_unused_components.cpp transformation_add_local_variable.cpp + transformation_add_loop_preheader.cpp + transformation_add_loop_to_create_int_constant_synonym.cpp transformation_add_no_contraction_decoration.cpp + transformation_add_opphi_synonym.cpp transformation_add_parameter.cpp transformation_add_relaxed_decoration.cpp transformation_add_spec_constant_op.cpp @@ -243,23 +341,38 @@ if(SPIRV_BUILD_FUZZER) transformation_adjust_branch_weights.cpp transformation_composite_construct.cpp transformation_composite_extract.cpp + transformation_composite_insert.cpp transformation_compute_data_synonym_fact_closure.cpp transformation_context.cpp + transformation_duplicate_region_with_selection.cpp transformation_equation_instruction.cpp + transformation_flatten_conditional_branch.cpp transformation_function_call.cpp + transformation_inline_function.cpp transformation_invert_comparison_operator.cpp transformation_load.cpp + transformation_make_vector_operation_dynamic.cpp transformation_merge_blocks.cpp transformation_move_block_down.cpp + transformation_move_instruction_down.cpp + transformation_mutate_pointer.cpp transformation_outline_function.cpp transformation_permute_function_parameters.cpp transformation_permute_phi_operands.cpp + transformation_propagate_instruction_up.cpp transformation_push_id_through_variable.cpp transformation_record_synonymous_constants.cpp + transformation_replace_add_sub_mul_with_carrying_extended.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp + transformation_replace_copy_memory_with_load_store.cpp + transformation_replace_copy_object_with_store_load.cpp transformation_replace_id_with_synonym.cpp + transformation_replace_irrelevant_id.cpp transformation_replace_linear_algebra_instruction.cpp + transformation_replace_load_store_with_copy_memory.cpp + transformation_replace_opphi_id_from_dead_predecessor.cpp + transformation_replace_opselect_with_conditional_branch.cpp transformation_replace_parameter_with_global.cpp transformation_replace_params_with_struct.cpp transformation_set_function_control.cpp @@ -276,7 +389,7 @@ if(SPIRV_BUILD_FUZZER) ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc ) - if(MSVC) + if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() @@ -286,7 +399,6 @@ if(SPIRV_BUILD_FUZZER) add_library(SPIRV-Tools-fuzz ${SPIRV_TOOLS_FUZZ_SOURCES}) spvtools_default_compile_options(SPIRV-Tools-fuzz) - target_compile_definitions(SPIRV-Tools-fuzz PUBLIC -DGOOGLE_PROTOBUF_NO_RTTI -DGOOGLE_PROTOBUF_USE_UNALIGNED=0) # Compilation of the auto-generated protobuf source file will yield warnings, # which we have no control over and thus wish to ignore. @@ -307,7 +419,7 @@ if(SPIRV_BUILD_FUZZER) # The fuzzer reuses a lot of functionality from the SPIRV-Tools library. target_link_libraries(SPIRV-Tools-fuzz - PUBLIC ${SPIRV_TOOLS} + PUBLIC ${SPIRV_TOOLS}-static PUBLIC SPIRV-Tools-opt PUBLIC protobuf::libprotobuf) diff --git a/source/fuzz/call_graph.cpp b/source/fuzz/call_graph.cpp index 15416fe3..c52bc342 100644 --- a/source/fuzz/call_graph.cpp +++ b/source/fuzz/call_graph.cpp @@ -20,12 +20,32 @@ namespace spvtools { namespace fuzz { CallGraph::CallGraph(opt::IRContext* context) { - // Initialize function in-degree and call graph edges to 0 and empty. + // Initialize function in-degree, call graph edges and corresponding maximum + // loop nesting depth to 0, empty and 0 respectively. for (auto& function : *context->module()) { function_in_degree_[function.result_id()] = 0; call_graph_edges_[function.result_id()] = std::set<uint32_t>(); + function_max_loop_nesting_depth_[function.result_id()] = 0; } + // Record the maximum loop nesting depth for each edge, by keeping a map from + // pairs of function ids, where (A, B) represents a function call from A to B, + // to the corresponding maximum depth. + std::map<std::pair<uint32_t, uint32_t>, uint32_t> call_to_max_depth; + + // Compute |function_in_degree_|, |call_graph_edges_| and |call_to_max_depth|. + BuildGraphAndGetDepthOfFunctionCalls(context, &call_to_max_depth); + + // Compute |functions_in_topological_order_|. + ComputeTopologicalOrderOfFunctions(); + + // Compute |function_max_loop_nesting_depth_|. + ComputeInterproceduralFunctionCallDepths(call_to_max_depth); +} + +void CallGraph::BuildGraphAndGetDepthOfFunctionCalls( + opt::IRContext* context, + std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth) { // Consider every function. for (auto& function : *context->module()) { // Avoid considering the same callee of this function multiple times by @@ -39,6 +59,25 @@ CallGraph::CallGraph(opt::IRContext* context) { } // Get the id of the function being called. uint32_t callee = instruction.GetSingleWordInOperand(0); + + // Get the loop nesting depth of this function call. + uint32_t loop_nesting_depth = + context->GetStructuredCFGAnalysis()->LoopNestingDepth(block.id()); + // If inside a loop header, consider the function call nested inside the + // loop headed by the block. + if (block.IsLoopHeader()) { + loop_nesting_depth++; + } + + // Update the map if we have not seen this pair (caller, callee) + // before or if this function call is from a greater depth. + if (!known_callees.count(callee) || + call_to_max_depth->at({function.result_id(), callee}) < + loop_nesting_depth) { + call_to_max_depth->insert( + {{function.result_id(), callee}, loop_nesting_depth}); + } + if (known_callees.count(callee)) { // We have already considered a call to this function - ignore it. continue; @@ -53,6 +92,69 @@ CallGraph::CallGraph(opt::IRContext* context) { } } +void CallGraph::ComputeTopologicalOrderOfFunctions() { + // This is an implementation of Kahn’s algorithm for topological sorting. + + // Initialise |functions_in_topological_order_|. + functions_in_topological_order_.clear(); + + // Get a copy of the initial in-degrees of all functions. The algorithm + // involves decrementing these values, hence why we work on a copy. + std::map<uint32_t, uint32_t> function_in_degree = GetFunctionInDegree(); + + // Populate a queue with all those function ids with in-degree zero. + std::queue<uint32_t> queue; + for (auto& entry : function_in_degree) { + if (entry.second == 0) { + queue.push(entry.first); + } + } + + // Pop ids from the queue, adding them to the sorted order and decreasing the + // in-degrees of their successors. A successor who's in-degree becomes zero + // gets added to the queue. + while (!queue.empty()) { + auto next = queue.front(); + queue.pop(); + functions_in_topological_order_.push_back(next); + for (auto successor : GetDirectCallees(next)) { + assert(function_in_degree.at(successor) > 0 && + "The in-degree cannot be zero if the function is a successor."); + function_in_degree[successor] = function_in_degree.at(successor) - 1; + if (function_in_degree.at(successor) == 0) { + queue.push(successor); + } + } + } + + assert(functions_in_topological_order_.size() == function_in_degree.size() && + "Every function should appear in the sort."); + + return; +} + +void CallGraph::ComputeInterproceduralFunctionCallDepths( + const std::map<std::pair<uint32_t, uint32_t>, uint32_t>& + call_to_max_depth) { + // Find the maximum loop nesting depth that each function can be + // called from, by considering them in topological order. + for (uint32_t function_id : functions_in_topological_order_) { + const auto& callees = call_graph_edges_[function_id]; + + // For each callee, update its maximum loop nesting depth, if a call from + // |function_id| increases it. + for (uint32_t callee : callees) { + uint32_t max_depth_from_this_function = + function_max_loop_nesting_depth_[function_id] + + call_to_max_depth.at({function_id, callee}); + if (function_max_loop_nesting_depth_[callee] < + max_depth_from_this_function) { + function_max_loop_nesting_depth_[callee] = max_depth_from_this_function; + } + } + } +} + void CallGraph::PushDirectCallees(uint32_t function_id, std::queue<uint32_t>* queue) const { for (auto callee : GetDirectCallees(function_id)) { diff --git a/source/fuzz/call_graph.h b/source/fuzz/call_graph.h index 14cd23b4..840b1f12 100644 --- a/source/fuzz/call_graph.h +++ b/source/fuzz/call_graph.h @@ -24,6 +24,9 @@ namespace spvtools { namespace fuzz { // Represents the acyclic call graph of a SPIR-V module. +// The module is assumed to be recursion-free, so there are no cycles in the +// graph. This class is immutable, so it will need to be recomputed if the +// module changes. class CallGraph { public: // Creates a call graph corresponding to the given SPIR-V module. @@ -43,7 +46,44 @@ class CallGraph { // invokes. std::set<uint32_t> GetIndirectCallees(uint32_t function_id) const; + // Returns the ids of all the functions in the graph in a topological order, + // in relation to the function calls, which are assumed to be recursion-free. + const std::vector<uint32_t>& GetFunctionsInTopologicalOrder() const { + return functions_in_topological_order_; + } + + // Returns the maximum loop nesting depth from which |function_id| can be + // called. This is computed inter-procedurally (i.e. if main calls A from + // depth 2 and A calls B from depth 1, the result will be 3 for A). + // This is a static analysis, so it's not necessarily true that the depth + // returned can actually be reached at runtime. + uint32_t GetMaxCallNestingDepth(uint32_t function_id) const { + return function_max_loop_nesting_depth_.at(function_id); + } + private: + // Computes |call_graph_edges_| and |function_in_degree_|. For each pair (A, + // B) of functions such that there is at least a function call from A to B, + // adds, to |call_to_max_depth|, a mapping from (A, B) to the maximum loop + // nesting depth (within A) of any such function call. + void BuildGraphAndGetDepthOfFunctionCalls( + opt::IRContext* context, + std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth); + + // Computes a topological order of the functions in the graph, writing the + // result to |functions_in_topological_order_|. Assumes that the function + // calls are recursion-free and that |function_in_degree_| has been computed. + void ComputeTopologicalOrderOfFunctions(); + + // Computes |function_max_loop_nesting_depth_| so that each function is mapped + // to the maximum loop nesting depth from which it can be called, as described + // by the comment to GetMaxCallNestingDepth. Assumes that |call_graph_edges_| + // and |functions_in_topological_order_| have been computed, and that + // |call_to_max_depth| contains a mapping for each edge in the graph. + void ComputeInterproceduralFunctionCallDepths( + const std::map<std::pair<uint32_t, uint32_t>, uint32_t>& + call_to_max_depth); + // Pushes the direct callees of |function_id| on to |queue|. void PushDirectCallees(uint32_t function_id, std::queue<uint32_t>* queue) const; @@ -54,6 +94,14 @@ class CallGraph { // For each function id, stores the number of distinct functions that call // the function. std::map<uint32_t, uint32_t> function_in_degree_; + + // Stores the ids of the functions in a topological order, + // in relation to the function calls, which are assumed to be recursion-free. + std::vector<uint32_t> functions_in_topological_order_; + + // For each function id, stores the maximum loop nesting depth that the + // function can be called from. + std::map<uint32_t, uint32_t> function_max_loop_nesting_depth_; }; } // namespace fuzz diff --git a/source/fuzz/comparator_deep_blocks_first.h b/source/fuzz/comparator_deep_blocks_first.h new file mode 100644 index 00000000..be63d1a7 --- /dev/null +++ b/source/fuzz/comparator_deep_blocks_first.h @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_COMPARATOR_BLOCKS_DEEP_FIRST_H_ +#define SOURCE_FUZZ_COMPARATOR_BLOCKS_DEEP_FIRST_H_ + +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +// Comparator for blocks, comparing them based on how deep they are nested +// inside selection or loop constructs. Deeper blocks are considered less than +// ones that are not as deep. The blocks are required to be in the same +// function. +class ComparatorDeepBlocksFirst { + public: + explicit ComparatorDeepBlocksFirst(opt::IRContext* ir_context) + : ir_context_(ir_context) {} + + bool operator()(uint32_t bb1, uint32_t bb2) const { + return this->operator()(fuzzerutil::MaybeFindBlock(ir_context_, bb1), + fuzzerutil::MaybeFindBlock(ir_context_, bb2)); + } + + bool operator()(const opt::BasicBlock* bb1, opt::BasicBlock* bb2) const { + assert(bb1 && bb2 && "The blocks must exist."); + assert(bb1->GetParent() == bb2->GetParent() && + "The blocks must be in the same functions."); + return ir_context_->GetStructuredCFGAnalysis()->NestingDepth(bb1->id()) > + ir_context_->GetStructuredCFGAnalysis()->NestingDepth(bb2->id()); + } + + private: + opt::IRContext* ir_context_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_COMPARATOR_BLOCKS_DEEP_FIRST_H_ diff --git a/source/fuzz/counter_overflow_id_source.cpp b/source/fuzz/counter_overflow_id_source.cpp new file mode 100644 index 00000000..1ed5603f --- /dev/null +++ b/source/fuzz/counter_overflow_id_source.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/counter_overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id) + : next_available_id_(first_available_id) {} + +bool CounterOverflowIdSource::HasOverflowIds() const { return true; } + +uint32_t CounterOverflowIdSource::GetNextOverflowId() { + return next_available_id_++; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/counter_overflow_id_source.h b/source/fuzz/counter_overflow_id_source.h new file mode 100644 index 00000000..aa8bc7ab --- /dev/null +++ b/source/fuzz/counter_overflow_id_source.h @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_ +#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_ + +#include "source/fuzz/overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +// A source of overflow ids that uses a counter to provide successive ids from +// a given starting value. +class CounterOverflowIdSource : public OverflowIdSource { + public: + // |first_available_id| is the starting value for the counter. + explicit CounterOverflowIdSource(uint32_t first_available_id); + + // Always returns true. + bool HasOverflowIds() const override; + + // Returns the current counter value and increments the counter. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should + // account for the case where the maximum allowed id is reached. + uint32_t GetNextOverflowId() override; + + private: + uint32_t next_available_id_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_ diff --git a/source/fuzz/data_descriptor.h b/source/fuzz/data_descriptor.h index c569ac80..a87a0d95 100644 --- a/source/fuzz/data_descriptor.h +++ b/source/fuzz/data_descriptor.h @@ -15,11 +15,11 @@ #ifndef SOURCE_FUZZ_DATA_DESCRIPTOR_H_ #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_ -#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" - #include <ostream> #include <vector> +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + namespace spvtools { namespace fuzz { diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp deleted file mode 100644 index eb66dfe0..00000000 --- a/source/fuzz/fact_manager.cpp +++ /dev/null @@ -1,1561 +0,0 @@ -// Copyright (c) 2019 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "source/fuzz/fact_manager.h" - -#include <sstream> -#include <unordered_map> -#include <unordered_set> - -#include "source/fuzz/equivalence_relation.h" -#include "source/fuzz/fuzzer_util.h" -#include "source/fuzz/uniform_buffer_element_descriptor.h" -#include "source/opt/ir_context.h" - -namespace spvtools { -namespace fuzz { - -namespace { - -std::string ToString(const protobufs::FactConstantUniform& fact) { - std::stringstream stream; - stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set() - << ", " << fact.uniform_buffer_element_descriptor().binding() << ")["; - - bool first = true; - for (auto index : fact.uniform_buffer_element_descriptor().index()) { - if (first) { - first = false; - } else { - stream << ", "; - } - stream << index; - } - - stream << "] == ["; - - first = true; - for (auto constant_word : fact.constant_word()) { - if (first) { - first = false; - } else { - stream << ", "; - } - stream << constant_word; - } - - stream << "]"; - return stream.str(); -} - -std::string ToString(const protobufs::FactDataSynonym& fact) { - std::stringstream stream; - stream << fact.data1() << " = " << fact.data2(); - return stream.str(); -} - -std::string ToString(const protobufs::FactIdEquation& fact) { - std::stringstream stream; - stream << fact.lhs_id(); - stream << " " << static_cast<SpvOp>(fact.opcode()); - for (auto rhs_id : fact.rhs_id()) { - stream << " " << rhs_id; - } - return stream.str(); -} - -std::string ToString(const protobufs::Fact& fact) { - switch (fact.fact_case()) { - case protobufs::Fact::kConstantUniformFact: - return ToString(fact.constant_uniform_fact()); - case protobufs::Fact::kDataSynonymFact: - return ToString(fact.data_synonym_fact()); - case protobufs::Fact::kIdEquationFact: - return ToString(fact.id_equation_fact()); - default: - assert(false && "Stringification not supported for this fact."); - return ""; - } -} - -} // namespace - -//======================= -// Constant uniform facts - -// The purpose of this class is to group the fields and data used to represent -// facts about uniform constants. -class FactManager::ConstantUniformFacts { - public: - // See method in FactManager which delegates to this method. - bool AddFact(const protobufs::FactConstantUniform& fact, - opt::IRContext* context); - - // See method in FactManager which delegates to this method. - std::vector<uint32_t> GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const; - - // See method in FactManager which delegates to this method. - const std::vector<protobufs::UniformBufferElementDescriptor> - GetUniformDescriptorsForConstant(opt::IRContext* ir_context, - uint32_t constant_id) const; - - // See method in FactManager which delegates to this method. - uint32_t GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) - const; - - // See method in FactManager which delegates to this method. - std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const; - - // See method in FactManager which delegates to this method. - const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& - GetConstantUniformFactsAndTypes() const; - - private: - // Returns true if and only if the words associated with - // |constant_instruction| exactly match the words for the constant associated - // with |constant_uniform_fact|. - bool DataMatches( - const opt::Instruction& constant_instruction, - const protobufs::FactConstantUniform& constant_uniform_fact) const; - - // Yields the constant words associated with |constant_uniform_fact|. - std::vector<uint32_t> GetConstantWords( - const protobufs::FactConstantUniform& constant_uniform_fact) const; - - // Yields the id of a constant of type |type_id| whose data matches the - // constant data in |constant_uniform_fact|, or 0 if no such constant is - // declared. - uint32_t GetConstantId( - opt::IRContext* context, - const protobufs::FactConstantUniform& constant_uniform_fact, - uint32_t type_id) const; - - // Checks that the width of a floating-point constant is supported, and that - // the constant is finite. - bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact, - uint32_t width) const; - - std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>> - facts_and_type_ids_; -}; - -uint32_t FactManager::ConstantUniformFacts::GetConstantId( - opt::IRContext* context, - const protobufs::FactConstantUniform& constant_uniform_fact, - uint32_t type_id) const { - auto type = context->get_type_mgr()->GetType(type_id); - assert(type != nullptr && "Unknown type id."); - const opt::analysis::Constant* known_constant; - if (type->AsInteger()) { - opt::analysis::IntConstant candidate_constant( - type->AsInteger(), GetConstantWords(constant_uniform_fact)); - known_constant = - context->get_constant_mgr()->FindConstant(&candidate_constant); - } else { - assert( - type->AsFloat() && - "Uniform constant facts are only supported for int and float types."); - opt::analysis::FloatConstant candidate_constant( - type->AsFloat(), GetConstantWords(constant_uniform_fact)); - known_constant = - context->get_constant_mgr()->FindConstant(&candidate_constant); - } - if (!known_constant) { - return 0; - } - return context->get_constant_mgr()->FindDeclaredConstant(known_constant, - type_id); -} - -std::vector<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords( - const protobufs::FactConstantUniform& constant_uniform_fact) const { - std::vector<uint32_t> result; - for (auto constant_word : constant_uniform_fact.constant_word()) { - result.push_back(constant_word); - } - return result; -} - -bool FactManager::ConstantUniformFacts::DataMatches( - const opt::Instruction& constant_instruction, - const protobufs::FactConstantUniform& constant_uniform_fact) const { - assert(constant_instruction.opcode() == SpvOpConstant); - std::vector<uint32_t> data_in_constant; - for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) { - data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i)); - } - return data_in_constant == GetConstantWords(constant_uniform_fact); -} - -std::vector<uint32_t> -FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const { - std::vector<uint32_t> result; - std::set<uint32_t> already_seen; - for (auto& fact_and_type_id : facts_and_type_ids_) { - if (fact_and_type_id.second != type_id) { - continue; - } - if (auto constant_id = - GetConstantId(ir_context, fact_and_type_id.first, type_id)) { - if (already_seen.find(constant_id) == already_seen.end()) { - result.push_back(constant_id); - already_seen.insert(constant_id); - } - } - } - return result; -} - -const std::vector<protobufs::UniformBufferElementDescriptor> -FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant( - opt::IRContext* ir_context, uint32_t constant_id) const { - std::vector<protobufs::UniformBufferElementDescriptor> result; - auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id); - assert(constant_inst->opcode() == SpvOpConstant && - "The given id must be that of a constant"); - auto type_id = constant_inst->type_id(); - for (auto& fact_and_type_id : facts_and_type_ids_) { - if (fact_and_type_id.second != type_id) { - continue; - } - if (DataMatches(*constant_inst, fact_and_type_id.first)) { - result.emplace_back( - fact_and_type_id.first.uniform_buffer_element_descriptor()); - } - } - return result; -} - -uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { - // Consider each fact. - for (auto& fact_and_type : facts_and_type_ids_) { - // Check whether the uniform descriptor associated with the fact matches - // |uniform_descriptor|. - if (UniformBufferElementDescriptorEquals()( - &uniform_descriptor, - &fact_and_type.first.uniform_buffer_element_descriptor())) { - return GetConstantId(context, fact_and_type.first, fact_and_type.second); - } - } - // No fact associated with the given uniform descriptor was found. - return 0; -} - -std::vector<uint32_t> -FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() - const { - std::vector<uint32_t> result; - for (auto& fact_and_type : facts_and_type_ids_) { - if (std::find(result.begin(), result.end(), fact_and_type.second) == - result.end()) { - result.push_back(fact_and_type.second); - } - } - return result; -} - -bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable( - const protobufs::FactConstantUniform& fact, uint32_t width) const { - const uint32_t kFloatWidth = 32; - const uint32_t kDoubleWidth = 64; - if (width != kFloatWidth && width != kDoubleWidth) { - // Only 32- and 64-bit floating-point types are handled. - return false; - } - std::vector<uint32_t> words = GetConstantWords(fact); - if (width == 32) { - float value; - memcpy(&value, words.data(), sizeof(float)); - if (!std::isfinite(value)) { - return false; - } - } else { - double value; - memcpy(&value, words.data(), sizeof(double)); - if (!std::isfinite(value)) { - return false; - } - } - return true; -} - -bool FactManager::ConstantUniformFacts::AddFact( - const protobufs::FactConstantUniform& fact, opt::IRContext* context) { - // Try to find a unique instruction that declares a variable such that the - // variable is decorated with the descriptor set and binding associated with - // the constant uniform fact. - opt::Instruction* uniform_variable = FindUniformVariable( - fact.uniform_buffer_element_descriptor(), context, true); - - if (!uniform_variable) { - return false; - } - - assert(SpvOpVariable == uniform_variable->opcode()); - assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0)); - - auto should_be_uniform_pointer_type = - context->get_type_mgr()->GetType(uniform_variable->type_id()); - if (!should_be_uniform_pointer_type->AsPointer()) { - return false; - } - if (should_be_uniform_pointer_type->AsPointer()->storage_class() != - SpvStorageClassUniform) { - return false; - } - auto should_be_uniform_pointer_instruction = - context->get_def_use_mgr()->GetDef(uniform_variable->type_id()); - auto composite_type = - should_be_uniform_pointer_instruction->GetSingleWordInOperand(1); - - auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices( - context, composite_type, - fact.uniform_buffer_element_descriptor().index()); - if (!final_element_type_id) { - return false; - } - auto final_element_type = - context->get_type_mgr()->GetType(final_element_type_id); - assert(final_element_type && - "There should be a type corresponding to this id."); - - if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) { - return false; - } - auto width = final_element_type->AsFloat() - ? final_element_type->AsFloat()->width() - : final_element_type->AsInteger()->width(); - - if (final_element_type->AsFloat() && - !FloatingPointValueIsSuitable(fact, width)) { - return false; - } - - auto required_words = (width + 32 - 1) / 32; - if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) { - return false; - } - facts_and_type_ids_.emplace_back( - std::pair<protobufs::FactConstantUniform, uint32_t>( - fact, final_element_type_id)); - return true; -} - -const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& -FactManager::ConstantUniformFacts::GetConstantUniformFactsAndTypes() const { - return facts_and_type_ids_; -} - -// End of uniform constant facts -//============================== - -//============================== -// Data synonym and id equation facts - -// This helper struct represents the right hand side of an equation as an -// operator applied to a number of data descriptor operands. -struct Operation { - SpvOp opcode; - std::vector<const protobufs::DataDescriptor*> operands; -}; - -// Hashing for operations, to allow deterministic unordered sets. -struct OperationHash { - size_t operator()(const Operation& operation) const { - std::u32string hash; - hash.push_back(operation.opcode); - for (auto operand : operation.operands) { - hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand))); - } - return std::hash<std::u32string>()(hash); - } -}; - -// Equality for operations, to allow deterministic unordered sets. -struct OperationEquals { - bool operator()(const Operation& first, const Operation& second) const { - // Equal operations require... - // - // Equal opcodes. - if (first.opcode != second.opcode) { - return false; - } - // Matching operand counds. - if (first.operands.size() != second.operands.size()) { - return false; - } - // Equal operands. - for (uint32_t i = 0; i < first.operands.size(); i++) { - if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) { - return false; - } - } - return true; - } -}; - -// A helper, for debugging, to represent an operation as a string. -std::string ToString(const Operation& operation) { - std::stringstream stream; - stream << operation.opcode; - for (auto operand : operation.operands) { - stream << " " << *operand; - } - return stream.str(); -} - -// The purpose of this class is to group the fields and data used to represent -// facts about data synonyms and id equations. -class FactManager::DataSynonymAndIdEquationFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context); - - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context); - - // See method in FactManager which delegates to this method. - std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor( - const protobufs::DataDescriptor& data_descriptor) const; - - // See method in FactManager which delegates to this method. - std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const; - - // See method in FactManager which delegates to this method. - bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1, - const protobufs::DataDescriptor& data_descriptor2) const; - - // See method in FactManager which delegates to this method. - void ComputeClosureOfFacts(opt::IRContext* context, - uint32_t maximum_equivalence_class_size); - - private: - using OperationSet = - std::unordered_set<Operation, OperationHash, OperationEquals>; - - // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses - // into sub-components of the data descriptors, if they are composites, to - // record that their components are pairwise-synonymous. - void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context); - - // Computes various corollary facts from the data descriptor |dd| if members - // of its equivalence class participate in equation facts with OpConvert* - // opcodes. The descriptor should be registered in the equivalence relation. - void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd, - opt::IRContext* context); - - // Recurses into sub-components of the data descriptors, if they are - // composites, to record that their components are pairwise-synonymous. - void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context); - - // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets - // of equations that are known about them. - void MakeEquivalent(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2); - - // Returns true if and only if |dd1| and |dd2| are valid data descriptors - // whose associated data have the same type (modulo integer signedness). - bool DataDescriptorsAreWellFormedAndComparable( - opt::IRContext* context, const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2) const; - - OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const; - - // Requires that |lhs_dd| and every element of |rhs_dds| is present in the - // |synonymous_| equivalence relation, but is not necessarily its own - // representative. Records the fact that the equation - // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any - // corollaries, in the form of data synonym or equation facts, that follow - // from this and other known facts. - void AddEquationFactRecursive( - const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, - const std::vector<const protobufs::DataDescriptor*>& rhs_dds, - opt::IRContext* context); - - // The data descriptors that are known to be synonymous with one another are - // captured by this equivalence relation. - EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash, - DataDescriptorEquals> - synonymous_; - - // When a new synonym fact is added, it may be possible to deduce further - // synonym facts by computing a closure of all known facts. However, this is - // an expensive operation, so it should be performed sparingly and only there - // is some chance of new facts being deduced. This boolean tracks whether a - // closure computation is required - i.e., whether a new fact has been added - // since the last time such a computation was performed. - bool closure_computation_required_ = false; - - // Represents a set of equations on data descriptors as a map indexed by - // left-hand-side, mapping a left-hand-side to a set of operations, each of - // which (together with the left-hand-side) defines an equation. - // - // All data descriptors occurring in equations are required to be present in - // the |synonymous_| equivalence relation, and to be their own representatives - // in that relation. - std::unordered_map<const protobufs::DataDescriptor*, OperationSet> - id_equations_; -}; - -void FactManager::DataSynonymAndIdEquationFacts::AddFact( - const protobufs::FactDataSynonym& fact, opt::IRContext* context) { - // Add the fact, including all facts relating sub-components of the data - // descriptors that are involved. - AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context); -} - -void FactManager::DataSynonymAndIdEquationFacts::AddFact( - const protobufs::FactIdEquation& fact, opt::IRContext* context) { - protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {}); - - // Register the LHS in the equivalence relation if needed. - if (!synonymous_.Exists(lhs_dd)) { - synonymous_.Register(lhs_dd); - } - - // Get equivalence class representatives for all ids used on the RHS of the - // equation. - std::vector<const protobufs::DataDescriptor*> rhs_dd_ptrs; - for (auto rhs_id : fact.rhs_id()) { - // Register a data descriptor based on this id in the equivalence relation - // if needed, and then record the equivalence class representative. - protobufs::DataDescriptor rhs_dd = MakeDataDescriptor(rhs_id, {}); - if (!synonymous_.Exists(rhs_dd)) { - synonymous_.Register(rhs_dd); - } - rhs_dd_ptrs.push_back(synonymous_.Find(&rhs_dd)); - } - - // Now add the fact. - AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), - rhs_dd_ptrs, context); -} - -FactManager::DataSynonymAndIdEquationFacts::OperationSet -FactManager::DataSynonymAndIdEquationFacts::GetEquations( - const protobufs::DataDescriptor* lhs) const { - auto existing = id_equations_.find(lhs); - if (existing == id_equations_.end()) { - return OperationSet(); - } - return existing->second; -} - -void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive( - const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, - const std::vector<const protobufs::DataDescriptor*>& rhs_dds, - opt::IRContext* context) { - assert(synonymous_.Exists(lhs_dd) && - "The LHS must be known to the equivalence relation."); - for (auto rhs_dd : rhs_dds) { - // Keep release compilers happy. - (void)(rhs_dd); - assert(synonymous_.Exists(*rhs_dd) && - "The RHS operands must be known to the equivalence relation."); - } - - auto lhs_dd_representative = synonymous_.Find(&lhs_dd); - - if (id_equations_.count(lhs_dd_representative) == 0) { - // We have not seen an equation with this LHS before, so associate the LHS - // with an initially empty set. - id_equations_.insert({lhs_dd_representative, OperationSet()}); - } - - { - auto existing_equations = id_equations_.find(lhs_dd_representative); - assert(existing_equations != id_equations_.end() && - "A set of operations should be present, even if empty."); - - Operation new_operation = {opcode, rhs_dds}; - if (existing_equations->second.count(new_operation)) { - // This equation is known, so there is nothing further to be done. - return; - } - // Add the equation to the set of known equations. - existing_equations->second.insert(new_operation); - } - - // Now try to work out corollaries implied by the new equation and existing - // facts. - switch (opcode) { - case SpvOpConvertSToF: - case SpvOpConvertUToF: - ComputeConversionDataSynonymFacts(*rhs_dds[0], context); - break; - case SpvOpIAdd: { - // Equation form: "a = b + c" - for (const auto& equation : GetEquations(rhs_dds[0])) { - if (equation.opcode == SpvOpISub) { - // Equation form: "a = (d - e) + c" - if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { - // Equation form: "a = (d - c) + c" - // We can thus infer "a = d" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); - } - if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { - // Equation form: "a = (c - e) + c" - // We can thus infer "a = -e" - AddEquationFactRecursive(lhs_dd, SpvOpSNegate, - {equation.operands[1]}, context); - } - } - } - for (const auto& equation : GetEquations(rhs_dds[1])) { - if (equation.opcode == SpvOpISub) { - // Equation form: "a = b + (d - e)" - if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { - // Equation form: "a = b + (d - b)" - // We can thus infer "a = d" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); - } - } - } - break; - } - case SpvOpISub: { - // Equation form: "a = b - c" - for (const auto& equation : GetEquations(rhs_dds[0])) { - if (equation.opcode == SpvOpIAdd) { - // Equation form: "a = (d + e) - c" - if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { - // Equation form: "a = (c + e) - c" - // We can thus infer "a = e" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context); - } - if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { - // Equation form: "a = (d + c) - c" - // We can thus infer "a = d" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); - } - } - - if (equation.opcode == SpvOpISub) { - // Equation form: "a = (d - e) - c" - if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { - // Equation form: "a = (c - e) - c" - // We can thus infer "a = -e" - AddEquationFactRecursive(lhs_dd, SpvOpSNegate, - {equation.operands[1]}, context); - } - } - } - - for (const auto& equation : GetEquations(rhs_dds[1])) { - if (equation.opcode == SpvOpIAdd) { - // Equation form: "a = b - (d + e)" - if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { - // Equation form: "a = b - (b + e)" - // We can thus infer "a = -e" - AddEquationFactRecursive(lhs_dd, SpvOpSNegate, - {equation.operands[1]}, context); - } - if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { - // Equation form: "a = b - (d + b)" - // We can thus infer "a = -d" - AddEquationFactRecursive(lhs_dd, SpvOpSNegate, - {equation.operands[0]}, context); - } - } - if (equation.opcode == SpvOpISub) { - // Equation form: "a = b - (d - e)" - if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { - // Equation form: "a = b - (b - e)" - // We can thus infer "a = e" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context); - } - } - } - break; - } - case SpvOpLogicalNot: - case SpvOpSNegate: { - // Equation form: "a = !b" or "a = -b" - for (const auto& equation : GetEquations(rhs_dds[0])) { - if (equation.opcode == opcode) { - // Equation form: "a = !!b" or "a = -(-b)" - // We can thus infer "a = b" - AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context); - } - } - break; - } - default: - break; - } -} - -void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( - const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2, - opt::IRContext* context) { - assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2)); - - // Record that the data descriptors provided in the fact are equivalent. - MakeEquivalent(dd1, dd2); - - // Compute various corollary facts. - ComputeConversionDataSynonymFacts(dd1, context); - ComputeCompositeDataSynonymFacts(dd1, dd2, context); -} - -void FactManager::DataSynonymAndIdEquationFacts:: - ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd, - opt::IRContext* context) { - assert(synonymous_.Exists(dd) && - "|dd| should've been registered in the equivalence relation"); - - const auto* representative = synonymous_.Find(&dd); - assert(representative && - "Representative can't be null for a registered descriptor"); - - const auto* type = - context->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices( - context, fuzzerutil::GetTypeId(context, representative->object()), - representative->index())); - assert(type && "Data descriptor has invalid type"); - - if ((type->AsVector() && type->AsVector()->element_type()->AsInteger()) || - type->AsInteger()) { - // If there exist equation facts of the form |%a = opcode %representative| - // and |%b = opcode %representative| where |opcode| is either OpConvertSToF - // or OpConvertUToF, then |a| and |b| are synonymous. - std::vector<const protobufs::DataDescriptor*> convert_s_to_f_lhs; - std::vector<const protobufs::DataDescriptor*> convert_u_to_f_lhs; - - for (const auto& fact : id_equations_) { - for (const auto& equation : fact.second) { - if (synonymous_.IsEquivalent(*equation.operands[0], *representative)) { - if (equation.opcode == SpvOpConvertSToF) { - convert_s_to_f_lhs.push_back(fact.first); - } else if (equation.opcode == SpvOpConvertUToF) { - convert_u_to_f_lhs.push_back(fact.first); - } - } - } - } - - for (const auto& synonyms : - {std::move(convert_s_to_f_lhs), std::move(convert_u_to_f_lhs)}) { - for (const auto* synonym_a : synonyms) { - for (const auto* synonym_b : synonyms) { - if (!synonymous_.IsEquivalent(*synonym_a, *synonym_b) && - DataDescriptorsAreWellFormedAndComparable(context, *synonym_a, - *synonym_b)) { - // |synonym_a| and |synonym_b| have compatible types - they are - // synonymous. - AddDataSynonymFactRecursive(*synonym_a, *synonym_b, context); - } - } - } - } - } -} - -void FactManager::DataSynonymAndIdEquationFacts:: - ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2, - opt::IRContext* context) { - // Check whether this is a synonym about composite objects. If it is, - // we can recursively add synonym facts about their associated sub-components. - - // Get the type of the object referred to by the first data descriptor in the - // synonym fact. - uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices( - context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(), - dd1.index()); - auto type = context->get_type_mgr()->GetType(type_id); - auto type_instruction = context->get_def_use_mgr()->GetDef(type_id); - assert(type != nullptr && - "Invalid data synonym fact: one side has an unknown type."); - - // Check whether the type is composite, recording the number of elements - // associated with the composite if so. - uint32_t num_composite_elements; - if (type->AsArray()) { - num_composite_elements = - fuzzerutil::GetArraySize(*type_instruction, context); - } else if (type->AsMatrix()) { - num_composite_elements = type->AsMatrix()->element_count(); - } else if (type->AsStruct()) { - num_composite_elements = - fuzzerutil::GetNumberOfStructMembers(*type_instruction); - } else if (type->AsVector()) { - num_composite_elements = type->AsVector()->element_count(); - } else { - // The type is not a composite, so return. - return; - } - - // If the fact has the form: - // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] - // then for each composite index i, we add a fact of the form: - // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] - // - // However, to avoid adding a large number of synonym facts e.g. in the case - // of arrays, we bound the number of composite elements to which this is - // applied. Nevertheless, we always add a synonym fact for the final - // components, as this may be an interesting edge case. - - // The bound on the number of indices of the composite pair to note as being - // synonymous. - const uint32_t kCompositeElementBound = 10; - - for (uint32_t i = 0; i < num_composite_elements;) { - std::vector<uint32_t> extended_indices1 = - fuzzerutil::RepeatedFieldToVector(dd1.index()); - extended_indices1.push_back(i); - std::vector<uint32_t> extended_indices2 = - fuzzerutil::RepeatedFieldToVector(dd2.index()); - extended_indices2.push_back(i); - AddDataSynonymFactRecursive( - MakeDataDescriptor(dd1.object(), std::move(extended_indices1)), - MakeDataDescriptor(dd2.object(), std::move(extended_indices2)), - context); - - if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) { - // We have not reached the bound yet, or have already skipped ahead to the - // last element, so increment the loop counter as standard. - i++; - } else { - // We have reached the bound, so skip ahead to the last element. - assert(i == kCompositeElementBound - 1); - i = num_composite_elements - 1; - } - } -} - -void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( - opt::IRContext* context, uint32_t maximum_equivalence_class_size) { - // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct - // data descriptors that describe objects of the same composite type, and that - // the composite type is comprised of k components. - // - // For example, if m is a mat4x4 and v a vec4, we might consider: - // m[2]: describes the 2nd column of m, a vec4 - // v[]: describes all of v, a vec4 - // - // Suppose that we know, for every 0 <= i < k, that the fact: - // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] - // holds - i.e. that the children of the two data descriptors are synonymous. - // - // Then we can conclude that: - // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] - // holds. - // - // For instance, if we have the facts: - // m[2, 0] == v[0] - // m[2, 1] == v[1] - // m[2, 2] == v[2] - // m[2, 3] == v[3] - // then we can conclude that: - // m[2] == v. - // - // This method repeatedly searches the equivalence relation of data - // descriptors, deducing and adding such facts, until a pass over the - // relation leads to no further facts being deduced. - - // The method relies on working with pairs of data descriptors, and in - // particular being able to hash and compare such pairs. - - using DataDescriptorPair = - std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>; - - struct DataDescriptorPairHash { - std::size_t operator()(const DataDescriptorPair& pair) const { - return DataDescriptorHash()(&pair.first) ^ - DataDescriptorHash()(&pair.second); - } - }; - - struct DataDescriptorPairEquals { - bool operator()(const DataDescriptorPair& first, - const DataDescriptorPair& second) const { - return (DataDescriptorEquals()(&first.first, &second.first) && - DataDescriptorEquals()(&first.second, &second.second)) || - (DataDescriptorEquals()(&first.first, &second.second) && - DataDescriptorEquals()(&first.second, &second.first)); - } - }; - - // This map records, for a given pair of composite data descriptors of the - // same type, all the indices at which the data descriptors are known to be - // synonymous. A pair is a key to this map only if we have observed that - // the pair are synonymous at *some* index, but not at *all* indices. - // Once we find that a pair of data descriptors are equivalent at all indices - // we record the fact that they are synonymous and remove them from the map. - // - // Using the m and v example from above, initially the pair (m[2], v) would - // not be a key to the map. If we find that m[2, 2] == v[2] holds, we would - // add an entry: - // (m[2], v) -> [false, false, true, false] - // to record that they are synonymous at index 2. If we then find that - // m[2, 0] == v[0] holds, we would update this entry to: - // (m[2], v) -> [true, false, true, false] - // If we then find that m[2, 3] == v[3] holds, we would update this entry to: - // (m[2], v) -> [true, false, true, true] - // Finally, if we then find that m[2, 1] == v[1] holds, which would make the - // boolean vector true at every index, we would add the fact: - // m[2] == v - // to the equivalence relation and remove (m[2], v) from the map. - std::unordered_map<DataDescriptorPair, std::vector<bool>, - DataDescriptorPairHash, DataDescriptorPairEquals> - candidate_composite_synonyms; - - // We keep looking for new facts until we perform a complete pass over the - // equivalence relation without finding any new facts. - while (closure_computation_required_) { - // We have not found any new facts yet during this pass; we set this to - // 'true' if we do find a new fact. - closure_computation_required_ = false; - - // Consider each class in the equivalence relation. - for (auto representative : - synonymous_.GetEquivalenceClassRepresentatives()) { - auto equivalence_class = synonymous_.GetEquivalenceClass(*representative); - - if (equivalence_class.size() > maximum_equivalence_class_size) { - // This equivalence class is larger than the maximum size we are willing - // to consider, so we skip it. This potentially leads to missed fact - // deductions, but avoids excessive runtime for closure computation. - continue; - } - - // Consider every data descriptor in the equivalence class. - for (auto dd1_it = equivalence_class.begin(); - dd1_it != equivalence_class.end(); ++dd1_it) { - // If this data descriptor has no indices then it does not have the form - // obj_1[a_1, ..., a_m, i], so move on. - auto dd1 = *dd1_it; - if (dd1->index_size() == 0) { - continue; - } - - // Consider every other data descriptor later in the equivalence class - // (due to symmetry, there is no need to compare with previous data - // descriptors). - auto dd2_it = dd1_it; - for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) { - auto dd2 = *dd2_it; - // If this data descriptor has no indices then it does not have the - // form obj_2[b_1, ..., b_n, i], so move on. - if (dd2->index_size() == 0) { - continue; - } - - // At this point we know that: - // - |dd1| has the form obj_1[a_1, ..., a_m, i] - // - |dd2| has the form obj_2[b_1, ..., b_n, j] - assert(dd1->index_size() > 0 && dd2->index_size() > 0 && - "Control should not reach here if either data descriptor has " - "no indices."); - - // We are only interested if i == j. - if (dd1->index(dd1->index_size() - 1) != - dd2->index(dd2->index_size() - 1)) { - continue; - } - - const uint32_t common_final_index = dd1->index(dd1->index_size() - 1); - - // Make data descriptors |dd1_prefix| and |dd2_prefix| for - // obj_1[a_1, ..., a_m] - // and - // obj_2[b_1, ..., b_n] - // These are the two data descriptors we might be getting closer to - // deducing as being synonymous, due to knowing that they are - // synonymous when extended by a particular index. - protobufs::DataDescriptor dd1_prefix; - dd1_prefix.set_object(dd1->object()); - for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1); - i++) { - dd1_prefix.add_index(dd1->index(i)); - } - protobufs::DataDescriptor dd2_prefix; - dd2_prefix.set_object(dd2->object()); - for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1); - i++) { - dd2_prefix.add_index(dd2->index(i)); - } - assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) && - "By construction these prefixes should be different."); - - // If we already know that these prefixes are synonymous, move on. - if (synonymous_.Exists(dd1_prefix) && - synonymous_.Exists(dd2_prefix) && - synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) { - continue; - } - - // Get the type of obj_1 - auto dd1_root_type_id = - context->get_def_use_mgr()->GetDef(dd1->object())->type_id(); - // Use this type, together with a_1, ..., a_m, to get the type of - // obj_1[a_1, ..., a_m]. - auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices( - context, dd1_root_type_id, dd1_prefix.index()); - - // Similarly, get the type of obj_2 and use it to get the type of - // obj_2[b_1, ..., b_n]. - auto dd2_root_type_id = - context->get_def_use_mgr()->GetDef(dd2->object())->type_id(); - auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices( - context, dd2_root_type_id, dd2_prefix.index()); - - // If the types of dd1_prefix and dd2_prefix are not the same, they - // cannot be synonymous. - if (dd1_prefix_type != dd2_prefix_type) { - continue; - } - - // At this point, we know we have synonymous data descriptors of the - // form: - // obj_1[a_1, ..., a_m, i] - // obj_2[b_1, ..., b_n, i] - // with the same last_index i, such that: - // obj_1[a_1, ..., a_m] - // and - // obj_2[b_1, ..., b_n] - // have the same type. - - // Work out how many components there are in the (common) commposite - // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n]. - // This depends on whether the composite type is array, matrix, struct - // or vector. - uint32_t num_components_in_composite; - auto composite_type = - context->get_type_mgr()->GetType(dd1_prefix_type); - auto composite_type_instruction = - context->get_def_use_mgr()->GetDef(dd1_prefix_type); - if (composite_type->AsArray()) { - num_components_in_composite = - fuzzerutil::GetArraySize(*composite_type_instruction, context); - if (num_components_in_composite == 0) { - // This indicates that the array has an unknown size, in which - // case we cannot be sure we have matched all of its elements with - // synonymous elements of another array. - continue; - } - } else if (composite_type->AsMatrix()) { - num_components_in_composite = - composite_type->AsMatrix()->element_count(); - } else if (composite_type->AsStruct()) { - num_components_in_composite = fuzzerutil::GetNumberOfStructMembers( - *composite_type_instruction); - } else { - assert(composite_type->AsVector()); - num_components_in_composite = - composite_type->AsVector()->element_count(); - } - - // We are one step closer to being able to say that |dd1_prefix| and - // |dd2_prefix| are synonymous. - DataDescriptorPair candidate_composite_synonym(dd1_prefix, - dd2_prefix); - - // We look up what we already know about this pair. - auto existing_entry = - candidate_composite_synonyms.find(candidate_composite_synonym); - - if (existing_entry == candidate_composite_synonyms.end()) { - // If this is the first time we have seen the pair, we make a vector - // of size |num_components_in_composite| that is 'true' at the - // common final index associated with |dd1| and |dd2|, and 'false' - // everywhere else, and register this vector as being associated - // with the pair. - std::vector<bool> entry; - for (uint32_t i = 0; i < num_components_in_composite; i++) { - entry.push_back(i == common_final_index); - } - candidate_composite_synonyms[candidate_composite_synonym] = entry; - existing_entry = - candidate_composite_synonyms.find(candidate_composite_synonym); - } else { - // We have seen this pair of data descriptors before, and we now - // know that they are synonymous at one further index, so we - // update the entry to record that. - existing_entry->second[common_final_index] = true; - } - assert(existing_entry != candidate_composite_synonyms.end()); - - // Check whether |dd1_prefix| and |dd2_prefix| are now known to match - // at every sub-component. - bool all_components_match = true; - for (uint32_t i = 0; i < num_components_in_composite; i++) { - if (!existing_entry->second[i]) { - all_components_match = false; - break; - } - } - if (all_components_match) { - // The two prefixes match on all sub-components, so we know that - // they are synonymous. We add this fact *non-recursively*, as we - // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous - // by observing that all their sub-components are already - // synonymous. - assert(DataDescriptorsAreWellFormedAndComparable( - context, dd1_prefix, dd2_prefix)); - MakeEquivalent(dd1_prefix, dd2_prefix); - // Now that we know this pair of data descriptors are synonymous, - // there is no point recording how close they are to being - // synonymous. - candidate_composite_synonyms.erase(candidate_composite_synonym); - } - } - } - } - } -} - -void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent( - const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2) { - // Register the data descriptors if they are not already known to the - // equivalence relation. - for (const auto& dd : {dd1, dd2}) { - if (!synonymous_.Exists(dd)) { - synonymous_.Register(dd); - } - } - - if (synonymous_.IsEquivalent(dd1, dd2)) { - // The data descriptors are already known to be equivalent, so there is - // nothing to do. - return; - } - - // We must make the data descriptors equivalent, and also make sure any - // equation facts known about their representatives are merged. - - // Record the original equivalence class representatives of the data - // descriptors. - auto dd1_original_representative = synonymous_.Find(&dd1); - auto dd2_original_representative = synonymous_.Find(&dd2); - - // Make the data descriptors equivalent. - synonymous_.MakeEquivalent(dd1, dd2); - // As we have updated the equivalence relation, we might be able to deduce - // more facts by performing a closure computation, so we record that such a - // computation is required. - closure_computation_required_ = true; - - // At this point, exactly one of |dd1_original_representative| and - // |dd2_original_representative| will be the representative of the combined - // equivalence class. We work out which one of them is still the class - // representative and which one is no longer the class representative. - - auto still_representative = synonymous_.Find(dd1_original_representative) == - dd1_original_representative - ? dd1_original_representative - : dd2_original_representative; - auto no_longer_representative = - still_representative == dd1_original_representative - ? dd2_original_representative - : dd1_original_representative; - - assert(no_longer_representative != still_representative && - "The current and former representatives cannot be the same."); - - // We now need to add all equations about |no_longer_representative| to the - // set of equations known about |still_representative|. - - // Get the equations associated with |no_longer_representative|. - auto no_longer_representative_id_equations = - id_equations_.find(no_longer_representative); - if (no_longer_representative_id_equations != id_equations_.end()) { - // There are some equations to transfer. There might not yet be any - // equations about |still_representative|; create an empty set of equations - // if this is the case. - if (!id_equations_.count(still_representative)) { - id_equations_.insert({still_representative, OperationSet()}); - } - auto still_representative_id_equations = - id_equations_.find(still_representative); - assert(still_representative_id_equations != id_equations_.end() && - "At this point there must be a set of equations."); - // Add all the equations known about |no_longer_representative| to the set - // of equations known about |still_representative|. - still_representative_id_equations->second.insert( - no_longer_representative_id_equations->second.begin(), - no_longer_representative_id_equations->second.end()); - } - // Delete the no longer-relevant equations about |no_longer_representative|. - id_equations_.erase(no_longer_representative); -} - -bool FactManager::DataSynonymAndIdEquationFacts:: - DataDescriptorsAreWellFormedAndComparable( - opt::IRContext* context, const protobufs::DataDescriptor& dd1, - const protobufs::DataDescriptor& dd2) const { - auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices( - context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(), - dd1.index()); - auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices( - context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(), - dd2.index()); - // The end types of the data descriptors must exist. - if (end_type_id_1 == 0 || end_type_id_2 == 0) { - return false; - } - // If the end types are the same, the data descriptors are comparable. - if (end_type_id_1 == end_type_id_2) { - return true; - } - // Otherwise they are only comparable if they are integer scalars or integer - // vectors that differ only in signedness. - - // Get both types. - const opt::analysis::Type* type_1 = - context->get_type_mgr()->GetType(end_type_id_1); - const opt::analysis::Type* type_2 = - context->get_type_mgr()->GetType(end_type_id_2); - - // If the first type is a vector, check that the second type is a vector of - // the same width, and drill down to the vector element types. - if (type_1->AsVector()) { - if (!type_2->AsVector()) { - return false; - } - if (type_1->AsVector()->element_count() != - type_2->AsVector()->element_count()) { - return false; - } - type_1 = type_1->AsVector()->element_type(); - type_2 = type_2->AsVector()->element_type(); - } - // Check that type_1 and type_2 are both integer types of the same bit-width - // (but with potentially different signedness). - auto integer_type_1 = type_1->AsInteger(); - auto integer_type_2 = type_2->AsInteger(); - return integer_type_1 && integer_type_2 && - integer_type_1->width() == integer_type_2->width(); -} - -std::vector<const protobufs::DataDescriptor*> -FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( - const protobufs::DataDescriptor& data_descriptor) const { - if (synonymous_.Exists(data_descriptor)) { - return synonymous_.GetEquivalenceClass(data_descriptor); - } - return std::vector<const protobufs::DataDescriptor*>(); -} - -std::vector<uint32_t> -FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() - const { - std::vector<uint32_t> result; - for (auto& data_descriptor : synonymous_.GetAllKnownValues()) { - if (data_descriptor->index().empty()) { - result.push_back(data_descriptor->object()); - } - } - return result; -} - -bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous( - const protobufs::DataDescriptor& data_descriptor1, - const protobufs::DataDescriptor& data_descriptor2) const { - return synonymous_.Exists(data_descriptor1) && - synonymous_.Exists(data_descriptor2) && - synonymous_.IsEquivalent(data_descriptor1, data_descriptor2); -} - -// End of data synonym facts -//============================== - -//============================== -// Dead block facts - -// The purpose of this class is to group the fields and data used to represent -// facts about data blocks. -class FactManager::DeadBlockFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactBlockIsDead& fact); - - // See method in FactManager which delegates to this method. - bool BlockIsDead(uint32_t block_id) const; - - private: - std::set<uint32_t> dead_block_ids_; -}; - -void FactManager::DeadBlockFacts::AddFact( - const protobufs::FactBlockIsDead& fact) { - dead_block_ids_.insert(fact.block_id()); -} - -bool FactManager::DeadBlockFacts::BlockIsDead(uint32_t block_id) const { - return dead_block_ids_.count(block_id) != 0; -} - -// End of dead block facts -//============================== - -//============================== -// Livesafe function facts - -// The purpose of this class is to group the fields and data used to represent -// facts about livesafe functions. -class FactManager::LivesafeFunctionFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactFunctionIsLivesafe& fact); - - // See method in FactManager which delegates to this method. - bool FunctionIsLivesafe(uint32_t function_id) const; - - private: - std::set<uint32_t> livesafe_function_ids_; -}; - -void FactManager::LivesafeFunctionFacts::AddFact( - const protobufs::FactFunctionIsLivesafe& fact) { - livesafe_function_ids_.insert(fact.function_id()); -} - -bool FactManager::LivesafeFunctionFacts::FunctionIsLivesafe( - uint32_t function_id) const { - return livesafe_function_ids_.count(function_id) != 0; -} - -// End of livesafe function facts -//============================== - -//============================== -// Irrelevant value facts - -// The purpose of this class is to group the fields and data used to represent -// facts about various irrelevant values in the module. -class FactManager::IrrelevantValueFacts { - public: - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact); - - // See method in FactManager which delegates to this method. - void AddFact(const protobufs::FactIdIsIrrelevant& fact); - - // See method in FactManager which delegates to this method. - bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; - - // See method in FactManager which delegates to this method. - bool IdIsIrrelevant(uint32_t pointer_id) const; - - private: - std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_; - std::unordered_set<uint32_t> irrelevant_ids_; -}; - -void FactManager::IrrelevantValueFacts::AddFact( - const protobufs::FactPointeeValueIsIrrelevant& fact) { - pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); -} - -void FactManager::IrrelevantValueFacts::AddFact( - const protobufs::FactIdIsIrrelevant& fact) { - irrelevant_ids_.insert(fact.result_id()); -} - -bool FactManager::IrrelevantValueFacts::PointeeValueIsIrrelevant( - uint32_t pointer_id) const { - return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0; -} - -bool FactManager::IrrelevantValueFacts::IdIsIrrelevant( - uint32_t pointer_id) const { - return irrelevant_ids_.count(pointer_id) != 0; -} - -// End of arbitrarily-valued variable facts -//============================== - -FactManager::FactManager() - : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()), - data_synonym_and_id_equation_facts_( - MakeUnique<DataSynonymAndIdEquationFacts>()), - dead_block_facts_(MakeUnique<DeadBlockFacts>()), - livesafe_function_facts_(MakeUnique<LivesafeFunctionFacts>()), - irrelevant_value_facts_(MakeUnique<IrrelevantValueFacts>()) {} - -FactManager::~FactManager() = default; - -void FactManager::AddFacts(const MessageConsumer& message_consumer, - const protobufs::FactSequence& initial_facts, - opt::IRContext* context) { - for (auto& fact : initial_facts.fact()) { - if (!AddFact(fact, context)) { - message_consumer( - SPV_MSG_WARNING, nullptr, {}, - ("Invalid fact " + ToString(fact) + " ignored.").c_str()); - } - } -} - -bool FactManager::AddFact(const fuzz::protobufs::Fact& fact, - opt::IRContext* context) { - switch (fact.fact_case()) { - case protobufs::Fact::kConstantUniformFact: - return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(), - context); - case protobufs::Fact::kDataSynonymFact: - data_synonym_and_id_equation_facts_->AddFact(fact.data_synonym_fact(), - context); - return true; - case protobufs::Fact::kBlockIsDeadFact: - dead_block_facts_->AddFact(fact.block_is_dead_fact()); - return true; - case protobufs::Fact::kFunctionIsLivesafeFact: - livesafe_function_facts_->AddFact(fact.function_is_livesafe_fact()); - return true; - default: - assert(false && "Unknown fact type."); - return false; - } -} - -void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1, - const protobufs::DataDescriptor& data2, - opt::IRContext* context) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550): - // assert that neither |data1| nor |data2| are irrelevant. - protobufs::FactDataSynonym fact; - *fact.mutable_data1() = data1; - *fact.mutable_data2() = data2; - data_synonym_and_id_equation_facts_->AddFact(fact, context); -} - -std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const { - return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType( - ir_context, type_id); -} - -const std::vector<protobufs::UniformBufferElementDescriptor> -FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context, - uint32_t constant_id) const { - return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context, - constant_id); -} - -uint32_t FactManager::GetConstantFromUniformDescriptor( - opt::IRContext* context, - const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { - return uniform_constant_facts_->GetConstantFromUniformDescriptor( - context, uniform_descriptor); -} - -std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown() - const { - return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown(); -} - -const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& -FactManager::GetConstantUniformFactsAndTypes() const { - return uniform_constant_facts_->GetConstantUniformFactsAndTypes(); -} - -std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const { - return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown(); -} - -std::vector<const protobufs::DataDescriptor*> -FactManager::GetSynonymsForDataDescriptor( - const protobufs::DataDescriptor& data_descriptor) const { - return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor( - data_descriptor); -} - -std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId( - uint32_t id) const { - return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {})); -} - -bool FactManager::IsSynonymous( - const protobufs::DataDescriptor& data_descriptor1, - const protobufs::DataDescriptor& data_descriptor2) const { - return data_synonym_and_id_equation_facts_->IsSynonymous(data_descriptor1, - data_descriptor2); -} - -bool FactManager::BlockIsDead(uint32_t block_id) const { - return dead_block_facts_->BlockIsDead(block_id); -} - -void FactManager::AddFactBlockIsDead(uint32_t block_id) { - protobufs::FactBlockIsDead fact; - fact.set_block_id(block_id); - dead_block_facts_->AddFact(fact); -} - -bool FactManager::FunctionIsLivesafe(uint32_t function_id) const { - return livesafe_function_facts_->FunctionIsLivesafe(function_id); -} - -void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) { - protobufs::FactFunctionIsLivesafe fact; - fact.set_function_id(function_id); - livesafe_function_facts_->AddFact(fact); -} - -bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const { - return irrelevant_value_facts_->PointeeValueIsIrrelevant(pointer_id); -} - -bool FactManager::IdIsIrrelevant(uint32_t result_id) const { - return irrelevant_value_facts_->IdIsIrrelevant(result_id); -} - -void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) { - protobufs::FactPointeeValueIsIrrelevant fact; - fact.set_pointer_id(pointer_id); - irrelevant_value_facts_->AddFact(fact); -} - -void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) { - protobufs::FactIdIsIrrelevant fact; - fact.set_result_id(result_id); - irrelevant_value_facts_->AddFact(fact); -} - -void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, - const std::vector<uint32_t>& rhs_id, - opt::IRContext* context) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550): - // assert that elements of |rhs_id| and |lhs_id| are not irrelevant. - protobufs::FactIdEquation fact; - fact.set_lhs_id(lhs_id); - fact.set_opcode(opcode); - for (auto an_rhs_id : rhs_id) { - fact.add_rhs_id(an_rhs_id); - } - data_synonym_and_id_equation_facts_->AddFact(fact, context); -} - -void FactManager::ComputeClosureOfFacts( - opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) { - data_synonym_and_id_equation_facts_->ComputeClosureOfFacts( - ir_context, maximum_equivalence_class_size); -} - -} // namespace fuzz -} // namespace spvtools diff --git a/source/fuzz/fact_manager/constant_uniform_facts.cpp b/source/fuzz/fact_manager/constant_uniform_facts.cpp new file mode 100644 index 00000000..23e3829a --- /dev/null +++ b/source/fuzz/fact_manager/constant_uniform_facts.cpp @@ -0,0 +1,234 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fact_manager/constant_uniform_facts.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/uniform_buffer_element_descriptor.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +ConstantUniformFacts::ConstantUniformFacts(opt::IRContext* ir_context) + : ir_context_(ir_context) {} + +uint32_t ConstantUniformFacts::GetConstantId( + const protobufs::FactConstantUniform& constant_uniform_fact, + uint32_t type_id) const { + auto type = ir_context_->get_type_mgr()->GetType(type_id); + assert(type != nullptr && "Unknown type id."); + const opt::analysis::Constant* known_constant; + if (type->AsInteger()) { + opt::analysis::IntConstant candidate_constant( + type->AsInteger(), GetConstantWords(constant_uniform_fact)); + known_constant = + ir_context_->get_constant_mgr()->FindConstant(&candidate_constant); + } else { + assert( + type->AsFloat() && + "Uniform constant facts are only supported for int and float types."); + opt::analysis::FloatConstant candidate_constant( + type->AsFloat(), GetConstantWords(constant_uniform_fact)); + known_constant = + ir_context_->get_constant_mgr()->FindConstant(&candidate_constant); + } + if (!known_constant) { + return 0; + } + return ir_context_->get_constant_mgr()->FindDeclaredConstant(known_constant, + type_id); +} + +std::vector<uint32_t> ConstantUniformFacts::GetConstantWords( + const protobufs::FactConstantUniform& constant_uniform_fact) { + std::vector<uint32_t> result; + for (auto constant_word : constant_uniform_fact.constant_word()) { + result.push_back(constant_word); + } + return result; +} + +bool ConstantUniformFacts::DataMatches( + const opt::Instruction& constant_instruction, + const protobufs::FactConstantUniform& constant_uniform_fact) { + assert(constant_instruction.opcode() == SpvOpConstant); + std::vector<uint32_t> data_in_constant; + for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) { + data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i)); + } + return data_in_constant == GetConstantWords(constant_uniform_fact); +} + +std::vector<uint32_t> +ConstantUniformFacts::GetConstantsAvailableFromUniformsForType( + uint32_t type_id) const { + std::vector<uint32_t> result; + std::set<uint32_t> already_seen; + for (auto& fact_and_type_id : facts_and_type_ids_) { + if (fact_and_type_id.second != type_id) { + continue; + } + if (auto constant_id = GetConstantId(fact_and_type_id.first, type_id)) { + if (already_seen.find(constant_id) == already_seen.end()) { + result.push_back(constant_id); + already_seen.insert(constant_id); + } + } + } + return result; +} + +std::vector<protobufs::UniformBufferElementDescriptor> +ConstantUniformFacts::GetUniformDescriptorsForConstant( + uint32_t constant_id) const { + std::vector<protobufs::UniformBufferElementDescriptor> result; + auto constant_inst = ir_context_->get_def_use_mgr()->GetDef(constant_id); + assert(constant_inst->opcode() == SpvOpConstant && + "The given id must be that of a constant"); + auto type_id = constant_inst->type_id(); + for (auto& fact_and_type_id : facts_and_type_ids_) { + if (fact_and_type_id.second != type_id) { + continue; + } + if (DataMatches(*constant_inst, fact_and_type_id.first)) { + result.emplace_back( + fact_and_type_id.first.uniform_buffer_element_descriptor()); + } + } + return result; +} + +uint32_t ConstantUniformFacts::GetConstantFromUniformDescriptor( + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { + // Consider each fact. + for (auto& fact_and_type : facts_and_type_ids_) { + // Check whether the uniform descriptor associated with the fact matches + // |uniform_descriptor|. + if (UniformBufferElementDescriptorEquals()( + &uniform_descriptor, + &fact_and_type.first.uniform_buffer_element_descriptor())) { + return GetConstantId(fact_and_type.first, fact_and_type.second); + } + } + // No fact associated with the given uniform descriptor was found. + return 0; +} + +std::vector<uint32_t> +ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() const { + std::vector<uint32_t> result; + for (auto& fact_and_type : facts_and_type_ids_) { + if (std::find(result.begin(), result.end(), fact_and_type.second) == + result.end()) { + result.push_back(fact_and_type.second); + } + } + return result; +} + +bool ConstantUniformFacts::FloatingPointValueIsSuitable( + const protobufs::FactConstantUniform& fact, uint32_t width) { + const uint32_t kFloatWidth = 32; + const uint32_t kDoubleWidth = 64; + if (width != kFloatWidth && width != kDoubleWidth) { + // Only 32- and 64-bit floating-point types are handled. + return false; + } + std::vector<uint32_t> words = GetConstantWords(fact); + if (width == 32) { + float value; + memcpy(&value, words.data(), sizeof(float)); + if (!std::isfinite(value)) { + return false; + } + } else { + double value; + memcpy(&value, words.data(), sizeof(double)); + if (!std::isfinite(value)) { + return false; + } + } + return true; +} + +bool ConstantUniformFacts::AddFact(const protobufs::FactConstantUniform& fact) { + // Try to find a unique instruction that declares a variable such that the + // variable is decorated with the descriptor set and binding associated with + // the constant uniform fact. + opt::Instruction* uniform_variable = FindUniformVariable( + fact.uniform_buffer_element_descriptor(), ir_context_, true); + + if (!uniform_variable) { + return false; + } + + assert(SpvOpVariable == uniform_variable->opcode()); + assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0)); + + auto should_be_uniform_pointer_type = + ir_context_->get_type_mgr()->GetType(uniform_variable->type_id()); + if (!should_be_uniform_pointer_type->AsPointer()) { + return false; + } + if (should_be_uniform_pointer_type->AsPointer()->storage_class() != + SpvStorageClassUniform) { + return false; + } + auto should_be_uniform_pointer_instruction = + ir_context_->get_def_use_mgr()->GetDef(uniform_variable->type_id()); + auto composite_type = + should_be_uniform_pointer_instruction->GetSingleWordInOperand(1); + + auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, composite_type, + fact.uniform_buffer_element_descriptor().index()); + if (!final_element_type_id) { + return false; + } + auto final_element_type = + ir_context_->get_type_mgr()->GetType(final_element_type_id); + assert(final_element_type && + "There should be a type corresponding to this id."); + + if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) { + return false; + } + auto width = final_element_type->AsFloat() + ? final_element_type->AsFloat()->width() + : final_element_type->AsInteger()->width(); + + if (final_element_type->AsFloat() && + !FloatingPointValueIsSuitable(fact, width)) { + return false; + } + + auto required_words = (width + 32 - 1) / 32; + if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) { + return false; + } + facts_and_type_ids_.emplace_back( + std::pair<protobufs::FactConstantUniform, uint32_t>( + fact, final_element_type_id)); + return true; +} + +const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& +ConstantUniformFacts::GetConstantUniformFactsAndTypes() const { + return facts_and_type_ids_; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager/constant_uniform_facts.h b/source/fuzz/fact_manager/constant_uniform_facts.h new file mode 100644 index 00000000..a136a056 --- /dev/null +++ b/source/fuzz/fact_manager/constant_uniform_facts.h @@ -0,0 +1,90 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ + +#include <vector> + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about uniform constants. +class ConstantUniformFacts { + public: + explicit ConstantUniformFacts(opt::IRContext* ir_context); + + // See method in FactManager which delegates to this method. + bool AddFact(const protobufs::FactConstantUniform& fact); + + // See method in FactManager which delegates to this method. + std::vector<uint32_t> GetConstantsAvailableFromUniformsForType( + uint32_t type_id) const; + + // See method in FactManager which delegates to this method. + std::vector<protobufs::UniformBufferElementDescriptor> + GetUniformDescriptorsForConstant(uint32_t constant_id) const; + + // See method in FactManager which delegates to this method. + uint32_t GetConstantFromUniformDescriptor( + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) + const; + + // See method in FactManager which delegates to this method. + std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const; + + // See method in FactManager which delegates to this method. + const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& + GetConstantUniformFactsAndTypes() const; + + private: + // Returns true if and only if the words associated with + // |constant_instruction| exactly match the words for the constant associated + // with |constant_uniform_fact|. + static bool DataMatches( + const opt::Instruction& constant_instruction, + const protobufs::FactConstantUniform& constant_uniform_fact); + + // Yields the constant words associated with |constant_uniform_fact|. + static std::vector<uint32_t> GetConstantWords( + const protobufs::FactConstantUniform& constant_uniform_fact); + + // Yields the id of a constant of type |type_id| whose data matches the + // constant data in |constant_uniform_fact|, or 0 if no such constant is + // declared. + uint32_t GetConstantId( + const protobufs::FactConstantUniform& constant_uniform_fact, + uint32_t type_id) const; + + // Checks that the width of a floating-point constant is supported, and that + // the constant is finite. + static bool FloatingPointValueIsSuitable( + const protobufs::FactConstantUniform& fact, uint32_t width); + + std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>> + facts_and_type_ids_; + + opt::IRContext* ir_context_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_ diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp new file mode 100644 index 00000000..5fc2d7f0 --- /dev/null +++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp @@ -0,0 +1,892 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +size_t DataSynonymAndIdEquationFacts::OperationHash::operator()( + const Operation& operation) const { + std::u32string hash; + hash.push_back(operation.opcode); + for (auto operand : operation.operands) { + hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand))); + } + return std::hash<std::u32string>()(hash); +} + +bool DataSynonymAndIdEquationFacts::OperationEquals::operator()( + const Operation& first, const Operation& second) const { + // Equal operations require... + // + // Equal opcodes. + if (first.opcode != second.opcode) { + return false; + } + // Matching operand counts. + if (first.operands.size() != second.operands.size()) { + return false; + } + // Equal operands. + for (uint32_t i = 0; i < first.operands.size(); i++) { + if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) { + return false; + } + } + return true; +} + +DataSynonymAndIdEquationFacts::DataSynonymAndIdEquationFacts( + opt::IRContext* ir_context) + : ir_context_(ir_context) {} + +void DataSynonymAndIdEquationFacts::AddFact( + const protobufs::FactDataSynonym& fact, + const DeadBlockFacts& dead_block_facts, + const IrrelevantValueFacts& irrelevant_value_facts) { + (void)dead_block_facts; // Keep release compilers happy. + (void)irrelevant_value_facts; // Keep release compilers happy. + assert(!irrelevant_value_facts.IdIsIrrelevant(fact.data1().object(), + dead_block_facts) && + !irrelevant_value_facts.IdIsIrrelevant(fact.data2().object(), + dead_block_facts) && + "Irrelevant ids cannot be synonymous with other ids."); + + // Add the fact, including all facts relating sub-components of the data + // descriptors that are involved. + AddDataSynonymFactRecursive(fact.data1(), fact.data2()); +} + +void DataSynonymAndIdEquationFacts::AddFact( + const protobufs::FactIdEquation& fact, + const DeadBlockFacts& dead_block_facts, + const IrrelevantValueFacts& irrelevant_value_facts) { + (void)dead_block_facts; // Keep release compilers happy. + (void)irrelevant_value_facts; // Keep release compilers happy. + assert( + !irrelevant_value_facts.IdIsIrrelevant(fact.lhs_id(), dead_block_facts) && + "Irrelevant ids are not allowed."); + + protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {}); + + // Register the LHS in the equivalence relation if needed. + RegisterDataDescriptor(lhs_dd); + + // Get equivalence class representatives for all ids used on the RHS of the + // equation. + std::vector<const protobufs::DataDescriptor*> rhs_dds; + for (auto rhs_id : fact.rhs_id()) { + assert(!irrelevant_value_facts.IdIsIrrelevant(rhs_id, dead_block_facts) && + "Irrelevant ids are not allowed."); + + // Register a data descriptor based on this id in the equivalence relation + // if needed, and then record the equivalence class representative. + rhs_dds.push_back(RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {}))); + } + + // Now add the fact. + AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), rhs_dds); +} + +DataSynonymAndIdEquationFacts::OperationSet +DataSynonymAndIdEquationFacts::GetEquations( + const protobufs::DataDescriptor* lhs) const { + auto existing = id_equations_.find(lhs); + if (existing == id_equations_.end()) { + return OperationSet(); + } + return existing->second; +} + +void DataSynonymAndIdEquationFacts::AddEquationFactRecursive( + const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, + const std::vector<const protobufs::DataDescriptor*>& rhs_dds) { + assert(synonymous_.Exists(lhs_dd) && + "The LHS must be known to the equivalence relation."); + for (auto rhs_dd : rhs_dds) { + // Keep release compilers happy. + (void)(rhs_dd); + assert(synonymous_.Exists(*rhs_dd) && + "The RHS operands must be known to the equivalence relation."); + } + + auto lhs_dd_representative = synonymous_.Find(&lhs_dd); + + if (id_equations_.count(lhs_dd_representative) == 0) { + // We have not seen an equation with this LHS before, so associate the LHS + // with an initially empty set. + id_equations_.insert({lhs_dd_representative, OperationSet()}); + } + + { + auto existing_equations = id_equations_.find(lhs_dd_representative); + assert(existing_equations != id_equations_.end() && + "A set of operations should be present, even if empty."); + + Operation new_operation = {opcode, rhs_dds}; + if (existing_equations->second.count(new_operation)) { + // This equation is known, so there is nothing further to be done. + return; + } + // Add the equation to the set of known equations. + existing_equations->second.insert(new_operation); + } + + // Now try to work out corollaries implied by the new equation and existing + // facts. + switch (opcode) { + case SpvOpConvertSToF: + case SpvOpConvertUToF: + ComputeConversionDataSynonymFacts(*rhs_dds[0]); + break; + case SpvOpBitcast: { + assert(DataDescriptorsAreWellFormedAndComparable(lhs_dd, *rhs_dds[0]) && + "Operands of OpBitcast equation fact must have compatible types"); + if (!synonymous_.IsEquivalent(lhs_dd, *rhs_dds[0])) { + AddDataSynonymFactRecursive(lhs_dd, *rhs_dds[0]); + } + } break; + case SpvOpIAdd: { + // Equation form: "a = b + c" + for (const auto& equation : GetEquations(rhs_dds[0])) { + if (equation.opcode == SpvOpISub) { + // Equation form: "a = (d - e) + c" + if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { + // Equation form: "a = (d - c) + c" + // We can thus infer "a = d" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]); + } + if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { + // Equation form: "a = (c - e) + c" + // We can thus infer "a = -e" + AddEquationFactRecursive(lhs_dd, SpvOpSNegate, + {equation.operands[1]}); + } + } + } + for (const auto& equation : GetEquations(rhs_dds[1])) { + if (equation.opcode == SpvOpISub) { + // Equation form: "a = b + (d - e)" + if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { + // Equation form: "a = b + (d - b)" + // We can thus infer "a = d" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]); + } + } + } + break; + } + case SpvOpISub: { + // Equation form: "a = b - c" + for (const auto& equation : GetEquations(rhs_dds[0])) { + if (equation.opcode == SpvOpIAdd) { + // Equation form: "a = (d + e) - c" + if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { + // Equation form: "a = (c + e) - c" + // We can thus infer "a = e" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1]); + } + if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) { + // Equation form: "a = (d + c) - c" + // We can thus infer "a = d" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]); + } + } + + if (equation.opcode == SpvOpISub) { + // Equation form: "a = (d - e) - c" + if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) { + // Equation form: "a = (c - e) - c" + // We can thus infer "a = -e" + AddEquationFactRecursive(lhs_dd, SpvOpSNegate, + {equation.operands[1]}); + } + } + } + + for (const auto& equation : GetEquations(rhs_dds[1])) { + if (equation.opcode == SpvOpIAdd) { + // Equation form: "a = b - (d + e)" + if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { + // Equation form: "a = b - (b + e)" + // We can thus infer "a = -e" + AddEquationFactRecursive(lhs_dd, SpvOpSNegate, + {equation.operands[1]}); + } + if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) { + // Equation form: "a = b - (d + b)" + // We can thus infer "a = -d" + AddEquationFactRecursive(lhs_dd, SpvOpSNegate, + {equation.operands[0]}); + } + } + if (equation.opcode == SpvOpISub) { + // Equation form: "a = b - (d - e)" + if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) { + // Equation form: "a = b - (b - e)" + // We can thus infer "a = e" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1]); + } + } + } + break; + } + case SpvOpLogicalNot: + case SpvOpSNegate: { + // Equation form: "a = !b" or "a = -b" + for (const auto& equation : GetEquations(rhs_dds[0])) { + if (equation.opcode == opcode) { + // Equation form: "a = !!b" or "a = -(-b)" + // We can thus infer "a = b" + AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]); + } + } + break; + } + default: + break; + } +} + +void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive( + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) { + assert(DataDescriptorsAreWellFormedAndComparable(dd1, dd2)); + + // Record that the data descriptors provided in the fact are equivalent. + MakeEquivalent(dd1, dd2); + assert(synonymous_.Find(&dd1) == synonymous_.Find(&dd2) && + "|dd1| and |dd2| must have a single representative"); + + // Compute various corollary facts. + + // |dd1| and |dd2| belong to the same equivalence class so it doesn't matter + // which one we use here. + ComputeConversionDataSynonymFacts(dd1); + + ComputeCompositeDataSynonymFacts(dd1, dd2); +} + +void DataSynonymAndIdEquationFacts::ComputeConversionDataSynonymFacts( + const protobufs::DataDescriptor& dd) { + assert(synonymous_.Exists(dd) && + "|dd| should've been registered in the equivalence relation"); + + const auto* type = + ir_context_->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices( + ir_context_, fuzzerutil::GetTypeId(ir_context_, dd.object()), + dd.index())); + assert(type && "Data descriptor has invalid type"); + + if ((type->AsVector() && type->AsVector()->element_type()->AsInteger()) || + type->AsInteger()) { + // If there exist equation facts of the form |%a = opcode %representative| + // and |%b = opcode %representative| where |opcode| is either OpConvertSToF + // or OpConvertUToF, then |a| and |b| are synonymous. + std::vector<const protobufs::DataDescriptor*> convert_s_to_f_lhs; + std::vector<const protobufs::DataDescriptor*> convert_u_to_f_lhs; + + for (const auto& fact : id_equations_) { + auto equivalence_class = synonymous_.GetEquivalenceClass(*fact.first); + auto dd_it = std::find_if( + equivalence_class.begin(), equivalence_class.end(), + [this](const protobufs::DataDescriptor* a) { + return ir_context_->get_def_use_mgr()->GetDef(a->object()) != + nullptr; + }); + if (dd_it == equivalence_class.end()) { + // Skip |equivalence_class| if it has no valid ids. + continue; + } + + for (const auto& equation : fact.second) { + if (synonymous_.IsEquivalent(*equation.operands[0], dd)) { + if (equation.opcode == SpvOpConvertSToF) { + convert_s_to_f_lhs.push_back(*dd_it); + } else if (equation.opcode == SpvOpConvertUToF) { + convert_u_to_f_lhs.push_back(*dd_it); + } + } + } + } + + // We use pointers in the initializer list here since otherwise we would + // copy memory from these vectors. + for (const auto* synonyms : {&convert_s_to_f_lhs, &convert_u_to_f_lhs}) { + for (const auto* synonym_a : *synonyms) { + for (const auto* synonym_b : *synonyms) { + // DataDescriptorsAreWellFormedAndComparable will be called in the + // AddDataSynonymFactRecursive method. + if (!synonymous_.IsEquivalent(*synonym_a, *synonym_b)) { + // |synonym_a| and |synonym_b| have compatible types - they are + // synonymous. + AddDataSynonymFactRecursive(*synonym_a, *synonym_b); + } + } + } + } + } +} + +void DataSynonymAndIdEquationFacts::ComputeCompositeDataSynonymFacts( + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) { + // Check whether this is a synonym about composite objects. If it is, + // we can recursively add synonym facts about their associated sub-components. + + // Get the type of the object referred to by the first data descriptor in the + // synonym fact. + uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, + ir_context_->get_def_use_mgr()->GetDef(dd1.object())->type_id(), + dd1.index()); + auto type = ir_context_->get_type_mgr()->GetType(type_id); + auto type_instruction = ir_context_->get_def_use_mgr()->GetDef(type_id); + assert(type != nullptr && + "Invalid data synonym fact: one side has an unknown type."); + + // Check whether the type is composite, recording the number of elements + // associated with the composite if so. + uint32_t num_composite_elements; + if (type->AsArray()) { + num_composite_elements = + fuzzerutil::GetArraySize(*type_instruction, ir_context_); + } else if (type->AsMatrix()) { + num_composite_elements = type->AsMatrix()->element_count(); + } else if (type->AsStruct()) { + num_composite_elements = + fuzzerutil::GetNumberOfStructMembers(*type_instruction); + } else if (type->AsVector()) { + num_composite_elements = type->AsVector()->element_count(); + } else { + // The type is not a composite, so return. + return; + } + + // If the fact has the form: + // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] + // then for each composite index i, we add a fact of the form: + // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] + // + // However, to avoid adding a large number of synonym facts e.g. in the case + // of arrays, we bound the number of composite elements to which this is + // applied. Nevertheless, we always add a synonym fact for the final + // components, as this may be an interesting edge case. + + // The bound on the number of indices of the composite pair to note as being + // synonymous. + const uint32_t kCompositeElementBound = 10; + + for (uint32_t i = 0; i < num_composite_elements;) { + std::vector<uint32_t> extended_indices1 = + fuzzerutil::RepeatedFieldToVector(dd1.index()); + extended_indices1.push_back(i); + std::vector<uint32_t> extended_indices2 = + fuzzerutil::RepeatedFieldToVector(dd2.index()); + extended_indices2.push_back(i); + AddDataSynonymFactRecursive( + MakeDataDescriptor(dd1.object(), std::move(extended_indices1)), + MakeDataDescriptor(dd2.object(), std::move(extended_indices2))); + + if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) { + // We have not reached the bound yet, or have already skipped ahead to the + // last element, so increment the loop counter as standard. + i++; + } else { + // We have reached the bound, so skip ahead to the last element. + assert(i == kCompositeElementBound - 1); + i = num_composite_elements - 1; + } + } +} + +void DataSynonymAndIdEquationFacts::ComputeClosureOfFacts( + uint32_t maximum_equivalence_class_size) { + // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct + // data descriptors that describe objects of the same composite type, and that + // the composite type is comprised of k components. + // + // For example, if m is a mat4x4 and v a vec4, we might consider: + // m[2]: describes the 2nd column of m, a vec4 + // v[]: describes all of v, a vec4 + // + // Suppose that we know, for every 0 <= i < k, that the fact: + // obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i] + // holds - i.e. that the children of the two data descriptors are synonymous. + // + // Then we can conclude that: + // obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n] + // holds. + // + // For instance, if we have the facts: + // m[2, 0] == v[0] + // m[2, 1] == v[1] + // m[2, 2] == v[2] + // m[2, 3] == v[3] + // then we can conclude that: + // m[2] == v. + // + // This method repeatedly searches the equivalence relation of data + // descriptors, deducing and adding such facts, until a pass over the + // relation leads to no further facts being deduced. + + // The method relies on working with pairs of data descriptors, and in + // particular being able to hash and compare such pairs. + + using DataDescriptorPair = + std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>; + + struct DataDescriptorPairHash { + std::size_t operator()(const DataDescriptorPair& pair) const { + return DataDescriptorHash()(&pair.first) ^ + DataDescriptorHash()(&pair.second); + } + }; + + struct DataDescriptorPairEquals { + bool operator()(const DataDescriptorPair& first, + const DataDescriptorPair& second) const { + return (DataDescriptorEquals()(&first.first, &second.first) && + DataDescriptorEquals()(&first.second, &second.second)) || + (DataDescriptorEquals()(&first.first, &second.second) && + DataDescriptorEquals()(&first.second, &second.first)); + } + }; + + // This map records, for a given pair of composite data descriptors of the + // same type, all the indices at which the data descriptors are known to be + // synonymous. A pair is a key to this map only if we have observed that + // the pair are synonymous at *some* index, but not at *all* indices. + // Once we find that a pair of data descriptors are equivalent at all indices + // we record the fact that they are synonymous and remove them from the map. + // + // Using the m and v example from above, initially the pair (m[2], v) would + // not be a key to the map. If we find that m[2, 2] == v[2] holds, we would + // add an entry: + // (m[2], v) -> [false, false, true, false] + // to record that they are synonymous at index 2. If we then find that + // m[2, 0] == v[0] holds, we would update this entry to: + // (m[2], v) -> [true, false, true, false] + // If we then find that m[2, 3] == v[3] holds, we would update this entry to: + // (m[2], v) -> [true, false, true, true] + // Finally, if we then find that m[2, 1] == v[1] holds, which would make the + // boolean vector true at every index, we would add the fact: + // m[2] == v + // to the equivalence relation and remove (m[2], v) from the map. + std::unordered_map<DataDescriptorPair, std::vector<bool>, + DataDescriptorPairHash, DataDescriptorPairEquals> + candidate_composite_synonyms; + + // We keep looking for new facts until we perform a complete pass over the + // equivalence relation without finding any new facts. + while (closure_computation_required_) { + // We have not found any new facts yet during this pass; we set this to + // 'true' if we do find a new fact. + closure_computation_required_ = false; + + // Consider each class in the equivalence relation. + for (auto representative : + synonymous_.GetEquivalenceClassRepresentatives()) { + auto equivalence_class = synonymous_.GetEquivalenceClass(*representative); + + if (equivalence_class.size() > maximum_equivalence_class_size) { + // This equivalence class is larger than the maximum size we are willing + // to consider, so we skip it. This potentially leads to missed fact + // deductions, but avoids excessive runtime for closure computation. + continue; + } + + // Consider every data descriptor in the equivalence class. + for (auto dd1_it = equivalence_class.begin(); + dd1_it != equivalence_class.end(); ++dd1_it) { + // If this data descriptor has no indices then it does not have the form + // obj_1[a_1, ..., a_m, i], so move on. + auto dd1 = *dd1_it; + if (dd1->index_size() == 0) { + continue; + } + + // Consider every other data descriptor later in the equivalence class + // (due to symmetry, there is no need to compare with previous data + // descriptors). + auto dd2_it = dd1_it; + for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) { + auto dd2 = *dd2_it; + // If this data descriptor has no indices then it does not have the + // form obj_2[b_1, ..., b_n, i], so move on. + if (dd2->index_size() == 0) { + continue; + } + + // At this point we know that: + // - |dd1| has the form obj_1[a_1, ..., a_m, i] + // - |dd2| has the form obj_2[b_1, ..., b_n, j] + assert(dd1->index_size() > 0 && dd2->index_size() > 0 && + "Control should not reach here if either data descriptor has " + "no indices."); + + // We are only interested if i == j. + if (dd1->index(dd1->index_size() - 1) != + dd2->index(dd2->index_size() - 1)) { + continue; + } + + const uint32_t common_final_index = dd1->index(dd1->index_size() - 1); + + // Make data descriptors |dd1_prefix| and |dd2_prefix| for + // obj_1[a_1, ..., a_m] + // and + // obj_2[b_1, ..., b_n] + // These are the two data descriptors we might be getting closer to + // deducing as being synonymous, due to knowing that they are + // synonymous when extended by a particular index. + protobufs::DataDescriptor dd1_prefix; + dd1_prefix.set_object(dd1->object()); + for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1); + i++) { + dd1_prefix.add_index(dd1->index(i)); + } + protobufs::DataDescriptor dd2_prefix; + dd2_prefix.set_object(dd2->object()); + for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1); + i++) { + dd2_prefix.add_index(dd2->index(i)); + } + assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) && + "By construction these prefixes should be different."); + + // If we already know that these prefixes are synonymous, move on. + if (synonymous_.Exists(dd1_prefix) && + synonymous_.Exists(dd2_prefix) && + synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) { + continue; + } + + // Get the type of obj_1 + auto dd1_root_type_id = + ir_context_->get_def_use_mgr()->GetDef(dd1->object())->type_id(); + // Use this type, together with a_1, ..., a_m, to get the type of + // obj_1[a_1, ..., a_m]. + auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, dd1_root_type_id, dd1_prefix.index()); + + // Similarly, get the type of obj_2 and use it to get the type of + // obj_2[b_1, ..., b_n]. + auto dd2_root_type_id = + ir_context_->get_def_use_mgr()->GetDef(dd2->object())->type_id(); + auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, dd2_root_type_id, dd2_prefix.index()); + + // If the types of dd1_prefix and dd2_prefix are not the same, they + // cannot be synonymous. + if (dd1_prefix_type != dd2_prefix_type) { + continue; + } + + // At this point, we know we have synonymous data descriptors of the + // form: + // obj_1[a_1, ..., a_m, i] + // obj_2[b_1, ..., b_n, i] + // with the same last_index i, such that: + // obj_1[a_1, ..., a_m] + // and + // obj_2[b_1, ..., b_n] + // have the same type. + + // Work out how many components there are in the (common) commposite + // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n]. + // This depends on whether the composite type is array, matrix, struct + // or vector. + uint32_t num_components_in_composite; + auto composite_type = + ir_context_->get_type_mgr()->GetType(dd1_prefix_type); + auto composite_type_instruction = + ir_context_->get_def_use_mgr()->GetDef(dd1_prefix_type); + if (composite_type->AsArray()) { + num_components_in_composite = fuzzerutil::GetArraySize( + *composite_type_instruction, ir_context_); + if (num_components_in_composite == 0) { + // This indicates that the array has an unknown size, in which + // case we cannot be sure we have matched all of its elements with + // synonymous elements of another array. + continue; + } + } else if (composite_type->AsMatrix()) { + num_components_in_composite = + composite_type->AsMatrix()->element_count(); + } else if (composite_type->AsStruct()) { + num_components_in_composite = fuzzerutil::GetNumberOfStructMembers( + *composite_type_instruction); + } else { + assert(composite_type->AsVector()); + num_components_in_composite = + composite_type->AsVector()->element_count(); + } + + // We are one step closer to being able to say that |dd1_prefix| and + // |dd2_prefix| are synonymous. + DataDescriptorPair candidate_composite_synonym(dd1_prefix, + dd2_prefix); + + // We look up what we already know about this pair. + auto existing_entry = + candidate_composite_synonyms.find(candidate_composite_synonym); + + if (existing_entry == candidate_composite_synonyms.end()) { + // If this is the first time we have seen the pair, we make a vector + // of size |num_components_in_composite| that is 'true' at the + // common final index associated with |dd1| and |dd2|, and 'false' + // everywhere else, and register this vector as being associated + // with the pair. + std::vector<bool> entry; + for (uint32_t i = 0; i < num_components_in_composite; i++) { + entry.push_back(i == common_final_index); + } + candidate_composite_synonyms[candidate_composite_synonym] = entry; + existing_entry = + candidate_composite_synonyms.find(candidate_composite_synonym); + } else { + // We have seen this pair of data descriptors before, and we now + // know that they are synonymous at one further index, so we + // update the entry to record that. + existing_entry->second[common_final_index] = true; + } + assert(existing_entry != candidate_composite_synonyms.end()); + + // Check whether |dd1_prefix| and |dd2_prefix| are now known to match + // at every sub-component. + bool all_components_match = true; + for (uint32_t i = 0; i < num_components_in_composite; i++) { + if (!existing_entry->second[i]) { + all_components_match = false; + break; + } + } + if (all_components_match) { + // The two prefixes match on all sub-components, so we know that + // they are synonymous. We add this fact *non-recursively*, as we + // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous + // by observing that all their sub-components are already + // synonymous. + assert(DataDescriptorsAreWellFormedAndComparable(dd1_prefix, + dd2_prefix)); + MakeEquivalent(dd1_prefix, dd2_prefix); + // Now that we know this pair of data descriptors are synonymous, + // there is no point recording how close they are to being + // synonymous. + candidate_composite_synonyms.erase(candidate_composite_synonym); + } + } + } + } + } +} + +void DataSynonymAndIdEquationFacts::MakeEquivalent( + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) { + // Register the data descriptors if they are not already known to the + // equivalence relation. + RegisterDataDescriptor(dd1); + RegisterDataDescriptor(dd2); + + if (synonymous_.IsEquivalent(dd1, dd2)) { + // The data descriptors are already known to be equivalent, so there is + // nothing to do. + return; + } + + // We must make the data descriptors equivalent, and also make sure any + // equation facts known about their representatives are merged. + + // Record the original equivalence class representatives of the data + // descriptors. + auto dd1_original_representative = synonymous_.Find(&dd1); + auto dd2_original_representative = synonymous_.Find(&dd2); + + // Make the data descriptors equivalent. + synonymous_.MakeEquivalent(dd1, dd2); + // As we have updated the equivalence relation, we might be able to deduce + // more facts by performing a closure computation, so we record that such a + // computation is required. + closure_computation_required_ = true; + + // At this point, exactly one of |dd1_original_representative| and + // |dd2_original_representative| will be the representative of the combined + // equivalence class. We work out which one of them is still the class + // representative and which one is no longer the class representative. + + auto still_representative = synonymous_.Find(dd1_original_representative) == + dd1_original_representative + ? dd1_original_representative + : dd2_original_representative; + auto no_longer_representative = + still_representative == dd1_original_representative + ? dd2_original_representative + : dd1_original_representative; + + assert(no_longer_representative != still_representative && + "The current and former representatives cannot be the same."); + + // We now need to add all equations about |no_longer_representative| to the + // set of equations known about |still_representative|. + + // Get the equations associated with |no_longer_representative|. + auto no_longer_representative_id_equations = + id_equations_.find(no_longer_representative); + if (no_longer_representative_id_equations != id_equations_.end()) { + // There are some equations to transfer. There might not yet be any + // equations about |still_representative|; create an empty set of equations + // if this is the case. + if (!id_equations_.count(still_representative)) { + id_equations_.insert({still_representative, OperationSet()}); + } + auto still_representative_id_equations = + id_equations_.find(still_representative); + assert(still_representative_id_equations != id_equations_.end() && + "At this point there must be a set of equations."); + // Add all the equations known about |no_longer_representative| to the set + // of equations known about |still_representative|. + still_representative_id_equations->second.insert( + no_longer_representative_id_equations->second.begin(), + no_longer_representative_id_equations->second.end()); + } + // Delete the no longer-relevant equations about |no_longer_representative|. + id_equations_.erase(no_longer_representative); +} + +const protobufs::DataDescriptor* +DataSynonymAndIdEquationFacts::RegisterDataDescriptor( + const protobufs::DataDescriptor& dd) { + return synonymous_.Exists(dd) ? synonymous_.Find(&dd) + : synonymous_.Register(dd); +} + +bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable( + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) const { + assert(ir_context_->get_def_use_mgr()->GetDef(dd1.object()) && + ir_context_->get_def_use_mgr()->GetDef(dd2.object()) && + "Both descriptors must exist in the module"); + + auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, fuzzerutil::GetTypeId(ir_context_, dd1.object()), + dd1.index()); + auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices( + ir_context_, fuzzerutil::GetTypeId(ir_context_, dd2.object()), + dd2.index()); + // The end types of the data descriptors must exist. + if (end_type_id_1 == 0 || end_type_id_2 == 0) { + return false; + } + // Neither end type is allowed to be void. + if (ir_context_->get_def_use_mgr()->GetDef(end_type_id_1)->opcode() == + SpvOpTypeVoid || + ir_context_->get_def_use_mgr()->GetDef(end_type_id_2)->opcode() == + SpvOpTypeVoid) { + return false; + } + // If the end types are the same, the data descriptors are comparable. + if (end_type_id_1 == end_type_id_2) { + return true; + } + // Otherwise they are only comparable if they are integer scalars or integer + // vectors that differ only in signedness. + + // Get both types. + const auto* type_a = ir_context_->get_type_mgr()->GetType(end_type_id_1); + const auto* type_b = ir_context_->get_type_mgr()->GetType(end_type_id_2); + assert(type_a && type_b && "Data descriptors have invalid type(s)"); + + // If both types are numerical or vectors of numerical components, then they + // are compatible if they have the same number of components and the same bit + // count per component. + + if (type_a->AsVector() && type_b->AsVector()) { + const auto* vector_a = type_a->AsVector(); + const auto* vector_b = type_b->AsVector(); + + if (vector_a->element_count() != vector_b->element_count() || + vector_a->element_type()->AsBool() || + vector_b->element_type()->AsBool()) { + // The case where both vectors have boolean elements and the same number + // of components is handled by the direct equality check earlier. + // You can't have multiple identical boolean vector types. + return false; + } + + type_a = vector_a->element_type(); + type_b = vector_b->element_type(); + } + + auto get_bit_count_for_numeric_type = + [](const opt::analysis::Type& type) -> uint32_t { + if (const auto* integer = type.AsInteger()) { + return integer->width(); + } else if (const auto* floating = type.AsFloat()) { + return floating->width(); + } else { + assert(false && "|type| must be a numerical type"); + return 0; + } + }; + + // Checks that both |type_a| and |type_b| are either numerical or vectors of + // numerical components and have the same number of bits. + return (type_a->AsInteger() || type_a->AsFloat()) && + (type_b->AsInteger() || type_b->AsFloat()) && + (get_bit_count_for_numeric_type(*type_a) == + get_bit_count_for_numeric_type(*type_b)); +} + +std::vector<const protobufs::DataDescriptor*> +DataSynonymAndIdEquationFacts::GetSynonymsForId(uint32_t id) const { + return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {})); +} + +std::vector<const protobufs::DataDescriptor*> +DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor( + const protobufs::DataDescriptor& data_descriptor) const { + if (synonymous_.Exists(data_descriptor)) { + return synonymous_.GetEquivalenceClass(data_descriptor); + } + return {}; +} + +std::vector<uint32_t> +DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const { + std::vector<uint32_t> result; + for (auto& data_descriptor : synonymous_.GetAllKnownValues()) { + if (data_descriptor->index().empty()) { + result.push_back(data_descriptor->object()); + } + } + return result; +} + +bool DataSynonymAndIdEquationFacts::IsSynonymous( + const protobufs::DataDescriptor& data_descriptor1, + const protobufs::DataDescriptor& data_descriptor2) const { + return synonymous_.Exists(data_descriptor1) && + synonymous_.Exists(data_descriptor2) && + synonymous_.IsEquivalent(data_descriptor1, data_descriptor2); +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h new file mode 100644 index 00000000..e84632b3 --- /dev/null +++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h @@ -0,0 +1,173 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ + +#include <unordered_set> +#include <vector> + +#include "source/fuzz/data_descriptor.h" +#include "source/fuzz/equivalence_relation.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// Forward reference to the DeadBlockFacts class. +class DeadBlockFacts; +// Forward reference to the IrrelevantValueFacts class. +class IrrelevantValueFacts; + +// The purpose of this class is to group the fields and data used to represent +// facts about data synonyms and id equations. +class DataSynonymAndIdEquationFacts { + public: + explicit DataSynonymAndIdEquationFacts(opt::IRContext* ir_context); + + // See method in FactManager which delegates to this method. + // |dead_block_facts| and |irrelevant_value_facts| are passed for consistency + // checks. + void AddFact(const protobufs::FactDataSynonym& fact, + const DeadBlockFacts& dead_block_facts, + const IrrelevantValueFacts& irrelevant_value_facts); + + // See method in FactManager which delegates to this method. + // |dead_block_facts| and |irrelevant_value_facts| are passed for consistency + // checks. + void AddFact(const protobufs::FactIdEquation& fact, + const DeadBlockFacts& dead_block_facts, + const IrrelevantValueFacts& irrelevant_value_facts); + + // See method in FactManager which delegates to this method. + std::vector<const protobufs::DataDescriptor*> GetSynonymsForId( + uint32_t id) const; + + // See method in FactManager which delegates to this method. + std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor( + const protobufs::DataDescriptor& data_descriptor) const; + + // See method in FactManager which delegates to this method. + std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const; + + // See method in FactManager which delegates to this method. + bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1, + const protobufs::DataDescriptor& data_descriptor2) const; + + // See method in FactManager which delegates to this method. + void ComputeClosureOfFacts(uint32_t maximum_equivalence_class_size); + + private: + // This helper struct represents the right hand side of an equation as an + // operator applied to a number of data descriptor operands. + struct Operation { + SpvOp opcode; + std::vector<const protobufs::DataDescriptor*> operands; + }; + + // Hashing for operations, to allow deterministic unordered sets. + struct OperationHash { + size_t operator()(const Operation& operation) const; + }; + + // Equality for operations, to allow deterministic unordered sets. + struct OperationEquals { + bool operator()(const Operation& first, const Operation& second) const; + }; + + using OperationSet = + std::unordered_set<Operation, OperationHash, OperationEquals>; + + // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses + // into sub-components of the data descriptors, if they are composites, to + // record that their components are pairwise-synonymous. + void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); + + // Computes various corollary facts from the data descriptor |dd| if members + // of its equivalence class participate in equation facts with OpConvert* + // opcodes. The descriptor should be registered in the equivalence relation. + void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd); + + // Recurses into sub-components of the data descriptors, if they are + // composites, to record that their components are pairwise-synonymous. + void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); + + // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets + // of equations that are known about them. + void MakeEquivalent(const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); + + // Registers a data descriptor in the equivalence relation if it hasn't been + // registered yet, and returns its representative. + const protobufs::DataDescriptor* RegisterDataDescriptor( + const protobufs::DataDescriptor& dd); + + // Returns true if and only if |dd1| and |dd2| are valid data descriptors + // whose associated data have compatible types. Two types are compatible if: + // - they are the same + // - they both are numerical or vectors of numerical components with the same + // number of components and the same bit count per component + bool DataDescriptorsAreWellFormedAndComparable( + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) const; + + OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const; + + // Requires that |lhs_dd| and every element of |rhs_dds| is present in the + // |synonymous_| equivalence relation, but is not necessarily its own + // representative. Records the fact that the equation + // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any + // corollaries, in the form of data synonym or equation facts, that follow + // from this and other known facts. + void AddEquationFactRecursive( + const protobufs::DataDescriptor& lhs_dd, SpvOp opcode, + const std::vector<const protobufs::DataDescriptor*>& rhs_dds); + + // The data descriptors that are known to be synonymous with one another are + // captured by this equivalence relation. + EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash, + DataDescriptorEquals> + synonymous_; + + // When a new synonym fact is added, it may be possible to deduce further + // synonym facts by computing a closure of all known facts. However, this is + // an expensive operation, so it should be performed sparingly and only there + // is some chance of new facts being deduced. This boolean tracks whether a + // closure computation is required - i.e., whether a new fact has been added + // since the last time such a computation was performed. + bool closure_computation_required_ = false; + + // Represents a set of equations on data descriptors as a map indexed by + // left-hand-side, mapping a left-hand-side to a set of operations, each of + // which (together with the left-hand-side) defines an equation. + // + // All data descriptors occurring in equations are required to be present in + // the |synonymous_| equivalence relation, and to be their own representatives + // in that relation. + std::unordered_map<const protobufs::DataDescriptor*, OperationSet> + id_equations_; + + // Pointer to the SPIR-V module we store facts about. + opt::IRContext* ir_context_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_ diff --git a/source/fuzz/fact_manager/dead_block_facts.cpp b/source/fuzz/fact_manager/dead_block_facts.cpp new file mode 100644 index 00000000..5f4f8bc2 --- /dev/null +++ b/source/fuzz/fact_manager/dead_block_facts.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fact_manager/dead_block_facts.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +void DeadBlockFacts::AddFact(const protobufs::FactBlockIsDead& fact) { + dead_block_ids_.insert(fact.block_id()); +} + +bool DeadBlockFacts::BlockIsDead(uint32_t block_id) const { + return dead_block_ids_.count(block_id) != 0; +} + +const std::unordered_set<uint32_t>& DeadBlockFacts::GetDeadBlocks() const { + return dead_block_ids_; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager/dead_block_facts.h b/source/fuzz/fact_manager/dead_block_facts.h new file mode 100644 index 00000000..b2da90bf --- /dev/null +++ b/source/fuzz/fact_manager/dead_block_facts.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_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ + +#include <unordered_set> + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about data blocks. +class DeadBlockFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactBlockIsDead& fact); + + // See method in FactManager which delegates to this method. + bool BlockIsDead(uint32_t block_id) const; + + // Returns a set of all the block ids that have been declared dead. + const std::unordered_set<uint32_t>& GetDeadBlocks() const; + + private: + std::unordered_set<uint32_t> dead_block_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_ diff --git a/source/fuzz/fact_manager/fact_manager.cpp b/source/fuzz/fact_manager/fact_manager.cpp new file mode 100644 index 00000000..425b0cca --- /dev/null +++ b/source/fuzz/fact_manager/fact_manager.cpp @@ -0,0 +1,249 @@ +// 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 "fact_manager.h" + +#include <sstream> +#include <unordered_map> + +#include "source/fuzz/uniform_buffer_element_descriptor.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string ToString(const protobufs::FactConstantUniform& fact) { + std::stringstream stream; + stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set() + << ", " << fact.uniform_buffer_element_descriptor().binding() << ")["; + + bool first = true; + for (auto index : fact.uniform_buffer_element_descriptor().index()) { + if (first) { + first = false; + } else { + stream << ", "; + } + stream << index; + } + + stream << "] == ["; + + first = true; + for (auto constant_word : fact.constant_word()) { + if (first) { + first = false; + } else { + stream << ", "; + } + stream << constant_word; + } + + stream << "]"; + return stream.str(); +} + +std::string ToString(const protobufs::FactDataSynonym& fact) { + std::stringstream stream; + stream << fact.data1() << " = " << fact.data2(); + return stream.str(); +} + +std::string ToString(const protobufs::FactIdEquation& fact) { + std::stringstream stream; + stream << fact.lhs_id(); + stream << " " << static_cast<SpvOp>(fact.opcode()); + for (auto rhs_id : fact.rhs_id()) { + stream << " " << rhs_id; + } + return stream.str(); +} + +std::string ToString(const protobufs::Fact& fact) { + switch (fact.fact_case()) { + case protobufs::Fact::kConstantUniformFact: + return ToString(fact.constant_uniform_fact()); + case protobufs::Fact::kDataSynonymFact: + return ToString(fact.data_synonym_fact()); + case protobufs::Fact::kIdEquationFact: + return ToString(fact.id_equation_fact()); + default: + assert(false && "Stringification not supported for this fact."); + return ""; + } +} + +} // namespace + +FactManager::FactManager(opt::IRContext* ir_context) + : constant_uniform_facts_(ir_context), + data_synonym_and_id_equation_facts_(ir_context), + dead_block_facts_(), + livesafe_function_facts_(), + irrelevant_value_facts_(ir_context) {} + +void FactManager::AddFacts(const MessageConsumer& message_consumer, + const protobufs::FactSequence& initial_facts) { + for (auto& fact : initial_facts.fact()) { + if (!AddFact(fact)) { + auto message = "Invalid fact " + ToString(fact) + " ignored."; + message_consumer(SPV_MSG_WARNING, nullptr, {}, message.c_str()); + } + } +} + +bool FactManager::AddFact(const fuzz::protobufs::Fact& fact) { + switch (fact.fact_case()) { + case protobufs::Fact::kConstantUniformFact: + return constant_uniform_facts_.AddFact(fact.constant_uniform_fact()); + case protobufs::Fact::kDataSynonymFact: + data_synonym_and_id_equation_facts_.AddFact( + fact.data_synonym_fact(), dead_block_facts_, irrelevant_value_facts_); + return true; + case protobufs::Fact::kBlockIsDeadFact: + dead_block_facts_.AddFact(fact.block_is_dead_fact()); + return true; + case protobufs::Fact::kFunctionIsLivesafeFact: + livesafe_function_facts_.AddFact(fact.function_is_livesafe_fact()); + return true; + default: + assert(false && "Unknown fact type."); + return false; + } +} + +void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1, + const protobufs::DataDescriptor& data2) { + protobufs::FactDataSynonym fact; + *fact.mutable_data1() = data1; + *fact.mutable_data2() = data2; + data_synonym_and_id_equation_facts_.AddFact(fact, dead_block_facts_, + irrelevant_value_facts_); +} + +std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType( + uint32_t type_id) const { + return constant_uniform_facts_.GetConstantsAvailableFromUniformsForType( + type_id); +} + +std::vector<protobufs::UniformBufferElementDescriptor> +FactManager::GetUniformDescriptorsForConstant(uint32_t constant_id) const { + return constant_uniform_facts_.GetUniformDescriptorsForConstant(constant_id); +} + +uint32_t FactManager::GetConstantFromUniformDescriptor( + const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const { + return constant_uniform_facts_.GetConstantFromUniformDescriptor( + uniform_descriptor); +} + +std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown() + const { + return constant_uniform_facts_.GetTypesForWhichUniformValuesAreKnown(); +} + +const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>& +FactManager::GetConstantUniformFactsAndTypes() const { + return constant_uniform_facts_.GetConstantUniformFactsAndTypes(); +} + +std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const { + return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown(); +} + +std::vector<const protobufs::DataDescriptor*> +FactManager::GetSynonymsForDataDescriptor( + const protobufs::DataDescriptor& data_descriptor) const { + return data_synonym_and_id_equation_facts_.GetSynonymsForDataDescriptor( + data_descriptor); +} + +std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId( + uint32_t id) const { + return data_synonym_and_id_equation_facts_.GetSynonymsForId(id); +} + +bool FactManager::IsSynonymous( + const protobufs::DataDescriptor& data_descriptor1, + const protobufs::DataDescriptor& data_descriptor2) const { + return data_synonym_and_id_equation_facts_.IsSynonymous(data_descriptor1, + data_descriptor2); +} + +bool FactManager::BlockIsDead(uint32_t block_id) const { + return dead_block_facts_.BlockIsDead(block_id); +} + +void FactManager::AddFactBlockIsDead(uint32_t block_id) { + protobufs::FactBlockIsDead fact; + fact.set_block_id(block_id); + dead_block_facts_.AddFact(fact); +} + +bool FactManager::FunctionIsLivesafe(uint32_t function_id) const { + return livesafe_function_facts_.FunctionIsLivesafe(function_id); +} + +void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) { + protobufs::FactFunctionIsLivesafe fact; + fact.set_function_id(function_id); + livesafe_function_facts_.AddFact(fact); +} + +bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const { + return irrelevant_value_facts_.PointeeValueIsIrrelevant(pointer_id); +} + +bool FactManager::IdIsIrrelevant(uint32_t result_id) const { + return irrelevant_value_facts_.IdIsIrrelevant(result_id, dead_block_facts_); +} + +std::unordered_set<uint32_t> FactManager::GetIrrelevantIds() const { + return irrelevant_value_facts_.GetIrrelevantIds(dead_block_facts_); +} + +void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) { + protobufs::FactPointeeValueIsIrrelevant fact; + fact.set_pointer_id(pointer_id); + irrelevant_value_facts_.AddFact(fact, data_synonym_and_id_equation_facts_); +} + +void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) { + protobufs::FactIdIsIrrelevant fact; + fact.set_result_id(result_id); + irrelevant_value_facts_.AddFact(fact, data_synonym_and_id_equation_facts_); +} + +void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, + const std::vector<uint32_t>& rhs_id) { + protobufs::FactIdEquation fact; + fact.set_lhs_id(lhs_id); + fact.set_opcode(opcode); + for (auto an_rhs_id : rhs_id) { + fact.add_rhs_id(an_rhs_id); + } + data_synonym_and_id_equation_facts_.AddFact(fact, dead_block_facts_, + irrelevant_value_facts_); +} + +void FactManager::ComputeClosureOfFacts( + uint32_t maximum_equivalence_class_size) { + data_synonym_and_id_equation_facts_.ComputeClosureOfFacts( + maximum_equivalence_class_size); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager/fact_manager.h index f83e2ff6..d3758b14 100644 --- a/source/fuzz/fact_manager.h +++ b/source/fuzz/fact_manager/fact_manager.h @@ -12,15 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FACT_MANAGER_H_ -#define SOURCE_FUZZ_FACT_MANAGER_H_ +#ifndef SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ +#define SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ -#include <memory> #include <set> #include <utility> #include <vector> #include "source/fuzz/data_descriptor.h" +#include "source/fuzz/fact_manager/constant_uniform_facts.h" +#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h" +#include "source/fuzz/fact_manager/dead_block_facts.h" +#include "source/fuzz/fact_manager/irrelevant_value_facts.h" +#include "source/fuzz/fact_manager/livesafe_function_facts.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/opt/constants.h" @@ -38,25 +42,22 @@ namespace fuzz { // the module. class FactManager { public: - FactManager(); - - ~FactManager(); + explicit FactManager(opt::IRContext* ir_context); // Adds all the facts from |facts|, checking them for validity with respect to // |context|. Warnings about invalid facts are communicated via // |message_consumer|; such facts are otherwise ignored. void AddFacts(const MessageConsumer& message_consumer, - const protobufs::FactSequence& facts, opt::IRContext* context); + const protobufs::FactSequence& facts); // Checks the fact for validity with respect to |context|. Returns false, // with no side effects, if the fact is invalid. Otherwise adds |fact| to the // fact manager. - bool AddFact(const protobufs::Fact& fact, opt::IRContext* context); + bool AddFact(const protobufs::Fact& fact); // Record the fact that |data1| and |data2| are synonymous. void AddFactDataSynonym(const protobufs::DataDescriptor& data1, - const protobufs::DataDescriptor& data2, - opt::IRContext* context); + const protobufs::DataDescriptor& data2); // Records the fact that |block_id| is dead. void AddFactBlockIsDead(uint32_t block_id); @@ -66,10 +67,12 @@ class FactManager { // Records the fact that the value of the pointee associated with |pointer_id| // is irrelevant: it does not affect the observable behaviour of the module. + // |pointer_id| must exist in the module and actually be a pointer. void AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id); // Records a fact that the |result_id| is irrelevant (i.e. it doesn't affect - // the semantics of the module) + // the semantics of the module). + // |result_id| must exist in the module and actually be a pointer. void AddFactIdIsIrrelevant(uint32_t result_id); // Records the fact that |lhs_id| is defined by the equation: @@ -77,8 +80,7 @@ class FactManager { // |lhs_id| = |opcode| |rhs_id[0]| ... |rhs_id[N-1]| // void AddFactIdEquation(uint32_t lhs_id, SpvOp opcode, - const std::vector<uint32_t>& rhs_id, - opt::IRContext* context); + const std::vector<uint32_t>& rhs_id); // Inspects all known facts and adds corollary facts; e.g. if we know that // a.x == b.x and a.y == b.y, where a and b have vec2 type, we can record @@ -92,8 +94,7 @@ class FactManager { // The parameter |maximum_equivalence_class_size| specifies the size beyond // which equivalence classes should not be mined for new facts, to avoid // excessively-long closure computations. - void ComputeClosureOfFacts(opt::IRContext* ir_context, - uint32_t maximum_equivalence_class_size); + void ComputeClosureOfFacts(uint32_t maximum_equivalence_class_size); // The fact manager is responsible for managing a few distinct categories of // facts. In principle there could be different fact managers for each kind @@ -113,20 +114,18 @@ class FactManager { // "constant == uniform element" fact is known. If multiple identically- // valued constants are relevant, only one will appear in the sequence. std::vector<uint32_t> GetConstantsAvailableFromUniformsForType( - opt::IRContext* ir_context, uint32_t type_id) const; + uint32_t type_id) const; // Provides details of all uniform elements that are known to be equal to the // constant associated with |constant_id| in |ir_context|. - const std::vector<protobufs::UniformBufferElementDescriptor> - GetUniformDescriptorsForConstant(opt::IRContext* ir_context, - uint32_t constant_id) const; + std::vector<protobufs::UniformBufferElementDescriptor> + GetUniformDescriptorsForConstant(uint32_t constant_id) const; // Returns the id of a constant whose value is known to match that of // |uniform_descriptor|, and whose type matches the type of the uniform // element. If multiple such constant is exist, the one that is returned // is arbitrary. Returns 0 if no such constant id exists. uint32_t GetConstantFromUniformDescriptor( - opt::IRContext* context, const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const; @@ -191,42 +190,28 @@ class FactManager { // |pointer_id| is irrelevant. bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; - // Returns true iff there exists a fact that the |result_id| is irrelevant. + // Returns true if there exists a fact that the |result_id| is irrelevant or + // if |result_id| is declared in a block that has been declared dead. bool IdIsIrrelevant(uint32_t result_id) const; + // Returns a set of all the ids which have been declared irrelevant, or which + // have been declared inside a dead block. + std::unordered_set<uint32_t> GetIrrelevantIds() const; + // End of irrelevant value facts //============================== private: - // For each distinct kind of fact to be managed, we use a separate opaque - // class type. - - class ConstantUniformFacts; // Opaque class for management of - // constant uniform facts. - std::unique_ptr<ConstantUniformFacts> - uniform_constant_facts_; // Unique pointer to internal data. - - class DataSynonymAndIdEquationFacts; // Opaque class for management of data - // synonym and id equation facts. - std::unique_ptr<DataSynonymAndIdEquationFacts> - data_synonym_and_id_equation_facts_; // Unique pointer to internal data. - - class DeadBlockFacts; // Opaque class for management of dead block facts. - std::unique_ptr<DeadBlockFacts> - dead_block_facts_; // Unique pointer to internal data. - - class LivesafeFunctionFacts; // Opaque class for management of livesafe - // function facts. - std::unique_ptr<LivesafeFunctionFacts> - livesafe_function_facts_; // Unique pointer to internal data. - - class IrrelevantValueFacts; // Opaque class for management of - // facts about various irrelevant values in the module. - std::unique_ptr<IrrelevantValueFacts> - irrelevant_value_facts_; // Unique pointer to internal data. + // Keep these in alphabetical order. + fact_manager::ConstantUniformFacts constant_uniform_facts_; + fact_manager::DataSynonymAndIdEquationFacts + data_synonym_and_id_equation_facts_; + fact_manager::DeadBlockFacts dead_block_facts_; + fact_manager::LivesafeFunctionFacts livesafe_function_facts_; + fact_manager::IrrelevantValueFacts irrelevant_value_facts_; }; } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FACT_MANAGER_H_ +#endif // SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_ diff --git a/source/fuzz/fact_manager/irrelevant_value_facts.cpp b/source/fuzz/fact_manager/irrelevant_value_facts.cpp new file mode 100644 index 00000000..ac5ad8b2 --- /dev/null +++ b/source/fuzz/fact_manager/irrelevant_value_facts.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fact_manager/irrelevant_value_facts.h" + +#include "source/fuzz/data_descriptor.h" +#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h" +#include "source/fuzz/fact_manager/dead_block_facts.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +IrrelevantValueFacts::IrrelevantValueFacts(opt::IRContext* ir_context) + : ir_context_(ir_context) {} + +void IrrelevantValueFacts::AddFact( + const protobufs::FactPointeeValueIsIrrelevant& fact, + const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts) { + (void)data_synonym_and_id_equation_facts; // Keep release compilers happy. + assert(data_synonym_and_id_equation_facts.GetSynonymsForId(fact.pointer_id()) + .empty() && + "The id cannot participate in DataSynonym facts."); + auto pointer_def = ir_context_->get_def_use_mgr()->GetDef(fact.pointer_id()); + assert(pointer_def && "The id must exist in the module."); + auto type = ir_context_->get_type_mgr()->GetType(pointer_def->type_id()); + (void)type; // Keep release compilers happy. + assert(type && type->AsPointer() && "The id must be a pointer."); + + pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id()); +} + +void IrrelevantValueFacts::AddFact( + const protobufs::FactIdIsIrrelevant& fact, + const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts) { + (void)data_synonym_and_id_equation_facts; // Keep release compilers happy. + assert(data_synonym_and_id_equation_facts.GetSynonymsForId(fact.result_id()) + .empty() && + "The id cannot participate in DataSynonym facts."); + auto pointer_def = ir_context_->get_def_use_mgr()->GetDef(fact.result_id()); + assert(pointer_def && "The id must exist in the module."); + auto type = ir_context_->get_type_mgr()->GetType(pointer_def->type_id()); + (void)type; // Keep release compilers happy. + assert(type && !type->AsPointer() && "The id must not be a pointer."); + + irrelevant_ids_.insert(fact.result_id()); +} + +bool IrrelevantValueFacts::PointeeValueIsIrrelevant(uint32_t pointer_id) const { + return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0; +} + +bool IrrelevantValueFacts::IdIsIrrelevant( + uint32_t result_id, const DeadBlockFacts& dead_block_facts) const { + // The id is irrelevant if it has been declared irrelevant. + if (irrelevant_ids_.count(result_id)) { + return true; + } + + // The id must have a non-pointer type to be irrelevant. + auto def = ir_context_->get_def_use_mgr()->GetDef(result_id); + if (!def) { + return false; + } + auto type = ir_context_->get_type_mgr()->GetType(def->type_id()); + if (!type || type->AsPointer()) { + return false; + } + + // The id is irrelevant if it is in a dead block. + return ir_context_->get_instr_block(result_id) && + dead_block_facts.BlockIsDead( + ir_context_->get_instr_block(result_id)->id()); +} + +std::unordered_set<uint32_t> IrrelevantValueFacts::GetIrrelevantIds( + const DeadBlockFacts& dead_block_facts) const { + // Get all the ids that have been declared irrelevant. + auto irrelevant_ids = irrelevant_ids_; + + // Get all the non-pointer ids declared in dead blocks that have a type. + for (uint32_t block_id : dead_block_facts.GetDeadBlocks()) { + auto block = fuzzerutil::MaybeFindBlock(ir_context_, block_id); + // It is possible and allowed for the block not to exist, e.g. it could have + // been merged with another block. + if (!block) { + continue; + } + block->ForEachInst([this, &irrelevant_ids](opt::Instruction* inst) { + // The instruction must have a result id and a type, and it must not be a + // pointer. + if (inst->HasResultId() && inst->type_id() && + !ir_context_->get_type_mgr()->GetType(inst->type_id())->AsPointer()) { + irrelevant_ids.emplace(inst->result_id()); + } + }); + } + + return irrelevant_ids; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager/irrelevant_value_facts.h b/source/fuzz/fact_manager/irrelevant_value_facts.h new file mode 100644 index 00000000..ad70e6bb --- /dev/null +++ b/source/fuzz/fact_manager/irrelevant_value_facts.h @@ -0,0 +1,77 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ + +#include <unordered_set> + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// Forward reference to the DataSynonymAndIdEquationFacts class. +class DataSynonymAndIdEquationFacts; +// Forward reference to the DeadBlockFacts class. +class DeadBlockFacts; + +// The purpose of this class is to group the fields and data used to represent +// facts about various irrelevant values in the module. +class IrrelevantValueFacts { + public: + explicit IrrelevantValueFacts(opt::IRContext* ir_context); + + // See method in FactManager which delegates to this method. + // |data_synonym_and_id_equation_facts| and |context| are passed for + // consistency checks. + void AddFact( + const protobufs::FactPointeeValueIsIrrelevant& fact, + const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts); + + // See method in FactManager which delegates to this method. + // |data_synonym_and_id_equation_facts| and |context| are passed for + // consistency checks. + void AddFact( + const protobufs::FactIdIsIrrelevant& fact, + const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts); + + // See method in FactManager which delegates to this method. + bool PointeeValueIsIrrelevant(uint32_t pointer_id) const; + + // See method in FactManager which delegates to this method. + // |dead_block_facts| and |context| are passed to check whether |result_id| is + // declared inside a dead block, in which case it is irrelevant. + bool IdIsIrrelevant(uint32_t result_id, + const DeadBlockFacts& dead_block_facts) const; + + // See method in FactManager which delegates to this method. + // |dead_block_facts| and |context| are passed to also add all the ids + // declared in dead blocks to the set of irrelevant ids. + std::unordered_set<uint32_t> GetIrrelevantIds( + const DeadBlockFacts& dead_block_facts) const; + + private: + std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_; + std::unordered_set<uint32_t> irrelevant_ids_; + opt::IRContext* ir_context_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_ diff --git a/source/fuzz/fact_manager/livesafe_function_facts.cpp b/source/fuzz/fact_manager/livesafe_function_facts.cpp new file mode 100644 index 00000000..6f36afb6 --- /dev/null +++ b/source/fuzz/fact_manager/livesafe_function_facts.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 "source/fuzz/fact_manager/livesafe_function_facts.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +void LivesafeFunctionFacts::AddFact( + const protobufs::FactFunctionIsLivesafe& fact) { + livesafe_function_ids_.insert(fact.function_id()); +} + +bool LivesafeFunctionFacts::FunctionIsLivesafe(uint32_t function_id) const { + return livesafe_function_ids_.count(function_id) != 0; +} + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fact_manager/livesafe_function_facts.h b/source/fuzz/fact_manager/livesafe_function_facts.h new file mode 100644 index 00000000..8c485063 --- /dev/null +++ b/source/fuzz/fact_manager/livesafe_function_facts.h @@ -0,0 +1,44 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ +#define SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ + +#include <unordered_set> + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + +namespace spvtools { +namespace fuzz { +namespace fact_manager { + +// The purpose of this class is to group the fields and data used to represent +// facts about livesafe functions. +class LivesafeFunctionFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactFunctionIsLivesafe& fact); + + // See method in FactManager which delegates to this method. + bool FunctionIsLivesafe(uint32_t function_id) const; + + private: + std::unordered_set<uint32_t> livesafe_function_ids_; +}; + +} // namespace fact_manager +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_ diff --git a/source/fuzz/force_render_red.cpp b/source/fuzz/force_render_red.cpp index 5bf28798..919f8c9e 100644 --- a/source/fuzz/force_render_red.cpp +++ b/source/fuzz/force_render_red.cpp @@ -14,7 +14,7 @@ #include "source/fuzz/force_render_red.h" -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation_context.h" @@ -26,9 +26,6 @@ #include "source/util/make_unique.h" #include "tools/util/cli_consumer.h" -#include <algorithm> -#include <utility> - namespace spvtools { namespace fuzz { @@ -153,7 +150,7 @@ MakeConstantUniformReplacement(opt::IRContext* ir_context, MakeInstructionDescriptor(greater_than_instruction, SpvOpFOrdGreaterThan, 0), in_operand_index), - fact_manager.GetUniformDescriptorsForConstant(ir_context, constant_id)[0], + fact_manager.GetUniformDescriptorsForConstant(constant_id)[0], ir_context->TakeNextId(), ir_context->TakeNextId()); } @@ -185,9 +182,9 @@ bool ForceRenderRed( assert(ir_context); // Set up a fact manager with any given initial facts. - FactManager fact_manager; + FactManager fact_manager(ir_context.get()); for (auto& fact : initial_facts.fact()) { - fact_manager.AddFact(fact, ir_context.get()); + fact_manager.AddFact(fact); } TransformationContext transformation_context(&fact_manager, validator_options); @@ -276,8 +273,7 @@ bool ForceRenderRed( // We have at least one float uniform; let's see whether we have at least // two. auto available_constants = - fact_manager.GetConstantsAvailableFromUniformsForType( - ir_context.get(), float_type_id); + fact_manager.GetConstantsAvailableFromUniformsForType(float_type_id); if (available_constants.size() > 1) { // Grab the float constants associated with the first two known float // uniforms. diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index f5495900..cbaa17cf 100644 --- a/source/fuzz/fuzzer.cpp +++ b/source/fuzz/fuzzer.cpp @@ -16,11 +16,13 @@ #include <cassert> #include <memory> -#include <sstream> +#include <numeric> -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_access_chains.h" +#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" #include "source/fuzz/fuzzer_pass_add_composite_types.h" #include "source/fuzz/fuzzer_pass_add_copy_memory.h" #include "source/fuzz/fuzzer_pass_add_dead_blocks.h" @@ -32,8 +34,12 @@ #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" #include "source/fuzz/fuzzer_pass_add_loads.h" #include "source/fuzz/fuzzer_pass_add_local_variables.h" +#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" +#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h" +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" #include "source/fuzz/fuzzer_pass_add_parameters.h" +#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" #include "source/fuzz/fuzzer_pass_add_stores.h" #include "source/fuzz/fuzzer_pass_add_synonyms.h" #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" @@ -46,24 +52,43 @@ #include "source/fuzz/fuzzer_pass_construct_composites.h" #include "source/fuzz/fuzzer_pass_copy_objects.h" #include "source/fuzz/fuzzer_pass_donate_modules.h" +#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" +#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" +#include "source/fuzz/fuzzer_pass_inline_functions.h" +#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h" #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h" #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" +#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" #include "source/fuzz/fuzzer_pass_merge_blocks.h" +#include "source/fuzz/fuzzer_pass_mutate_pointers.h" #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" #include "source/fuzz/fuzzer_pass_outline_functions.h" #include "source/fuzz/fuzzer_pass_permute_blocks.h" #include "source/fuzz/fuzzer_pass_permute_function_parameters.h" +#include "source/fuzz/fuzzer_pass_permute_instructions.h" #include "source/fuzz/fuzzer_pass_permute_phi_operands.h" +#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" +#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" +#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" +#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" +#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" +#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" +#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" #include "source/fuzz/fuzzer_pass_split_blocks.h" #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h" #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h" +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" +#include "source/fuzz/pass_management/repeated_pass_manager_simple.h" +#include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" -#include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/transformation_context.h" #include "source/opt/build_module.h" #include "source/spirv_fuzzer_options.h" @@ -75,114 +100,105 @@ namespace fuzz { namespace { const uint32_t kIdBoundGap = 100; -const uint32_t kTransformationLimit = 500; - -const uint32_t kChanceOfApplyingAnotherPass = 85; - -// A convenience method to add a fuzzer pass to |passes| with probability 0.5. -// All fuzzer passes take |ir_context|, |transformation_context|, -// |fuzzer_context| and |transformation_sequence_out| as parameters. Extra -// arguments can be provided via |extra_args|. -template <typename T, typename... Args> -void MaybeAddPass( - std::vector<std::unique_ptr<FuzzerPass>>* passes, - opt::IRContext* ir_context, TransformationContext* transformation_context, - FuzzerContext* fuzzer_context, - protobufs::TransformationSequence* transformation_sequence_out, - Args&&... extra_args) { - if (fuzzer_context->ChooseEven()) { - passes->push_back(MakeUnique<T>(ir_context, transformation_context, - fuzzer_context, transformation_sequence_out, - std::forward<Args>(extra_args)...)); - } -} +const uint32_t kTransformationLimit = 2000; } // namespace -struct Fuzzer::Impl { - Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass, - spv_validator_options options) - : target_env(env), - seed(random_seed), - validate_after_each_fuzzer_pass(validate_after_each_pass), - validator_options(options) {} - - bool ApplyPassAndCheckValidity(FuzzerPass* pass, - const opt::IRContext& ir_context, - const spvtools::SpirvTools& tools) const; - - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const uint32_t seed; // Seed for random number generator. - bool validate_after_each_fuzzer_pass; // Determines whether the validator - // should be invoked after every fuzzer - // pass. - spv_validator_options validator_options; // Options to control validation. -}; - -Fuzzer::Fuzzer(spv_target_env env, uint32_t seed, +Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer, + const std::vector<uint32_t>& binary_in, + const protobufs::FactSequence& initial_facts, + const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, + std::unique_ptr<RandomGenerator> random_generator, + bool enable_all_passes, + RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options) - : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass, - validator_options)) {} + : target_env_(target_env), + consumer_(std::move(consumer)), + binary_in_(binary_in), + initial_facts_(initial_facts), + donor_suppliers_(donor_suppliers), + random_generator_(std::move(random_generator)), + enable_all_passes_(enable_all_passes), + repeated_pass_strategy_(repeated_pass_strategy), + validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass), + validator_options_(validator_options), + num_repeated_passes_applied_(0), + ir_context_(nullptr), + fuzzer_context_(nullptr), + transformation_context_(nullptr), + transformation_sequence_out_() {} Fuzzer::~Fuzzer() = default; -void Fuzzer::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); +template <typename FuzzerPassT, typename... Args> +void Fuzzer::MaybeAddRepeatedPass(RepeatedPassInstances* pass_instances, + Args&&... extra_args) { + if (enable_all_passes_ || fuzzer_context_->ChooseEven()) { + pass_instances->SetPass(MakeUnique<FuzzerPassT>( + ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), + &transformation_sequence_out_, std::forward<Args>(extra_args)...)); + } +} + +template <typename FuzzerPassT, typename... Args> +void Fuzzer::MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes, + Args&&... extra_args) { + if (enable_all_passes_ || fuzzer_context_->ChooseEven()) { + passes->push_back(MakeUnique<FuzzerPassT>( + ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(), + &transformation_sequence_out_, std::forward<Args>(extra_args)...)); + } } -bool Fuzzer::Impl::ApplyPassAndCheckValidity( - FuzzerPass* pass, const opt::IRContext& ir_context, - const spvtools::SpirvTools& tools) const { +bool Fuzzer::ApplyPassAndCheckValidity( + FuzzerPass* pass, const spvtools::SpirvTools& tools) const { pass->Apply(); - if (validate_after_each_fuzzer_pass) { + if (validate_after_each_fuzzer_pass_) { std::vector<uint32_t> binary_to_validate; - ir_context.module()->ToBinary(&binary_to_validate, false); + ir_context_->module()->ToBinary(&binary_to_validate, false); if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(), - validator_options)) { - consumer(SPV_MSG_INFO, nullptr, {}, - "Binary became invalid during fuzzing (set a breakpoint to " - "inspect); stopping."); + validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Binary became invalid during fuzzing (set a breakpoint to " + "inspect); stopping."); return false; } } return true; } -Fuzzer::FuzzerResultStatus Fuzzer::Run( - const std::vector<uint32_t>& binary_in, - const protobufs::FactSequence& initial_facts, - const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, - std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const { +Fuzzer::FuzzerResult Fuzzer::Run() { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; - spvtools::SpirvTools tools(impl_->target_env); - tools.SetMessageConsumer(impl_->consumer); + assert(ir_context_ == nullptr && fuzzer_context_ == nullptr && + transformation_context_ == nullptr && + transformation_sequence_out_.transformation_size() == 0 && + "'Run' must not be invoked more than once."); + + spvtools::SpirvTools tools(target_env_); + tools.SetMessageConsumer(consumer_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); - return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface; + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return {Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Initial binary is invalid; stopping."); - return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid; + if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Initial binary is invalid; stopping."); + return {Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // Build the module from the input binary. - std::unique_ptr<opt::IRContext> ir_context = BuildModule( - impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size()); - assert(ir_context); - - // Make a PRNG from the seed passed to the fuzzer on creation. - PseudoRandomGenerator random_generator(impl_->seed); + ir_context_ = + BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size()); + assert(ir_context_); // The fuzzer will introduce new ids into the module. The module's id bound // gives the smallest id that can be used for this purpose. We add an offset @@ -191,172 +207,179 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( // // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the // case where the maximum id bound is reached. - auto minimum_fresh_id = ir_context->module()->id_bound() + kIdBoundGap; - FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id); + auto minimum_fresh_id = ir_context_->module()->id_bound() + kIdBoundGap; + fuzzer_context_ = + MakeUnique<FuzzerContext>(random_generator_.get(), minimum_fresh_id); + + FactManager fact_manager(ir_context_.get()); + fact_manager.AddFacts(consumer_, initial_facts_); + transformation_context_ = + MakeUnique<TransformationContext>(&fact_manager, validator_options_); + + RepeatedPassInstances pass_instances{}; + do { + // Each call to MaybeAddRepeatedPass randomly decides whether the given pass + // should be enabled, and adds an instance of the pass to |pass_instances| + // if it is enabled. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3764): Consider + // enabling some passes always, or with higher probability. + MaybeAddRepeatedPass<FuzzerPassAddAccessChains>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddDeadBlocks>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddDeadBreaks>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddDeadContinues>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddEquationInstructions>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddFunctionCalls>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddGlobalVariables>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddImageSampleUnusedComponents>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddStores>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddSynonyms>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassAddVectorShuffleInstructions>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassApplyIdSynonyms>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassConstructComposites>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassCopyObjects>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassDonateModules>(&pass_instances, + donor_suppliers_); + MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassObfuscateConstants>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceParameterWithGlobal>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceLinearAlgebraInstructions>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceIrrelevantIds>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>( + &pass_instances); + MaybeAddRepeatedPass<FuzzerPassReplaceParamsWithStruct>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances); + MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>( + &pass_instances); + // There is a theoretical possibility that no pass instances were created + // until now; loop again if so. + } while (pass_instances.GetPasses().empty()); - FactManager fact_manager; - fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get()); - TransformationContext transformation_context(&fact_manager, - impl_->validator_options); + RepeatedPassRecommenderStandard pass_recommender(&pass_instances, + fuzzer_context_.get()); - // Apply some semantics-preserving passes. - std::vector<std::unique_ptr<FuzzerPass>> passes; - while (passes.empty()) { - MaybeAddPass<FuzzerPassAddAccessChains>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddCompositeTypes>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddCopyMemory>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddDeadBlocks>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddDeadBreaks>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddDeadContinues>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddEquationInstructions>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddFunctionCalls>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddGlobalVariables>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddImageSampleUnusedComponents>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddLoads>(&passes, ir_context.get(), - &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddLocalVariables>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddParameters>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(), - &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddSynonyms>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddVectorShuffleInstructions>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassApplyIdSynonyms>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassConstructComposites>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassCopyObjects>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassDonateModules>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out, donor_suppliers); - MaybeAddPass<FuzzerPassInvertComparisonOperators>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassMergeBlocks>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassObfuscateConstants>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassOutlineFunctions>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassPermuteBlocks>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassPermuteFunctionParameters>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassPushIdsThroughVariables>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassReplaceParameterWithGlobal>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassReplaceParamsWithStruct>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassSplitBlocks>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassSwapBranchConditionalOperands>( - &passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); + std::unique_ptr<RepeatedPassManager> repeated_pass_manager = nullptr; + switch (repeated_pass_strategy_) { + case RepeatedPassStrategy::kSimple: + repeated_pass_manager = MakeUnique<RepeatedPassManagerSimple>( + fuzzer_context_.get(), &pass_instances); + break; + case RepeatedPassStrategy::kLoopedWithRecommendations: + repeated_pass_manager = + MakeUnique<RepeatedPassManagerLoopedWithRecommendations>( + fuzzer_context_.get(), &pass_instances, &pass_recommender); + break; + case RepeatedPassStrategy::kRandomWithRecommendations: + repeated_pass_manager = + MakeUnique<RepeatedPassManagerRandomWithRecommendations>( + fuzzer_context_.get(), &pass_instances, &pass_recommender); + break; } - bool is_first = true; - while (static_cast<uint32_t>( - transformation_sequence_out->transformation_size()) < - kTransformationLimit && - (is_first || - fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) { - is_first = false; - if (!impl_->ApplyPassAndCheckValidity( - passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context, - tools)) { - return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; + do { + if (!ApplyPassAndCheckValidity(repeated_pass_manager->ChoosePass(), + tools)) { + return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } - } + } while (ShouldContinueFuzzing()); // Now apply some passes that it does not make sense to apply repeatedly, // as they do not unlock other passes. std::vector<std::unique_ptr<FuzzerPass>> final_passes; - MaybeAddPass<FuzzerPassAdjustBranchWeights>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAdjustFunctionControls>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAdjustLoopControls>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAdjustMemoryOperandsMasks>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAdjustSelectionControls>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassAddNoContractionDecorations>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassInterchangeZeroLikeConstants>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassPermutePhiOperands>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassSwapCommutableOperands>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); - MaybeAddPass<FuzzerPassToggleAccessChainInstruction>( - &final_passes, ir_context.get(), &transformation_context, &fuzzer_context, - transformation_sequence_out); + MaybeAddFinalPass<FuzzerPassAdjustBranchWeights>(&final_passes); + MaybeAddFinalPass<FuzzerPassAdjustFunctionControls>(&final_passes); + MaybeAddFinalPass<FuzzerPassAdjustLoopControls>(&final_passes); + MaybeAddFinalPass<FuzzerPassAdjustMemoryOperandsMasks>(&final_passes); + MaybeAddFinalPass<FuzzerPassAdjustSelectionControls>(&final_passes); + MaybeAddFinalPass<FuzzerPassAddNoContractionDecorations>(&final_passes); + MaybeAddFinalPass<FuzzerPassInterchangeSignednessOfIntegerOperands>( + &final_passes); + MaybeAddFinalPass<FuzzerPassInterchangeZeroLikeConstants>(&final_passes); + MaybeAddFinalPass<FuzzerPassPermutePhiOperands>(&final_passes); + MaybeAddFinalPass<FuzzerPassSwapCommutableOperands>(&final_passes); + MaybeAddFinalPass<FuzzerPassToggleAccessChainInstruction>(&final_passes); for (auto& pass : final_passes) { - if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) { - return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule; + if (!ApplyPassAndCheckValidity(pass.get(), tools)) { + return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } } - // Encode the module as a binary. - ir_context->module()->ToBinary(binary_out, false); + std::vector<uint32_t> binary_out; + ir_context_->module()->ToBinary(&binary_out, false); - return Fuzzer::FuzzerResultStatus::kComplete; + return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out), + std::move(transformation_sequence_out_)}; +} + +bool Fuzzer::ShouldContinueFuzzing() { + // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer + // passes are able to apply any transformations. To guard against this we + // count the number of times some repeated pass has been applied and ensure + // that fuzzing stops if the number of repeated passes hits the limit on the + // number of transformations that can be applied. + assert( + num_repeated_passes_applied_ <= kTransformationLimit && + "The number of repeated passes applied must not exceed its upper limit."); + if (num_repeated_passes_applied_ == kTransformationLimit) { + // Stop because fuzzing has got stuck. + return false; + } + auto transformations_applied_so_far = + static_cast<uint32_t>(transformation_sequence_out_.transformation_size()); + if (transformations_applied_so_far >= kTransformationLimit) { + // Stop because we have reached the transformation limit. + return false; + } + auto chance_of_continuing = static_cast<uint32_t>( + 100.0 * (1.0 - (static_cast<double>(transformations_applied_so_far) / + static_cast<double>(kTransformationLimit)))); + if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) { + // We have probabilistically decided to stop. + return false; + } + // Continue fuzzing! + num_repeated_passes_applied_++; + return true; } } // namespace fuzz diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h index 6c3ef71c..379c0415 100644 --- a/source/fuzz/fuzzer.h +++ b/source/fuzz/fuzzer.h @@ -18,8 +18,14 @@ #include <memory> #include <vector> +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/random_generator.h" +#include "source/opt/ir_context.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { @@ -37,11 +43,27 @@ class Fuzzer { kInitialBinaryInvalid, }; - // Constructs a fuzzer from the given target environment |env|. |seed| is a - // seed for pseudo-random number generation. - // |validate_after_each_fuzzer_pass| controls whether the validator will be - // invoked after every fuzzer pass is applied. - Fuzzer(spv_target_env env, uint32_t seed, + struct FuzzerResult { + FuzzerResultStatus status; + std::vector<uint32_t> transformed_binary; + protobufs::TransformationSequence applied_transformations; + }; + + // Each field of this enum corresponds to an available repeated pass + // strategy, and is used to decide which kind of RepeatedPassManager object + // to create. + enum class RepeatedPassStrategy { + kSimple, + kRandomWithRecommendations, + kLoopedWithRecommendations + }; + + Fuzzer(spv_target_env target_env, MessageConsumer consumer, + const std::vector<uint32_t>& binary_in, + const protobufs::FactSequence& initial_facts, + const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, + std::unique_ptr<RandomGenerator> random_generator, + bool enable_all_passes, RepeatedPassStrategy repeated_pass_strategy, bool validate_after_each_fuzzer_pass, spv_validator_options validator_options); @@ -53,26 +75,98 @@ class Fuzzer { ~Fuzzer(); - // Sets the message consumer to the given |consumer|. The |consumer| will be - // invoked once for each message communicated from the library. - void SetMessageConsumer(MessageConsumer consumer); - - // Transforms |binary_in| to |binary_out| by running a number of randomized - // fuzzer passes. Initial facts about the input binary and the context in - // which it will execute are provided via |initial_facts|. A source of donor - // modules to be used by transformations is provided via |donor_suppliers|. - // The transformation sequence that was applied is returned via - // |transformation_sequence_out|. - FuzzerResultStatus Run( - const std::vector<uint32_t>& binary_in, - const protobufs::FactSequence& initial_facts, - const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers, - std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const; + // Transforms |binary_in_| by running a number of randomized fuzzer passes. + // Initial facts about the input binary and the context in which it will + // execute are provided via |initial_facts_|. A source of donor modules to be + // used by transformations is provided via |donor_suppliers_|. On success, + // returns a successful result status together with the transformed binary and + // the sequence of transformations that were applied. Otherwise, returns an + // appropriate result status together with an empty binary and empty + // transformation sequence. + FuzzerResult Run(); private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr<Impl> impl_; // Unique pointer to internal data. + // A convenience method to add a repeated fuzzer pass to |pass_instances| with + // probability 0.5, or with probability 1 if |enable_all_passes_| is true. + // + // All fuzzer passes take members |ir_context_|, |transformation_context_|, + // |fuzzer_context_| and |transformation_sequence_out_| as parameters. Extra + // arguments can be provided via |extra_args|. + template <typename FuzzerPassT, typename... Args> + void MaybeAddRepeatedPass(RepeatedPassInstances* pass_instances, + Args&&... extra_args); + + // A convenience method to add a final fuzzer pass to |passes| with + // probability 0.5, or with probability 1 if |enable_all_passes_| is true. + // + // All fuzzer passes take members |ir_context_|, |transformation_context_|, + // |fuzzer_context_| and |transformation_sequence_out_| as parameters. Extra + // arguments can be provided via |extra_args|. + template <typename FuzzerPassT, typename... Args> + void MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes, + Args&&... extra_args); + + // Decides whether to apply more repeated passes. The probability decreases as + // the number of transformations that have been applied increases. + bool ShouldContinueFuzzing(); + + // Applies |pass|, which must be a pass constructed with |ir_context|, and + // then returns true if and only if |ir_context| is valid. |tools| is used to + // check validity. + bool ApplyPassAndCheckValidity(FuzzerPass* pass, + const spvtools::SpirvTools& tools) const; + + // Target environment. + const spv_target_env target_env_; + + // Message consumer that will be invoked once for each message communicated + // from the library. + MessageConsumer consumer_; + + // The initial binary to which fuzzing should be applied. + const std::vector<uint32_t>& binary_in_; + + // Initial facts known to hold in advance of applying any transformations. + const protobufs::FactSequence& initial_facts_; + + // A source of modules whose contents can be donated into the module being + // fuzzed. + const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers_; + + // Random number generator to control decision making during fuzzing. + std::unique_ptr<RandomGenerator> random_generator_; + + // Determines whether all passes should be enabled, vs. having passes be + // probabilistically enabled. + bool enable_all_passes_; + + // Controls which type of RepeatedPassManager object to create. + RepeatedPassStrategy repeated_pass_strategy_; + + // Determines whether the validator should be invoked after every fuzzer pass. + bool validate_after_each_fuzzer_pass_; + + // Options to control validation. + spv_validator_options validator_options_; + + // The number of repeated fuzzer passes that have been applied is kept track + // of, in order to enforce a hard limit on the number of times such passes + // can be applied. + uint32_t num_repeated_passes_applied_; + + // Intermediate representation for the module being fuzzed, which gets + // mutated as fuzzing proceeds. + std::unique_ptr<opt::IRContext> ir_context_; + + // Provides probabilities that control the fuzzing process. + std::unique_ptr<FuzzerContext> fuzzer_context_; + + // Contextual information that is required in order to apply transformations. + std::unique_ptr<TransformationContext> transformation_context_; + + // The sequence of transformations that have been applied during fuzzing. It + // is initially empty and grows as fuzzer passes are applied. + protobufs::TransformationSequence transformation_sequence_out_; }; } // namespace fuzz diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index 86eb2540..9e9e78dd 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -23,10 +23,19 @@ namespace { // Default <minimum, maximum> pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair<uint32_t, uint32_t> + kChanceOfAcceptingRepeatedPassRecommendation = {70, 100}; const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50}; +const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherPassToPassLoop = {85, + 95}; const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfAddingBitInstructionSynonym = {20, + 90}; +const std::pair<uint32_t, uint32_t> + kChanceOfAddingBothBranchesWhenReplacingOpSelect = {40, 60}; +const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50}; const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50}; const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80}; @@ -38,13 +47,17 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingImageSampleUnusedComponents = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingLoad = {5, 50}; const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70}; const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = { 5, 70}; +const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70}; const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70}; const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50}; const std::pair<uint32_t, uint32_t> kChanceOfAddingSynonyms = {20, 50}; +const std::pair<uint32_t, uint32_t> + kChanceOfAddingTrueBranchWhenReplacingOpSelect = {40, 60}; const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70}; const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70}; const std::pair<uint32_t, uint32_t> kChanceOfAdjustingBranchWeights = {20, 90}; @@ -62,24 +75,56 @@ const std::pair<uint32_t, uint32_t> kChanceOfChoosingWorkgroupStorageClass = { 50, 50}; const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50}; const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50}; +const std::pair<uint32_t, uint32_t> kChanceOfCreatingIntSynonymsUsingLoops = { + 5, 10}; const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = { + 20, 50}; +const std::pair<uint32_t, uint32_t> kChanceOfFlatteningConditionalBranch = {45, + 95}; +const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = { + 30, 70}; const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain = {50, 95}; +const std::pair<uint32_t, uint32_t> + kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym = {50, 80}; +const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90}; const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = { 10, 90}; +const std::pair<uint32_t, uint32_t> + kChanceOfInterchangingSignednessOfIntegerOperands = {10, 90}; const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = { 20, 50}; const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60}; +const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = { + 20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95}; const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50}; +const std::pair<uint32_t, uint32_t> kChanceOfMutatingPointer = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90}; const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfPermutingInstructions = {20, 70}; const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90}; const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20, + 70}; const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50}; +const std::pair<uint32_t, uint32_t> + kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70}; +const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore = + {20, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad = + {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfReplacingIrrelevantId = {35, 95}; const std::pair<uint32_t, uint32_t> kChanceOfReplacingLinearAlgebraInstructions = {10, 90}; +const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory = + {20, 90}; +const std::pair<uint32_t, uint32_t> + kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90}; +const std::pair<uint32_t, uint32_t> + kChanceOfReplacingOpSelectWithConditionalBranch = {20, 90}; const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = { 30, 70}; const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = { @@ -131,12 +176,22 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, kGetDefaultMaxNumberOfParametersReplacedWithStruct), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_accepting_repeated_pass_recommendation_ = + ChooseBetweenMinAndMax(kChanceOfAcceptingRepeatedPassRecommendation); chance_of_adding_access_chain_ = ChooseBetweenMinAndMax(kChanceOfAddingAccessChain); + chance_of_adding_another_pass_to_pass_loop_ = + ChooseBetweenMinAndMax(kChanceOfAddingAnotherPassToPassLoop); chance_of_adding_another_struct_field_ = ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType); + chance_of_adding_bit_instruction_synonym_ = + ChooseBetweenMinAndMax(kChanceOfAddingBitInstructionSynonym); + chance_of_adding_both_branches_when_replacing_opselect_ = + ChooseBetweenMinAndMax(kChanceOfAddingBothBranchesWhenReplacingOpSelect); + chance_of_adding_composite_insert_ = + ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert); chance_of_adding_copy_memory_ = ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory); chance_of_adding_dead_block_ = @@ -150,6 +205,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_adding_global_variable_ = ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable); chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad); + chance_of_adding_loop_preheader_ = + ChooseBetweenMinAndMax(kChanceOfAddingLoopPreheader); chance_of_adding_image_sample_unused_components_ = ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents); chance_of_adding_local_variable_ = @@ -158,11 +215,15 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAddingMatrixType); chance_of_adding_no_contraction_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration); + chance_of_adding_opphi_synonym_ = + ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym); chance_of_adding_parameters = ChooseBetweenMinAndMax(kChanceOfAddingParameters); chance_of_adding_relaxed_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingRelaxedDecoration); chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore); + chance_of_adding_true_branch_when_replacing_opselect_ = + ChooseBetweenMinAndMax(kChanceOfAddingTrueBranchWhenReplacingOpSelect); chance_of_adding_vector_shuffle_ = ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle); chance_of_adding_vector_type_ = @@ -187,33 +248,69 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_constructing_composite_ = ChooseBetweenMinAndMax(kChanceOfConstructingComposite); chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); + chance_of_creating_int_synonyms_using_loops_ = + ChooseBetweenMinAndMax(kChanceOfCreatingIntSynonymsUsingLoops); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_duplicating_region_with_selection_ = + ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection); + chance_of_flattening_conditional_branch_ = + ChooseBetweenMinAndMax(kChanceOfFlatteningConditionalBranch); + chance_of_going_deeper_to_insert_in_composite_ = + ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain); + chance_of_having_two_blocks_in_loop_to_create_int_synonym_ = + ChooseBetweenMinAndMax(kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym); + chance_of_inlining_function_ = + ChooseBetweenMinAndMax(kChanceOfInliningFunction); + chance_of_interchanging_signedness_of_integer_operands_ = + ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands); chance_of_interchanging_zero_like_constants_ = ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants); chance_of_inverting_comparison_operators_ = ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators); chance_of_making_donor_livesafe_ = ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe); + chance_of_making_vector_operation_dynamic_ = + ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic); chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks); chance_of_moving_block_down_ = ChooseBetweenMinAndMax(kChanceOfMovingBlockDown); + chance_of_mutating_pointer_ = + ChooseBetweenMinAndMax(kChanceOfMutatingPointer); chance_of_obfuscating_constant_ = ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant); chance_of_outlining_function_ = ChooseBetweenMinAndMax(kChanceOfOutliningFunction); + chance_of_permuting_instructions_ = + ChooseBetweenMinAndMax(kChanceOfPermutingInstructions); chance_of_permuting_parameters_ = ChooseBetweenMinAndMax(kChanceOfPermutingParameters); chance_of_permuting_phi_operands_ = ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands); + chance_of_propagating_instructions_up_ = + ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp); chance_of_pushing_id_through_variable_ = ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable); + chance_of_replacing_add_sub_mul_with_carrying_extended_ = + ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended); + chance_of_replacing_copy_memory_with_load_store_ = + ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore); + chance_of_replacing_copyobject_with_store_load_ = + ChooseBetweenMinAndMax(kChanceOfReplacingCopyObjectWithStoreLoad); chance_of_replacing_id_with_synonym_ = ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym); + chance_of_replacing_irrelevant_id_ = + ChooseBetweenMinAndMax(kChanceOfReplacingIrrelevantId); chance_of_replacing_linear_algebra_instructions_ = ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions); + chance_of_replacing_load_store_with_copy_memory_ = + ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory); + chance_of_replacing_opphi_id_from_dead_predecessor_ = + ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor); + chance_of_replacing_opselect_with_conditional_branch_ = + ChooseBetweenMinAndMax(kChanceOfReplacingOpSelectWithConditionalBranch); chance_of_replacing_parameters_with_globals_ = ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals); chance_of_replacing_parameters_with_struct_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 6efea4ed..b6f466ff 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -106,15 +106,30 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAcceptingRepeatedPassRecommendation() { + return chance_of_accepting_repeated_pass_recommendation_; + } uint32_t GetChanceOfAddingAccessChain() { return chance_of_adding_access_chain_; } + uint32_t GetChanceOfAddingAnotherPassToPassLoop() { + return chance_of_adding_another_pass_to_pass_loop_; + } uint32_t GetChanceOfAddingAnotherStructField() { return chance_of_adding_another_struct_field_; } uint32_t GetChanceOfAddingArrayOrStructType() { return chance_of_adding_array_or_struct_type_; } + uint32_t GetChanceOfAddingBitInstructionSynonym() { + return chance_of_adding_bit_instruction_synonym_; + } + uint32_t GetChanceOfAddingBothBranchesWhenReplacingOpSelect() { + return chance_of_adding_both_branches_when_replacing_opselect_; + } + uint32_t GetChanceOfAddingCompositeInsert() { + return chance_of_adding_composite_insert_; + } uint32_t GetChanceOfAddingCopyMemory() { return chance_of_adding_copy_memory_; } @@ -136,18 +151,27 @@ class FuzzerContext { uint32_t GetChanceOfAddingLocalVariable() { return chance_of_adding_local_variable_; } + uint32_t GetChanceOfAddingLoopPreheader() { + return chance_of_adding_loop_preheader_; + } uint32_t GetChanceOfAddingMatrixType() { return chance_of_adding_matrix_type_; } uint32_t GetChanceOfAddingNoContractionDecoration() { return chance_of_adding_no_contraction_decoration_; } + uint32_t GetChanceOfAddingOpPhiSynonym() { + return chance_of_adding_opphi_synonym_; + } uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; } uint32_t GetChanceOfAddingRelaxedDecoration() { return chance_of_adding_relaxed_decoration_; } uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; } uint32_t GetChanceOfAddingSynonyms() { return chance_of_adding_synonyms_; } + uint32_t GetChanceOfAddingTrueBranchWhenReplacingOpSelect() { + return chance_of_adding_true_branch_when_replacing_opselect_; + } uint32_t GetChanceOfAddingVectorShuffle() { return chance_of_adding_vector_shuffle_; } @@ -180,12 +204,33 @@ class FuzzerContext { return chance_of_constructing_composite_; } uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; } + uint32_t GetChanceOfCreatingIntSynonymsUsingLoops() { + return chance_of_creating_int_synonyms_using_loops_; + } uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } + uint32_t GetChanceOfDuplicatingRegionWithSelection() { + return chance_of_duplicating_region_with_selection_; + } + uint32_t GetChanceOfFlatteningConditionalBranch() { + return chance_of_flattening_conditional_branch_; + } + uint32_t GetChanceOfGoingDeeperToInsertInComposite() { + return chance_of_going_deeper_to_insert_in_composite_; + } uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() { return chance_of_going_deeper_when_making_access_chain_; } + uint32_t GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym() { + return chance_of_having_two_blocks_in_loop_to_create_int_synonym_; + } + uint32_t GetChanceOfInliningFunction() { + return chance_of_inlining_function_; + } + uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() { + return chance_of_interchanging_signedness_of_integer_operands_; + } uint32_t GetChanceOfInterchangingZeroLikeConstants() { return chance_of_interchanging_zero_like_constants_; } @@ -195,29 +240,60 @@ class FuzzerContext { uint32_t ChanceOfMakingDonorLivesafe() { return chance_of_making_donor_livesafe_; } + uint32_t GetChanceOfMakingVectorOperationDynamic() { + return chance_of_making_vector_operation_dynamic_; + } uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; } uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; } + uint32_t GetChanceOfMutatingPointer() { return chance_of_mutating_pointer_; } uint32_t GetChanceOfObfuscatingConstant() { return chance_of_obfuscating_constant_; } uint32_t GetChanceOfOutliningFunction() { return chance_of_outlining_function_; } + uint32_t GetChanceOfPermutingInstructions() { + return chance_of_permuting_instructions_; + } uint32_t GetChanceOfPermutingParameters() { return chance_of_permuting_parameters_; } uint32_t GetChanceOfPermutingPhiOperands() { return chance_of_permuting_phi_operands_; } + uint32_t GetChanceOfPropagatingInstructionsUp() { + return chance_of_propagating_instructions_up_; + } uint32_t GetChanceOfPushingIdThroughVariable() { return chance_of_pushing_id_through_variable_; } + uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() { + return chance_of_replacing_add_sub_mul_with_carrying_extended_; + } + uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() { + return chance_of_replacing_copy_memory_with_load_store_; + } + uint32_t GetChanceOfReplacingCopyObjectWithStoreLoad() { + return chance_of_replacing_copyobject_with_store_load_; + } uint32_t GetChanceOfReplacingIdWithSynonym() { return chance_of_replacing_id_with_synonym_; } + uint32_t GetChanceOfReplacingIrrelevantId() { + return chance_of_replacing_irrelevant_id_; + } uint32_t GetChanceOfReplacingLinearAlgebraInstructions() { return chance_of_replacing_linear_algebra_instructions_; } + uint32_t GetChanceOfReplacingLoadStoreWithCopyMemory() { + return chance_of_replacing_load_store_with_copy_memory_; + } + uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() { + return chance_of_replacing_opphi_id_from_dead_predecessor_; + } + uint32_t GetChanceOfReplacingOpselectWithConditionalBranch() { + return chance_of_replacing_opselect_with_conditional_branch_; + } uint32_t GetChanceOfReplacingParametersWithGlobals() { return chance_of_replacing_parameters_with_globals_; } @@ -269,6 +345,12 @@ class FuzzerContext { uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) { return random_generator_->RandomUint32(composite_size_bound); } + uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) { + return random_generator_->RandomUint32(number_of_components); + } + int64_t GetRandomValueForStepConstantInLoop() { + return random_generator_->RandomUint64(UINT64_MAX); + } uint32_t GetRandomLoopControlPartialCount() { return random_generator_->RandomUint32(max_loop_control_partial_count_); } @@ -278,6 +360,9 @@ class FuzzerContext { uint32_t GetRandomLoopLimit() { return random_generator_->RandomUint32(max_loop_limit_); } + uint32_t GetRandomNumberOfLoopIterations(uint32_t max_num_iterations) { + return ChooseBetweenMinAndMax({1, max_num_iterations}); + } uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) { assert(num_of_params < GetMaximumNumberOfFunctionParameters()); return ChooseBetweenMinAndMax( @@ -312,9 +397,14 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_accepting_repeated_pass_recommendation_; uint32_t chance_of_adding_access_chain_; + uint32_t chance_of_adding_another_pass_to_pass_loop_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; + uint32_t chance_of_adding_bit_instruction_synonym_; + uint32_t chance_of_adding_both_branches_when_replacing_opselect_; + uint32_t chance_of_adding_composite_insert_; uint32_t chance_of_adding_copy_memory_; uint32_t chance_of_adding_dead_block_; uint32_t chance_of_adding_dead_break_; @@ -324,12 +414,15 @@ class FuzzerContext { uint32_t chance_of_adding_image_sample_unused_components_; uint32_t chance_of_adding_load_; uint32_t chance_of_adding_local_variable_; + uint32_t chance_of_adding_loop_preheader_; uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; + uint32_t chance_of_adding_opphi_synonym_; uint32_t chance_of_adding_parameters; uint32_t chance_of_adding_relaxed_decoration_; uint32_t chance_of_adding_store_; uint32_t chance_of_adding_synonyms_; + uint32_t chance_of_adding_true_branch_when_replacing_opselect_; uint32_t chance_of_adding_vector_shuffle_; uint32_t chance_of_adding_vector_type_; uint32_t chance_of_adjusting_branch_weights_; @@ -342,20 +435,38 @@ class FuzzerContext { uint32_t chance_of_choosing_workgroup_storage_class_; uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; + uint32_t chance_of_creating_int_synonyms_using_loops_; uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_duplicating_region_with_selection_; + uint32_t chance_of_flattening_conditional_branch_; + uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; + uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_; + uint32_t chance_of_inlining_function_; + uint32_t chance_of_interchanging_signedness_of_integer_operands_; uint32_t chance_of_interchanging_zero_like_constants_; uint32_t chance_of_inverting_comparison_operators_; uint32_t chance_of_making_donor_livesafe_; + uint32_t chance_of_making_vector_operation_dynamic_; uint32_t chance_of_merging_blocks_; uint32_t chance_of_moving_block_down_; + uint32_t chance_of_mutating_pointer_; uint32_t chance_of_obfuscating_constant_; uint32_t chance_of_outlining_function_; + uint32_t chance_of_permuting_instructions_; uint32_t chance_of_permuting_parameters_; uint32_t chance_of_permuting_phi_operands_; + uint32_t chance_of_propagating_instructions_up_; uint32_t chance_of_pushing_id_through_variable_; + uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_; + uint32_t chance_of_replacing_copy_memory_with_load_store_; + uint32_t chance_of_replacing_copyobject_with_store_load_; uint32_t chance_of_replacing_id_with_synonym_; + uint32_t chance_of_replacing_irrelevant_id_; uint32_t chance_of_replacing_linear_algebra_instructions_; + uint32_t chance_of_replacing_load_store_with_copy_memory_; + uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_; + uint32_t chance_of_replacing_opselect_with_conditional_branch_; uint32_t chance_of_replacing_parameters_with_globals_; uint32_t chance_of_replacing_parameters_with_struct_; uint32_t chance_of_splitting_block_; diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp index ebd88d01..b1f39b73 100644 --- a/source/fuzz/fuzzer_pass.cpp +++ b/source/fuzz/fuzzer_pass.cpp @@ -17,12 +17,16 @@ #include <set> #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/id_use_descriptor.h" #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_null.h" #include "source/fuzz/transformation_add_constant_scalar.h" #include "source/fuzz/transformation_add_global_undef.h" +#include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_local_variable.h" +#include "source/fuzz/transformation_add_loop_preheader.h" #include "source/fuzz/transformation_add_type_boolean.h" #include "source/fuzz/transformation_add_type_float.h" #include "source/fuzz/transformation_add_type_function.h" @@ -31,6 +35,7 @@ #include "source/fuzz/transformation_add_type_pointer.h" #include "source/fuzz/transformation_add_type_struct.h" #include "source/fuzz/transformation_add_type_vector.h" +#include "source/fuzz/transformation_split_block.h" namespace spvtools { namespace fuzz { @@ -103,7 +108,20 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor( action) { // Consider every block in every function. for (auto& function : *GetIRContext()->module()) { + // Consider only reachable blocks. We do this in a separate loop to avoid + // recomputing the dominator analysis every time |action| changes the + // module. + std::vector<opt::BasicBlock*> reachable_blocks; + + const auto* dominator_analysis = + GetIRContext()->GetDominatorAnalysis(&function); for (auto& block : function) { + if (dominator_analysis->IsReachable(&block)) { + reachable_blocks.push_back(&block); + } + } + + for (auto* block : reachable_blocks) { // We now consider every instruction in the block, randomly deciding // whether to apply a transformation before it. @@ -118,7 +136,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor( base_opcode_skip_triples; // The initial base instruction is the block label. - uint32_t base = block.id(); + uint32_t base = block->id(); // Counts the number of times we have seen each opcode since we reset the // base instruction. @@ -127,7 +145,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor( // Consider every instruction in the block. The label is excluded: it is // only necessary to consider it as a base in case the first instruction // in the block does not have a result id. - for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) { + for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) { if (inst_it->HasResultId()) { // In the case that the instruction has a result id, we use the // instruction as its own base, and clear the skip counts we have @@ -138,7 +156,7 @@ void FuzzerPass::ForEachInstructionWithInstructionDescriptor( const SpvOp opcode = inst_it->opcode(); // Invoke the provided function, which might apply a transformation. - action(&function, &block, inst_it, + action(&function, block, inst_it, MakeInstructionDescriptor( base, opcode, skip_count.count(opcode) ? skip_count.at(opcode) : 0)); @@ -292,8 +310,6 @@ uint32_t FuzzerPass::FindOrCreateIntegerConstant( uint32_t FuzzerPass::FindOrCreateFloatConstant( const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) { auto float_type_id = FindOrCreateFloatType(width); - opt::analysis::FloatConstant float_constant( - GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), words); if (auto constant_id = fuzzerutil::MaybeGetScalarConstant( GetIRContext(), *GetTransformationContext(), words, float_type_id, is_irrelevant)) { @@ -514,5 +530,203 @@ uint32_t FuzzerPass::FindOrCreateZeroConstant( } } +void FuzzerPass::MaybeAddUseToReplace( + opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, + std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>* + uses_to_replace) { + // Only consider this use if it is in a block + if (!GetIRContext()->get_instr_block(use_inst)) { + return; + } + + // Get the index of the operand restricted to input operands. + uint32_t in_operand_index = + fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); + auto id_use_descriptor = + MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index); + uses_to_replace->emplace_back( + std::make_pair(id_use_descriptor, replacement_id)); +} + +opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader( + uint32_t header_id) { + auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id); + + assert(header_block && header_block->IsLoopHeader() && + "|header_id| should be the label id of a loop header"); + + auto predecessors = GetIRContext()->cfg()->preds(header_id); + + assert(predecessors.size() >= 2 && + "The block |header_id| should be reachable."); + + auto function = header_block->GetParent(); + + if (predecessors.size() == 2) { + // The header has a single out-of-loop predecessor, which could be a + // preheader. + + opt::BasicBlock* maybe_preheader; + + if (GetIRContext()->GetDominatorAnalysis(function)->Dominates( + header_id, predecessors[0])) { + // The first predecessor is the back-edge block, because the header + // dominates it, so the second one is out of the loop. + maybe_preheader = &*function->FindBlock(predecessors[1]); + } else { + // The first predecessor is out of the loop. + maybe_preheader = &*function->FindBlock(predecessors[0]); + } + + // |maybe_preheader| is a preheader if it branches unconditionally to + // the header. We also require it not to be a loop header. + if (maybe_preheader->terminator()->opcode() == SpvOpBranch && + !maybe_preheader->IsLoopHeader()) { + return maybe_preheader; + } + } + + // We need to add a preheader. + + // Get a fresh id for the preheader. + uint32_t preheader_id = GetFuzzerContext()->GetFreshId(); + + // Get a fresh id for each OpPhi instruction, if there is more than one + // out-of-loop predecessor. + std::vector<uint32_t> phi_ids; + if (predecessors.size() > 2) { + header_block->ForEachPhiInst( + [this, &phi_ids](opt::Instruction* /* unused */) { + phi_ids.push_back(GetFuzzerContext()->GetFreshId()); + }); + } + + // Add the preheader. + ApplyTransformation( + TransformationAddLoopPreheader(header_id, preheader_id, phi_ids)); + + // Make the newly-created preheader the new entry block. + return &*function->FindBlock(preheader_id); +} + +opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable( + uint32_t block_id) { + auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id); + assert(block && "|block_id| must be a block label"); + assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header"); + + // Find the first non-OpPhi and non-OpVariable instruction. + auto non_phi_or_var_inst = &*block->begin(); + while (non_phi_or_var_inst->opcode() == SpvOpPhi || + non_phi_or_var_inst->opcode() == SpvOpVariable) { + non_phi_or_var_inst = non_phi_or_var_inst->NextNode(); + } + + // Split the block. + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst), + new_block_id)); + + // We need to return the newly-created block. + return &*block->GetParent()->FindBlock(new_block_id); +} + +uint32_t FuzzerPass::FindOrCreateLocalVariable( + uint32_t pointer_type_id, uint32_t function_id, + bool pointee_value_is_irrelevant) { + auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id); + // No unused variables in release mode. + (void)pointer_type; + assert(pointer_type && pointer_type->AsPointer() && + pointer_type->AsPointer()->storage_class() == + SpvStorageClassFunction && + "The pointer_type_id must refer to a defined pointer type with " + "storage class Function"); + auto function = fuzzerutil::FindFunction(GetIRContext(), function_id); + assert(function && "The function must be defined."); + + // First we try to find a suitable existing variable. + // All of the local variable declarations are located in the first block. + for (auto& instruction : *function->begin()) { + if (instruction.opcode() != SpvOpVariable) { + continue; + } + // The existing OpVariable must have type |pointer_type_id|. + if (instruction.type_id() != pointer_type_id) { + continue; + } + // Check if the found variable is marked with PointeeValueIsIrrelevant + // according to |pointee_value_is_irrelevant|. + if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant( + instruction.result_id()) != pointee_value_is_irrelevant) { + continue; + } + return instruction.result_id(); + } + + // No such variable was found. Apply a transformation to get one. + uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), pointer_type_id); + uint32_t result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddLocalVariable( + result_id, pointer_type_id, function_id, + FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant), + pointee_value_is_irrelevant)); + return result_id; +} + +uint32_t FuzzerPass::FindOrCreateGlobalVariable( + uint32_t pointer_type_id, bool pointee_value_is_irrelevant) { + auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id); + // No unused variables in release mode. + (void)pointer_type; + assert( + pointer_type && pointer_type->AsPointer() && + (pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate || + pointer_type->AsPointer()->storage_class() == + SpvStorageClassWorkgroup) && + "The pointer_type_id must refer to a defined pointer type with storage " + "class Private or Workgroup"); + + // First we try to find a suitable existing variable. + for (auto& instruction : GetIRContext()->module()->types_values()) { + if (instruction.opcode() != SpvOpVariable) { + continue; + } + // The existing OpVariable must have type |pointer_type_id|. + if (instruction.type_id() != pointer_type_id) { + continue; + } + // Check if the found variable is marked with PointeeValueIsIrrelevant + // according to |pointee_value_is_irrelevant|. + if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant( + instruction.result_id()) != pointee_value_is_irrelevant) { + continue; + } + return instruction.result_id(); + } + + // No such variable was found. Apply a transformation to get one. + uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), pointer_type_id); + auto storage_class = fuzzerutil::GetStorageClassFromPointerType( + GetIRContext(), pointer_type_id); + uint32_t result_id = GetFuzzerContext()->GetFreshId(); + + // A variable with storage class Workgroup shouldn't have an initializer. + if (storage_class == SpvStorageClassWorkgroup) { + ApplyTransformation(TransformationAddGlobalVariable( + result_id, pointer_type_id, SpvStorageClassWorkgroup, 0, + pointee_value_is_irrelevant)); + } else { + ApplyTransformation(TransformationAddGlobalVariable( + result_id, pointer_type_id, SpvStorageClassPrivate, + FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant), + pointee_value_is_irrelevant)); + } + return result_id; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h index f066fb87..a9aae097 100644 --- a/source/fuzz/fuzzer_pass.h +++ b/source/fuzz/fuzzer_pass.h @@ -70,9 +70,9 @@ class FuzzerPass { std::function<bool(opt::IRContext*, opt::Instruction*)> instruction_is_relevant) const; - // A helper method that iterates through each instruction in each block, at - // all times tracking an instruction descriptor that allows the latest - // instruction to be located even if it has no result id. + // A helper method that iterates through each instruction in each reachable + // block, at all times tracking an instruction descriptor that allows the + // latest instruction to be located even if it has no result id. // // The code to manipulate the instruction descriptor is a bit fiddly. The // point of this method is to avoiding having to duplicate it in multiple @@ -175,11 +175,10 @@ class FuzzerPass { // with |words| as its value. If either the required integer type or the // constant do not exist, transformations are applied to add them. // The returned id either participates in IdIsIrrelevant fact or not, - // depending - // on the |is_irrelevant| parameter. + // depending on the |is_irrelevant| parameter. uint32_t FindOrCreateIntegerConstant(const std::vector<uint32_t>& words, uint32_t width, bool is_signed, - bool is_irrelevant = false); + bool is_irrelevant); // Returns the id of an OpConstant instruction, with a floating-point // type of width specified by |width|, with |words| as its value. If either @@ -188,15 +187,14 @@ class FuzzerPass { // participates in IdIsIrrelevant fact or not, depending on the // |is_irrelevant| parameter. uint32_t FindOrCreateFloatConstant(const std::vector<uint32_t>& words, - uint32_t width, - bool is_irrelevant = false); + uint32_t width, bool is_irrelevant); // Returns the id of an OpConstantTrue or OpConstantFalse instruction, // according to |value|. If either the required instruction or the bool // type do not exist, transformations are applied to add them. // The returned id either participates in IdIsIrrelevant fact or not, // depending on the |is_irrelevant| parameter. - uint32_t FindOrCreateBoolConstant(bool value, bool is_irrelevant = false); + uint32_t FindOrCreateBoolConstant(bool value, bool is_irrelevant); // Returns the id of an OpConstant instruction of type with |type_id| // that consists of |words|. If that instruction doesn't exist, @@ -205,7 +203,7 @@ class FuzzerPass { // in the module. The returned id either participates in IdIsIrrelevant fact // or not, depending on the |is_irrelevant| parameter. uint32_t FindOrCreateConstant(const std::vector<uint32_t>& words, - uint32_t type_id, bool is_irrelevant = false); + uint32_t type_id, bool is_irrelevant); // Returns the id of an OpConstantComposite instruction of type with |type_id| // that consists of |component_ids|. If that instruction doesn't exist, @@ -216,7 +214,7 @@ class FuzzerPass { // depending on the |is_irrelevant| parameter. uint32_t FindOrCreateCompositeConstant( const std::vector<uint32_t>& component_ids, uint32_t type_id, - bool is_irrelevant = false); + bool is_irrelevant); // Returns the result id of an instruction of the form: // %id = OpUndef %|type_id| @@ -273,7 +271,53 @@ class FuzzerPass { // } | // --------------+------------------------------- uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id, - bool is_irrelevant = false); + bool is_irrelevant); + + // Adds a pair (id_use_descriptor, |replacement_id|) to the vector + // |uses_to_replace|, where id_use_descriptor is the id use descriptor + // representing the usage of an id in the |use_inst| instruction, at operand + // index |use_index|, only if the instruction is in a basic block. + // If the instruction is not in a basic block, it does nothing. + void MaybeAddUseToReplace( + opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, + std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>* + uses_to_replace); + + // Returns the preheader of the loop with header |header_id|, which satisfies + // all of the following conditions: + // - It is the only out-of-loop predecessor of the header + // - It unconditionally branches to the header + // - It is not a loop header itself + // If such preheader does not exist, a new one is added and returned. + // Requires |header_id| to be the label id of a loop header block that is + // reachable in the CFG (and thus has at least 2 predecessors). + opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id); + + // Returns the second block in the pair obtained by splitting |block_id| just + // after the last OpPhi or OpVariable instruction in it. Assumes that the + // block is not a loop header. + opt::BasicBlock* SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id); + + // Returns the id of an available local variable (storage class Function) with + // the fact PointeeValueIsIrrelevant set according to + // |pointee_value_is_irrelevant|. If there is no such variable, it creates one + // in the |function| adding a zero initializer constant that is irrelevant. + // The new variable has the fact PointeeValueIsIrrelevant set according to + // |pointee_value_is_irrelevant|. The function returns the id of the created + // variable. + uint32_t FindOrCreateLocalVariable(uint32_t pointer_type_id, + uint32_t function_id, + bool pointee_value_is_irrelevant); + + // Returns the id of an available global variable (storage class Private or + // Workgroup) with the fact PointeeValueIsIrrelevant set according to + // |pointee_value_is_irrelevant|. If there is no such variable, it creates + // one, adding a zero initializer constant that is irrelevant. The new + // variable has the fact PointeeValueIsIrrelevant set according to + // |pointee_value_is_irrelevant|. The function returns the id of the created + // variable. + uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id, + bool pointee_value_is_irrelevant); private: opt::IRContext* ir_context_; diff --git a/source/fuzz/fuzzer_pass_add_access_chains.cpp b/source/fuzz/fuzzer_pass_add_access_chains.cpp index 6d5164ee..11155f23 100644 --- a/source/fuzz/fuzzer_pass_add_access_chains.cpp +++ b/source/fuzz/fuzzer_pass_add_access_chains.cpp @@ -92,6 +92,11 @@ void FuzzerPassAddAccessChains::Apply() { relevant_pointer_instructions[GetFuzzerContext()->RandomIndex( relevant_pointer_instructions)]; std::vector<uint32_t> index_ids; + + // Each index accessing a non-struct composite will be clamped, thus + // needing a pair of fresh ids + std::vector<std::pair<uint32_t, uint32_t>> fresh_ids_for_clamping; + auto pointer_type = GetIRContext()->get_def_use_mgr()->GetDef( chosen_pointer->type_id()); uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); @@ -132,20 +137,37 @@ void FuzzerPassAddAccessChains::Apply() { break; } - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We - // could allow non-constant indices when looking up non-structs, - // using clamping to ensure they are in-bounds. uint32_t index_value = GetFuzzerContext()->GetRandomIndexForAccessChain(bound); - index_ids.push_back(FindOrCreateIntegerConstant( - {index_value}, 32, GetFuzzerContext()->ChooseEven())); + switch (subobject_type->opcode()) { case SpvOpTypeArray: case SpvOpTypeMatrix: - case SpvOpTypeVector: + case SpvOpTypeVector: { + // The index will be clamped + + bool is_signed = GetFuzzerContext()->ChooseEven(); + + // Make the constant ready for clamping. We need: + // - an OpTypeBool to be present in the module + // - an OpConstant with the same type as the index and value + // the maximum value for an index + // - a new pair of fresh ids for the clamping instructions + FindOrCreateBoolType(); + FindOrCreateIntegerConstant({bound - 1}, 32, is_signed, false); + std::pair<uint32_t, uint32_t> fresh_pair_of_ids = { + GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId()}; + fresh_ids_for_clamping.emplace_back(fresh_pair_of_ids); + + index_ids.push_back(FindOrCreateIntegerConstant( + {index_value}, 32, is_signed, false)); subobject_type_id = subobject_type->GetSingleWordInOperand(0); - break; + + } break; case SpvOpTypeStruct: + index_ids.push_back(FindOrCreateIntegerConstant( + {index_value}, 32, GetFuzzerContext()->ChooseEven(), false)); subobject_type_id = subobject_type->GetSingleWordInOperand(index_value); break; @@ -162,7 +184,7 @@ void FuzzerPassAddAccessChains::Apply() { // Apply the transformation to add an access chain. ApplyTransformation(TransformationAccessChain( GetFuzzerContext()->GetFreshId(), chosen_pointer->result_id(), - index_ids, instruction_descriptor)); + index_ids, instruction_descriptor, fresh_ids_for_clamping)); }); } diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp new file mode 100644 index 00000000..1547810e --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_add_bit_instruction_synonym.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddBitInstructionSynonyms::FuzzerPassAddBitInstructionSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddBitInstructionSynonyms::~FuzzerPassAddBitInstructionSynonyms() = + default; + +void FuzzerPassAddBitInstructionSynonyms::Apply() { + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + // Randomly decides whether the transformation will be applied. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingBitInstructionSynonym())) { + continue; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557): + // Right now we only support certain operations. When this issue is + // addressed the following conditional can use the function + // |spvOpcodeIsBit|. + if (instruction.opcode() != SpvOpBitwiseOr && + instruction.opcode() != SpvOpBitwiseXor && + instruction.opcode() != SpvOpBitwiseAnd) { + continue; + } + + // Right now, only integer operands are supported. + if (GetIRContext() + ->get_type_mgr() + ->GetType(instruction.type_id()) + ->AsVector()) { + continue; + } + + // Make sure all bit indexes are defined as 32-bit unsigned integers. + uint32_t width = GetIRContext() + ->get_type_mgr() + ->GetType(instruction.type_id()) + ->AsInteger() + ->width(); + for (uint32_t i = 0; i < width; i++) { + FindOrCreateIntegerConstant({i}, 32, false, false); + } + + // Applies the add bit instruction synonym transformation. + ApplyTransformation(TransformationAddBitInstructionSynonym( + instruction.result_id(), + GetFuzzerContext()->GetFreshIds( + TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount( + GetIRContext(), &instruction)))); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h new file mode 100644 index 00000000..0194425d --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// This fuzzer pass adds synonyms for bit instructions. It iterates over the +// module instructions, checks if they are bit instructions and randomly applies +// the transformation. +class FuzzerPassAddBitInstructionSynonyms : public FuzzerPass { + public: + FuzzerPassAddBitInstructionSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddBitInstructionSynonyms(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_ diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp new file mode 100644 index 00000000..515407bc --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp @@ -0,0 +1,235 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/transformation_composite_insert.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default; + +void FuzzerPassAddCompositeInserts::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator instruction_iterator, + const protobufs::InstructionDescriptor& instruction_descriptor) + -> void { + assert(instruction_iterator->opcode() == + instruction_descriptor.target_instruction_opcode() && + "The opcode of the instruction we might insert before must be " + "the same as the opcode in the descriptor for the instruction"); + + // Randomly decide whether to try adding an OpCompositeInsert + // instruction. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) { + return; + } + + // It must be possible to insert an OpCompositeInsert instruction + // before |instruction_iterator|. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpCompositeInsert, instruction_iterator)) { + return; + } + + // Look for available values that have composite type. + std::vector<opt::Instruction*> available_composites = + FindAvailableInstructions( + function, block, instruction_iterator, + [instruction_descriptor]( + opt::IRContext* ir_context, + opt::Instruction* instruction) -> bool { + // |instruction| must be a supported instruction of composite + // type. + if (!TransformationCompositeInsert:: + IsCompositeInstructionSupported(ir_context, + instruction)) { + return false; + } + + auto instruction_type = ir_context->get_type_mgr()->GetType( + instruction->type_id()); + + // No components of the composite can have type + // OpTypeRuntimeArray. + if (ContainsRuntimeArray(*instruction_type)) { + return false; + } + + // No components of the composite can be pointers. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3658): + // Structs can have components of pointer type. + // FindOrCreateZeroConstant cannot be called on a + // pointer. We ignore pointers for now. Consider adding + // support for pointer types. + if (ContainsPointer(*instruction_type)) { + return false; + } + + return true; + }); + + // If there are no available values, then return. + if (available_composites.empty()) { + return; + } + + // Choose randomly one available composite value. + auto available_composite = + available_composites[GetFuzzerContext()->RandomIndex( + available_composites)]; + + // Take a random component of the chosen composite value. If the chosen + // component is itself a composite, then randomly decide whether to take + // its component and repeat. + uint32_t current_node_type_id = available_composite->type_id(); + std::vector<uint32_t> path_to_replaced; + while (true) { + auto current_node_type_inst = + GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id); + uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex( + *current_node_type_inst, GetIRContext()); + + // If the composite is empty, then end the iteration. + if (num_of_components == 0) { + break; + } + uint32_t one_selected_index = + GetFuzzerContext()->GetRandomIndexForCompositeInsert( + num_of_components); + + // Construct a final index by appending the current index. + path_to_replaced.push_back(one_selected_index); + current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + GetIRContext(), current_node_type_id, one_selected_index); + + // If the component is not a composite then end the iteration. + if (!fuzzerutil::IsCompositeType( + GetIRContext()->get_type_mgr()->GetType( + current_node_type_id))) { + break; + } + + // If the component is a composite, but we decide not to go deeper, + // then end the iteration. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfGoingDeeperToInsertInComposite())) { + break; + } + } + + // Look for available objects that have the type id + // |current_node_type_id| and can be inserted. + std::vector<opt::Instruction*> available_objects = + FindAvailableInstructions( + function, block, instruction_iterator, + [instruction_descriptor, current_node_type_id]( + opt::IRContext* /*unused*/, + opt::Instruction* instruction) -> bool { + if (instruction->result_id() == 0 || + instruction->type_id() == 0) { + return false; + } + if (instruction->type_id() != current_node_type_id) { + return false; + } + return true; + }); + + // If there are no objects of the specific type available, check if + // FindOrCreateZeroConstant can be called and create a zero constant of + // this type. + uint32_t available_object_id; + if (available_objects.empty()) { + auto current_node_type = + GetIRContext()->get_type_mgr()->GetType(current_node_type_id); + if (!fuzzerutil::CanCreateConstant(*current_node_type)) { + return; + } + available_object_id = + FindOrCreateZeroConstant(current_node_type_id, false); + } else { + available_object_id = + available_objects[GetFuzzerContext()->RandomIndex( + available_objects)] + ->result_id(); + } + auto new_result_id = GetFuzzerContext()->GetFreshId(); + + // Insert an OpCompositeInsert instruction which copies + // |available_composite| and in the copy inserts the object + // of type |available_object_id| at index |index_to_replace|. + ApplyTransformation(TransformationCompositeInsert( + instruction_descriptor, new_result_id, + available_composite->result_id(), available_object_id, + path_to_replaced)); + }); +} + +bool FuzzerPassAddCompositeInserts::ContainsPointer( + const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kPointer: + return true; + case opt::analysis::Type::kArray: + return ContainsPointer(*type.AsArray()->element_type()); + case opt::analysis::Type::kMatrix: + return ContainsPointer(*type.AsMatrix()->element_type()); + case opt::analysis::Type::kVector: + return ContainsPointer(*type.AsVector()->element_type()); + case opt::analysis::Type::kStruct: + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsPointer(*element_type); + }); + default: + return false; + } +} + +bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray( + const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kRuntimeArray: + return true; + case opt::analysis::Type::kStruct: + // If any component of a struct is of type OpTypeRuntimeArray, return + // true. + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsRuntimeArray(*element_type); + }); + default: + return false; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.h b/source/fuzz/fuzzer_pass_add_composite_inserts.h new file mode 100644 index 00000000..c4f51034 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_composite_inserts.h @@ -0,0 +1,45 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds new OpCompositeInsert instructions to +// available values that have the composite type. +class FuzzerPassAddCompositeInserts : public FuzzerPass { + public: + FuzzerPassAddCompositeInserts( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddCompositeInserts(); + void Apply() override; + + // Checks if any component of a composite is a pointer. + static bool ContainsPointer(const opt::analysis::Type& type); + + // Checks if any component of a composite has type OpTypeRuntimeArray. + static bool ContainsRuntimeArray(const opt::analysis::Type& type); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_ diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp index 304826cc..c4d8d1c9 100644 --- a/source/fuzz/fuzzer_pass_add_composite_types.cpp +++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp @@ -97,7 +97,7 @@ void FuzzerPassAddCompositeTypes::AddNewArrayType() { ApplyTransformation(TransformationAddTypeArray( GetFuzzerContext()->GetFreshId(), ChooseScalarOrCompositeType(), FindOrCreateIntegerConstant( - {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false))); + {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false, false))); } void FuzzerPassAddCompositeTypes::AddNewStructType() { @@ -120,10 +120,15 @@ uint32_t FuzzerPassAddCompositeTypes::ChooseScalarOrCompositeType() { case SpvOpTypeFloat: case SpvOpTypeInt: case SpvOpTypeMatrix: - case SpvOpTypeStruct: case SpvOpTypeVector: candidates.push_back(inst.result_id()); break; + case SpvOpTypeStruct: { + if (!fuzzerutil::MembersHaveBuiltInDecoration(GetIRContext(), + inst.result_id())) { + candidates.push_back(inst.result_id()); + } + } break; default: break; } diff --git a/source/fuzz/fuzzer_pass_add_copy_memory.cpp b/source/fuzz/fuzzer_pass_add_copy_memory.cpp index ed375a1d..d98619c2 100644 --- a/source/fuzz/fuzzer_pass_add_copy_memory.cpp +++ b/source/fuzz/fuzzer_pass_add_copy_memory.cpp @@ -74,7 +74,7 @@ void FuzzerPassAddCopyMemory::Apply() { ApplyTransformation(TransformationAddCopyMemory( instruction_descriptor, GetFuzzerContext()->GetFreshId(), inst->result_id(), storage_class, - FindOrCreateZeroConstant(pointee_type_id))); + FindOrCreateZeroConstant(pointee_type_id, false))); }); } diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp index 56d33e56..84ed1fb9 100644 --- a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp +++ b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp @@ -45,7 +45,7 @@ void FuzzerPassAddDeadBlocks::Apply() { // Make sure the module contains a boolean constant equal to // |condition_value|. bool condition_value = GetFuzzerContext()->ChooseEven(); - FindOrCreateBoolConstant(condition_value); + FindOrCreateBoolConstant(condition_value, false); // We speculatively create a transformation, and then apply it (below) if // it turns out to be applicable. This avoids duplicating the logic for diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp index 521628e2..5873a077 100644 --- a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp +++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" + #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_add_dead_break.h" #include "source/opt/ir_context.h" @@ -77,7 +78,7 @@ void FuzzerPassAddDeadBreaks::Apply() { // Make sure the module has a required boolean constant to be used in // OpBranchConditional instruction. auto break_condition = GetFuzzerContext()->ChooseEven(); - FindOrCreateBoolConstant(break_condition); + FindOrCreateBoolConstant(break_condition, false); auto candidate_transformation = TransformationAddDeadBreak( block.id(), merge_block->id(), break_condition, std::move(phi_ids)); diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp index 8306d049..ed7233f8 100644 --- a/source/fuzz/fuzzer_pass_add_dead_continues.cpp +++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_add_dead_continues.h" + #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_add_dead_continue.h" #include "source/opt/ir_context.h" @@ -68,7 +69,7 @@ void FuzzerPassAddDeadContinues::Apply() { // Make sure the module contains a boolean constant equal to // |condition_value|. bool condition_value = GetFuzzerContext()->ChooseEven(); - FindOrCreateBoolConstant(condition_value); + FindOrCreateBoolConstant(condition_value, false); // Make a transformation to add a dead continue from this node; if the // node turns out to be inappropriate (e.g. by not being in a loop) the diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp index c8f8681f..6376c9fc 100644 --- a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp +++ b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp @@ -21,6 +21,26 @@ namespace spvtools { namespace fuzz { +namespace { + +bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) { + switch (bit_width) { + case 32: + return true; + case 64: + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityFloat64) && + ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64); + case 16: + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityFloat16) && + ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16); + default: + return false; + } +} + +} // namespace FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions( opt::IRContext* ir_context, TransformationContext* transformation_context, @@ -57,9 +77,13 @@ void FuzzerPassAddEquationInstructions::Apply() { std::vector<opt::Instruction*> available_instructions = FindAvailableInstructions( function, block, inst_it, - [](opt::IRContext*, opt::Instruction* instruction) -> bool { + [this](opt::IRContext* /*unused*/, + opt::Instruction* instruction) -> bool { return instruction->result_id() && instruction->type_id() && - instruction->opcode() != SpvOpUndef; + instruction->opcode() != SpvOpUndef && + !GetTransformationContext() + ->GetFactManager() + ->IdIsIrrelevant(instruction->result_id()); }); // Try the opcodes for which we know how to make ids at random until @@ -73,8 +97,22 @@ void FuzzerPassAddEquationInstructions::Apply() { switch (opcode) { case SpvOpConvertSToF: case SpvOpConvertUToF: { - auto candidate_instructions = - GetIntegerInstructions(available_instructions); + std::vector<const opt::Instruction*> candidate_instructions; + for (const auto* inst : + GetIntegerInstructions(available_instructions)) { + const auto* type = + GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + assert(type && "|inst| has invalid type"); + + if (const auto* vector_type = type->AsVector()) { + type = vector_type->element_type(); + } + + if (IsBitWidthSupported(GetIRContext(), + type->AsInteger()->width())) { + candidate_instructions.push_back(inst); + } + } if (candidate_instructions.empty()) { break; @@ -90,10 +128,15 @@ void FuzzerPassAddEquationInstructions::Apply() { // Make sure a result type exists in the module. if (const auto* vector = type->AsVector()) { + // We store element count in a separate variable since the + // call FindOrCreate* functions below might invalidate + // |vector| pointer. + const auto element_count = vector->element_count(); + FindOrCreateVectorType( FindOrCreateFloatType( vector->element_type()->AsInteger()->width()), - vector->element_count()); + element_count); } else { FindOrCreateFloatType(type->AsInteger()->width()); } @@ -104,20 +147,8 @@ void FuzzerPassAddEquationInstructions::Apply() { return; } case SpvOpBitcast: { - std::vector<const opt::Instruction*> candidate_instructions; - for (const auto* inst : available_instructions) { - const auto* type = - GetIRContext()->get_type_mgr()->GetType(inst->type_id()); - assert(type && "Instruction has invalid type"); - if ((type->AsVector() && - (type->AsVector()->element_type()->AsInteger() || - type->AsVector()->element_type()->AsFloat())) || - type->AsInteger() || type->AsFloat()) { - // We support OpBitcast for only scalars or vectors of - // numerical type. - candidate_instructions.push_back(inst); - } - } + const auto candidate_instructions = + GetNumericalInstructions(available_instructions); if (!candidate_instructions.empty()) { const auto* operand_inst = @@ -135,6 +166,11 @@ void FuzzerPassAddEquationInstructions::Apply() { // is that they must have the same number of bits. Consider // improving the code below to support this in full. if (const auto* vector = operand_type->AsVector()) { + // We store element count in a separate variable since the + // call FindOrCreate* functions below might invalidate + // |vector| pointer. + const auto element_count = vector->element_count(); + uint32_t element_type_id; if (const auto* int_type = vector->element_type()->AsInteger()) { @@ -147,8 +183,7 @@ void FuzzerPassAddEquationInstructions::Apply() { GetFuzzerContext()->ChooseEven()); } - FindOrCreateVectorType(element_type_id, - vector->element_count()); + FindOrCreateVectorType(element_type_id, element_count); } else if (const auto* int_type = operand_type->AsInteger()) { FindOrCreateFloatType(int_type->width()); } else { @@ -344,5 +379,36 @@ FuzzerPassAddEquationInstructions::RestrictToElementBitWidth( return result; } +std::vector<opt::Instruction*> +FuzzerPassAddEquationInstructions::GetNumericalInstructions( + const std::vector<opt::Instruction*>& instructions) const { + std::vector<opt::Instruction*> result; + + for (auto* inst : instructions) { + const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id()); + assert(type && "Instruction has invalid type"); + + if (const auto* vector_type = type->AsVector()) { + type = vector_type->element_type(); + } + + if (!type->AsInteger() && !type->AsFloat()) { + // Only numerical scalars or vectors of numerical components are + // supported. + continue; + } + + if (!IsBitWidthSupported(GetIRContext(), type->AsInteger() + ? type->AsInteger()->width() + : type->AsFloat()->width())) { + continue; + } + + result.push_back(inst); + } + + return result; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.h b/source/fuzz/fuzzer_pass_add_equation_instructions.h index 8328b6b8..9ce581eb 100644 --- a/source/fuzz/fuzzer_pass_add_equation_instructions.h +++ b/source/fuzz/fuzzer_pass_add_equation_instructions.h @@ -51,6 +51,14 @@ class FuzzerPassAddEquationInstructions : public FuzzerPass { std::vector<opt::Instruction*> GetBooleanInstructions( const std::vector<opt::Instruction*>& instructions) const; + // Yields those instructions in |instructions| that have a scalar numerical or + // a vector of numerical components type. Only 16, 32 and 64-bit numericals + // are supported if both OpTypeInt and OpTypeFloat instructions can be created + // with the specified width (e.g. for 16-bit types both Float16 and Int16 + // capabilities must be present). + std::vector<opt::Instruction*> GetNumericalInstructions( + const std::vector<opt::Instruction*>& instructions) const; + // Requires that |instructions| are scalars or vectors of some type. Returns // only those instructions whose width is |width|. If |width| is 1 this means // the scalars. diff --git a/source/fuzz/fuzzer_pass_add_function_calls.cpp b/source/fuzz/fuzzer_pass_add_function_calls.cpp index 10755d5d..b6f4c85d 100644 --- a/source/fuzz/fuzzer_pass_add_function_calls.cpp +++ b/source/fuzz/fuzzer_pass_add_function_calls.cpp @@ -174,7 +174,7 @@ std::vector<uint32_t> FuzzerPassAddFunctionCalls::ChooseFunctionCallArguments( // function, noting that its pointee value is irrelevant. ApplyTransformation(TransformationAddLocalVariable( fresh_variable_id, param->type_id(), caller_function->result_id(), - FindOrCreateZeroConstant(pointee_type_id), true)); + FindOrCreateZeroConstant(pointee_type_id, false), true)); } else { assert((storage_class == SpvStorageClassPrivate || storage_class == SpvStorageClassWorkgroup) && @@ -186,7 +186,7 @@ std::vector<uint32_t> FuzzerPassAddFunctionCalls::ChooseFunctionCallArguments( ApplyTransformation(TransformationAddGlobalVariable( fresh_variable_id, param->type_id(), storage_class, storage_class == SpvStorageClassPrivate - ? FindOrCreateZeroConstant(pointee_type_id) + ? FindOrCreateZeroConstant(pointee_type_id, false) : 0, true)); } diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp index 2a6fdc28..9a45a374 100644 --- a/source/fuzz/fuzzer_pass_add_global_variables.cpp +++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp @@ -85,7 +85,7 @@ void FuzzerPassAddGlobalVariables::Apply() { GetFuzzerContext()->GetFreshId(), pointer_type_id, variable_storage_class, variable_storage_class == SpvStorageClassPrivate - ? FindOrCreateZeroConstant(basic_type) + ? FindOrCreateZeroConstant(basic_type, false) : 0, true)); } diff --git a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp index 01fd282d..3095bb6e 100644 --- a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp +++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp @@ -184,7 +184,7 @@ void FuzzerPassAddImageSampleUnusedComponents::Apply() { // FindOrCreateZeroConstant // %20 = OpConstant %4 0 // %21 = OpConstantComposite %5 %20 %20 - FindOrCreateZeroConstant(zero_constant_type_id)}, + FindOrCreateZeroConstant(zero_constant_type_id, true)}, MakeInstructionDescriptor(GetIRContext(), instruction), coordinate_with_unused_components_id)); diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp index 661159e6..ef8b5d0e 100644 --- a/source/fuzz/fuzzer_pass_add_local_variables.cpp +++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp @@ -71,7 +71,7 @@ void FuzzerPassAddLocalVariables::Apply() { } ApplyTransformation(TransformationAddLocalVariable( GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(), - FindOrCreateZeroConstant(basic_type), true)); + FindOrCreateZeroConstant(basic_type, false), true)); } } } diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp new file mode 100644 index 00000000..bdc31513 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_loop_preheader.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default; + +void FuzzerPassAddLoopPreheaders::Apply() { + for (auto& function : *GetIRContext()->module()) { + // Keep track of all the loop headers we want to add a preheader to. + std::vector<uint32_t> loop_header_ids_to_consider; + for (auto& block : function) { + // We only care about loop headers. + if (!block.IsLoopHeader()) { + continue; + } + + // Randomly decide whether to consider this header. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingLoopPreheader())) { + continue; + } + + // We exclude loop headers with just one predecessor (the back-edge block) + // because they are unreachable. + if (GetIRContext()->cfg()->preds(block.id()).size() < 2) { + continue; + } + + loop_header_ids_to_consider.push_back(block.id()); + } + + for (uint32_t header_id : loop_header_ids_to_consider) { + // If not already present, add a preheader which is not also a loop + // header. + GetOrCreateSimpleLoopPreheader(header_id); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.h b/source/fuzz/fuzzer_pass_add_loop_preheaders.h new file mode 100644 index 00000000..a8350567 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.h @@ -0,0 +1,43 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that randomly adds simple loop preheaders to loops that do not +// have one. A simple loop preheader is a block that: +// - is the only out-of-loop predecessor of the header +// - branches unconditionally to the header +// - is not a loop header itself +class FuzzerPassAddLoopPreheaders : public FuzzerPass { + public: + FuzzerPassAddLoopPreheaders( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLoopPreheaders(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp new file mode 100644 index 00000000..1b286dd1 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp @@ -0,0 +1,247 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" + +#include "source/fuzz/call_graph.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" + +namespace spvtools { +namespace fuzz { +namespace { +uint32_t kMaxNestingDepth = 4; +} // namespace + +FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + FuzzerPassAddLoopsToCreateIntConstantSynonyms( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default; + +void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() { + std::vector<uint32_t> constants; + + // Choose the constants for which to create synonyms. + for (auto constant_def : GetIRContext()->GetConstants()) { + // Randomly decide whether to consider this constant. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfCreatingIntSynonymsUsingLoops())) { + continue; + } + + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant( + constant_def->result_id()); + + // We only consider integer constants (scalar or vector). + if (!constant->AsIntConstant() && + !(constant->AsVectorConstant() && + constant->AsVectorConstant()->component_type()->AsInteger())) { + continue; + } + + constants.push_back(constant_def->result_id()); + } + + std::vector<uint32_t> blocks; + + // Get a list of all the blocks before which we can add a loop creating a new + // synonym. We cannot apply the transformation while iterating over the + // module, because we are going to add new blocks. + for (auto& function : *GetIRContext()->module()) { + // Consider all blocks reachable from the first block of the function. + GetIRContext()->cfg()->ForEachBlockInPostOrder( + &*function.begin(), + [&blocks](opt::BasicBlock* block) { blocks.push_back(block->id()); }); + } + + // Make sure that the module has an OpTypeBool instruction, and 32-bit signed + // integer constants 0 and 1, adding them if necessary. + FindOrCreateBoolType(); + FindOrCreateIntegerConstant({0}, 32, true, false); + FindOrCreateIntegerConstant({1}, 32, true, false); + + // Compute the call graph. We can use this for any further computation, since + // we are not adding or removing functions or function calls. + auto call_graph = CallGraph(GetIRContext()); + + // Consider each constant and each block. + for (uint32_t constant_id : constants) { + // Choose one of the blocks. + uint32_t block_id = blocks[GetFuzzerContext()->RandomIndex(blocks)]; + + // Adjust the block so that the transformation can be applied. + auto block = GetIRContext()->get_instr_block(block_id); + + // If the block is a loop header, add a simple preheader. We can do this + // because we have excluded all the non-reachable headers. + if (block->IsLoopHeader()) { + block = GetOrCreateSimpleLoopPreheader(block->id()); + block_id = block->id(); + } + + assert(!block->IsLoopHeader() && + "The block cannot be a loop header at this point."); + + // If the block is a merge block, a continue block or it does not have + // exactly 1 predecessor, split it after any OpPhi or OpVariable + // instructions. + if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id()) || + GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + block->id()) || + GetIRContext()->cfg()->preds(block->id()).size() != 1) { + block = SplitBlockAfterOpPhiOrOpVariable(block->id()); + block_id = block->id(); + } + + // Randomly decide the values for the number of iterations and the step + // value, and compute the initial value accordingly. + + // The maximum number of iterations depends on the maximum possible loop + // nesting depth of the block, computed interprocedurally, i.e. also + // considering the possibility that the enclosing function is called inside + // a loop. It is: + // - 1 if the nesting depth is >= kMaxNestingDepth + // - 2^(kMaxNestingDepth - nesting_depth) otherwise + uint32_t max_nesting_depth = + call_graph.GetMaxCallNestingDepth(block->GetParent()->result_id()) + + GetIRContext()->GetStructuredCFGAnalysis()->LoopNestingDepth( + block->id()); + uint32_t num_iterations = + max_nesting_depth >= kMaxNestingDepth + ? 1 + : GetFuzzerContext()->GetRandomNumberOfLoopIterations( + 1u << (kMaxNestingDepth - max_nesting_depth)); + + // Find or create the corresponding constant containing the number of + // iterations. + uint32_t num_iterations_id = + FindOrCreateIntegerConstant({num_iterations}, 32, true, false); + + // Find the other constants. + // We use 64-bit values and then use the bits that we need. We find the + // step value (S) randomly and then compute the initial value (I) using + // the equation I = C + S*N. + uint32_t initial_value_id = 0; + uint32_t step_value_id = 0; + + // Get the content of the existing constant. + const auto constant = + GetIRContext()->get_constant_mgr()->FindDeclaredConstant(constant_id); + const auto constant_type_id = + GetIRContext()->get_def_use_mgr()->GetDef(constant_id)->type_id(); + + if (constant->AsIntConstant()) { + // The constant is a scalar integer. + + std::tie(initial_value_id, step_value_id) = + FindSuitableStepAndInitialValueConstants( + constant->GetZeroExtendedValue(), + constant->type()->AsInteger()->width(), + constant->type()->AsInteger()->IsSigned(), num_iterations); + } else { + // The constant is a vector of integers. + assert(constant->AsVectorConstant() && + constant->AsVectorConstant()->component_type()->AsInteger() && + "If the program got here, the constant should be a vector of " + "integers."); + + // Find a constant for each component of the initial value and the step + // values. + std::vector<uint32_t> initial_value_component_ids; + std::vector<uint32_t> step_value_component_ids; + + // Get the value, width and signedness of the components. + std::vector<uint64_t> component_values; + for (auto component : constant->AsVectorConstant()->GetComponents()) { + component_values.push_back(component->GetZeroExtendedValue()); + } + uint32_t bit_width = + constant->AsVectorConstant()->component_type()->AsInteger()->width(); + uint32_t is_signed = constant->AsVectorConstant() + ->component_type() + ->AsInteger() + ->IsSigned(); + + for (uint64_t component_val : component_values) { + uint32_t initial_val_id; + uint32_t step_val_id; + std::tie(initial_val_id, step_val_id) = + FindSuitableStepAndInitialValueConstants(component_val, bit_width, + is_signed, num_iterations); + initial_value_component_ids.push_back(initial_val_id); + step_value_component_ids.push_back(step_val_id); + } + + // Find or create the vector constants. + initial_value_id = FindOrCreateCompositeConstant( + initial_value_component_ids, constant_type_id, false); + step_value_id = FindOrCreateCompositeConstant(step_value_component_ids, + constant_type_id, false); + } + + assert(initial_value_id && step_value_id && + "|initial_value_id| and |step_value_id| should have been defined."); + + // Randomly decide whether to have two blocks (or just one) in the new + // loop. + uint32_t additional_block_id = + GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym()) + ? GetFuzzerContext()->GetFreshId() + : 0; + + // Add the loop and create the synonym. + ApplyTransformation(TransformationAddLoopToCreateIntConstantSynonym( + constant_id, initial_value_id, step_value_id, num_iterations_id, + block_id, GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(), + additional_block_id)); + } +} + +std::pair<uint32_t, uint32_t> FuzzerPassAddLoopsToCreateIntConstantSynonyms:: + FindSuitableStepAndInitialValueConstants(uint64_t constant_val, + uint32_t bit_width, bool is_signed, + uint32_t num_iterations) { + // Choose the step value randomly and compute the initial value accordingly. + // The result of |initial_value| could overflow, but this is OK, since + // the transformation takes overflows into consideration (the equation still + // holds as long as the last |bit_width| bits of C and of (I-S*N) match). + uint64_t step_value = + GetFuzzerContext()->GetRandomValueForStepConstantInLoop(); + uint64_t initial_value = constant_val + step_value * num_iterations; + + uint32_t initial_val_id = FindOrCreateIntegerConstant( + fuzzerutil::IntToWords(initial_value, bit_width, is_signed), bit_width, + is_signed, false); + + uint32_t step_val_id = FindOrCreateIntegerConstant( + fuzzerutil::IntToWords(step_value, bit_width, is_signed), bit_width, + is_signed, false); + + return {initial_val_id, step_val_id}; +} + +} // namespace fuzz +} // namespace spvtools
\ No newline at end of file diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h new file mode 100644 index 00000000..ee98c4e7 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that adds synonyms for integer, scalar or vector, constants, by +// adding loops that compute the same value by subtracting a value S from an +// initial value I, and for N times, so that C = I - S*N. +class FuzzerPassAddLoopsToCreateIntConstantSynonyms : public FuzzerPass { + public: + FuzzerPassAddLoopsToCreateIntConstantSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddLoopsToCreateIntConstantSynonyms(); + + void Apply() override; + + private: + // Returns a pair (initial_val_id, step_val_id) such that both ids are + // integer scalar constants of the same type as the scalar integer constant + // identified by the given |constant_val|, |bit_width| and signedness, and + // such that, if I is the value of initial_val_id, S is the value of + // step_val_id and C is the value of the constant, the equation (C = I - S * + // num_iterations) holds, (only considering the last |bit_width| bits of each + // constant). + std::pair<uint32_t, uint32_t> FindSuitableStepAndInitialValueConstants( + uint64_t constant_val, uint32_t bit_width, bool is_signed, + uint32_t num_iterations); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_ diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h index f32e5bc6..c0c1e09d 100644 --- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h +++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_ -#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -36,4 +36,4 @@ class FuzzerPassAddNoContractionDecorations : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_ diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp new file mode 100644 index 00000000..88cc830f --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp @@ -0,0 +1,306 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_opphi_synonym.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default; + +void FuzzerPassAddOpPhiSynonyms::Apply() { + // Get a list of synonymous ids with the same type that can be used in the + // same OpPhi instruction. + auto equivalence_classes = GetIdEquivalenceClasses(); + + // Make a list of references, to avoid copying sets unnecessarily. + std::vector<std::set<uint32_t>*> equivalence_class_pointers; + for (auto& set : equivalence_classes) { + equivalence_class_pointers.push_back(&set); + } + + // Keep a list of transformations to apply at the end. + std::vector<TransformationAddOpPhiSynonym> transformations_to_apply; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Randomly decide whether to consider this block. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) { + continue; + } + + // The block must have at least one predecessor. + size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size(); + if (num_preds == 0) { + continue; + } + + std::set<uint32_t>* chosen_equivalence_class = nullptr; + + if (num_preds > 1) { + // If the block has more than one predecessor, prioritise sets with at + // least 2 ids available at some predecessor. + chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( + equivalence_class_pointers, block.id(), 2); + } + + // If a set was not already chosen, choose one with at least one available + // id. + if (!chosen_equivalence_class) { + chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly( + equivalence_class_pointers, block.id(), 1); + } + + // If no suitable set was found, we cannot apply the transformation to + // this block. + if (!chosen_equivalence_class) { + continue; + } + + // Initialise the map from predecessor labels to ids. + std::map<uint32_t, uint32_t> preds_to_ids; + + // Keep track of the ids used and of the id of a predecessor with at least + // two ids to choose from. This is to ensure that, if possible, at least + // two distinct ids will be used. + std::set<uint32_t> ids_chosen; + uint32_t pred_with_alternatives = 0; + + // Choose an id for each predecessor. + for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) { + auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id); + assert(!suitable_ids.empty() && + "We must be able to find at least one suitable id because the " + "equivalence class was chosen among suitable ones."); + + // If this predecessor has more than one id to choose from and it is the + // first one of this kind that we found, remember its id. + if (suitable_ids.size() > 1 && !pred_with_alternatives) { + pred_with_alternatives = pred_id; + } + + uint32_t chosen_id = + suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)]; + + // Add this id to the set of ids chosen. + ids_chosen.emplace(chosen_id); + + // Add the pair (predecessor, chosen id) to the map. + preds_to_ids[pred_id] = chosen_id; + } + + // If: + // - the block has more than one predecessor + // - at least one predecessor has more than one alternative + // - the same id has been chosen by all the predecessors + // then choose another one for the predecessor with more than one + // alternative. + if (num_preds > 1 && pred_with_alternatives != 0 && + ids_chosen.size() == 1) { + auto suitable_ids = + GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives); + uint32_t chosen_id = + GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); + if (chosen_id == preds_to_ids[pred_with_alternatives]) { + chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids); + } + + preds_to_ids[pred_with_alternatives] = chosen_id; + } + + // Add the transformation to the list of transformations to apply. + transformations_to_apply.emplace_back(block.id(), preds_to_ids, + GetFuzzerContext()->GetFreshId()); + } + } + + // Apply the transformations. + for (const auto& transformation : transformations_to_apply) { + ApplyTransformation(transformation); + } +} + +std::vector<std::set<uint32_t>> +FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() { + std::vector<std::set<uint32_t>> id_equivalence_classes; + + // Keep track of all the ids that have already be assigned to a class. + std::set<uint32_t> already_in_a_class; + + for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) { + // Exclude ids that have already been assigned to a class. + if (already_in_a_class.count(pair.first)) { + continue; + } + + // Exclude irrelevant ids. + if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + pair.first)) { + continue; + } + + // Exclude ids having a type that is not allowed by the transformation. + if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed( + GetIRContext(), pair.second->type_id())) { + continue; + } + + // Exclude OpFunction and OpUndef instructions, because: + // - OpFunction does not yield a value; + // - OpUndef yields an undefined value at each use, so it should never be a + // synonym of another id. + if (pair.second->opcode() == SpvOpFunction || + pair.second->opcode() == SpvOpUndef) { + continue; + } + + // We need a new equivalence class for this id. + std::set<uint32_t> new_equivalence_class; + + // Add this id to the class. + new_equivalence_class.emplace(pair.first); + already_in_a_class.emplace(pair.first); + + // Add all the synonyms with the same type to this class. + for (auto synonym : + GetTransformationContext()->GetFactManager()->GetSynonymsForId( + pair.first)) { + // The synonym must be a plain id - it cannot be an indexed access into a + // composite. + if (synonym->index_size() > 0) { + continue; + } + + // The synonym must not be irrelevant. + if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + synonym->object())) { + continue; + } + + auto synonym_def = + GetIRContext()->get_def_use_mgr()->GetDef(synonym->object()); + // The synonym must exist and have the same type as the id we are + // considering. + if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) { + continue; + } + + // We can add this synonym to the new equivalence class. + new_equivalence_class.emplace(synonym->object()); + already_in_a_class.emplace(synonym->object()); + } + + // Add the new equivalence class to the list of equivalence classes. + id_equivalence_classes.emplace_back(std::move(new_equivalence_class)); + } + + return id_equivalence_classes; +} + +bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock( + const std::set<uint32_t>& equivalence_class, uint32_t block_id, + uint32_t distinct_ids_required) { + bool at_least_one_id_for_each_pred = true; + + // Keep a set of the suitable ids found. + std::set<uint32_t> suitable_ids_found; + + // Loop through all the predecessors of the block. + for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) { + // Find the last instruction in the predecessor block. + auto last_instruction = + GetIRContext()->get_instr_block(pred_id)->terminator(); + + // Initially assume that there is not a suitable id for this predecessor. + bool at_least_one_suitable_id_found = false; + for (uint32_t id : equivalence_class) { + if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(), + last_instruction, id)) { + // We have found a suitable id. + at_least_one_suitable_id_found = true; + suitable_ids_found.emplace(id); + + // If we have already found enough distinct suitable ids, we don't need + // to check the remaining ones for this predecessor. + if (suitable_ids_found.size() >= distinct_ids_required) { + break; + } + } + } + // If no suitable id was found for this predecessor, this equivalence class + // is not suitable and we don't need to check the other predecessors. + if (!at_least_one_suitable_id_found) { + at_least_one_id_for_each_pred = false; + break; + } + } + + // The equivalence class is suitable if at least one suitable id was found for + // each predecessor and we have found at least |distinct_ids_required| + // distinct suitable ids in general. + return at_least_one_id_for_each_pred && + suitable_ids_found.size() >= distinct_ids_required; +} + +std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds( + const std::set<uint32_t>& ids, uint32_t pred_id) { + // Initialise an empty vector of suitable ids. + std::vector<uint32_t> suitable_ids; + + // Get the predecessor block. + auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id); + + // Loop through the ids to find the suitable ones. + for (uint32_t id : ids) { + if (fuzzerutil::IdIsAvailableBeforeInstruction( + GetIRContext(), predecessor->terminator(), id)) { + suitable_ids.push_back(id); + } + } + + return suitable_ids; +} + +std::set<uint32_t>* +FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly( + const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id, + uint32_t distinct_ids_required) { + auto remaining_candidates = candidates; + while (!remaining_candidates.empty()) { + // Choose one set randomly and return it if it is suitable. + auto chosen = + GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates); + if (EquivalenceClassIsSuitableForBlock(*chosen, block_id, + distinct_ids_required)) { + return chosen; + } + } + + // No suitable sets were found. + return nullptr; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.h b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h new file mode 100644 index 00000000..6c7d7522 --- /dev/null +++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h @@ -0,0 +1,73 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass to add OpPhi instructions which can take the values of ids that +// have been marked as synonymous. This instruction will itself be marked as +// synonymous with the others. +class FuzzerPassAddOpPhiSynonyms : public FuzzerPass { + public: + FuzzerPassAddOpPhiSynonyms( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddOpPhiSynonyms() override; + + void Apply() override; + + // Computes the equivalence classes for the non-pointer and non-irrelevant ids + // in the module, where two ids are considered equivalent iff they have been + // declared synonymous and they have the same type. + std::vector<std::set<uint32_t>> GetIdEquivalenceClasses(); + + // Returns true iff |equivalence_class| contains at least + // |distinct_ids_required| ids so that all of these ids are available at the + // end of at least one predecessor of the block with label |block_id|. + // Assumes that the block has at least one predecessor. + bool EquivalenceClassIsSuitableForBlock( + const std::set<uint32_t>& equivalence_class, uint32_t block_id, + uint32_t distinct_ids_required); + + // Returns a vector with the ids that are available to use at the end of the + // block with id |pred_id|, selected among the given |ids|. Assumes that + // |pred_id| is the label of a block and all ids in |ids| exist in the module. + std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids, + uint32_t pred_id); + + private: + // Randomly chooses one of the equivalence classes in |candidates|, so that it + // satisfies all of the following conditions: + // - For each of the predecessors of the |block_id| block, there is at least + // one id in the chosen equivalence class that is available at the end of + // it. + // - There are at least |distinct_ids_required| ids available at the end of + // some predecessor. + // Returns nullptr if no equivalence class in |candidates| satisfies the + // requirements. + std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly( + const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id, + uint32_t distinct_ids_required); +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_ diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp index c5c9c336..3600a0f6 100644 --- a/source/fuzz/fuzzer_pass_add_parameters.cpp +++ b/source/fuzz/fuzzer_pass_add_parameters.cpp @@ -69,14 +69,66 @@ void FuzzerPassAddParameters::Apply() { auto num_new_parameters = GetFuzzerContext()->GetRandomNumberOfNewParameters( GetNumberOfParameters(function)); + for (uint32_t i = 0; i < num_new_parameters; ++i) { + auto current_type_id = + type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)]; + auto current_type = + GetIRContext()->get_type_mgr()->GetType(current_type_id); + std::map<uint32_t, uint32_t> call_parameter_ids; + + // Consider the case when a pointer type was selected. + if (current_type->kind() == opt::analysis::Type::kPointer) { + auto storage_class = fuzzerutil::GetStorageClassFromPointerType( + GetIRContext(), current_type_id); + switch (storage_class) { + case SpvStorageClassFunction: { + // In every caller find or create a local variable that has the + // selected type. + for (auto* instr : + fuzzerutil::GetCallers(GetIRContext(), function.result_id())) { + auto block = GetIRContext()->get_instr_block(instr); + auto function_id = block->GetParent()->result_id(); + uint32_t variable_id = + FindOrCreateLocalVariable(current_type_id, function_id, true); + call_parameter_ids[instr->result_id()] = variable_id; + } + } break; + case SpvStorageClassPrivate: + case SpvStorageClassWorkgroup: { + // If there exists at least one caller, find or create a global + // variable that has the selected type. + std::vector<opt::Instruction*> callers = + fuzzerutil::GetCallers(GetIRContext(), function.result_id()); + if (!callers.empty()) { + uint32_t variable_id = + FindOrCreateGlobalVariable(current_type_id, true); + for (auto* instr : callers) { + call_parameter_ids[instr->result_id()] = variable_id; + } + } + } break; + default: + break; + } + } else { + // If there exists at least one caller, find or create a zero constant + // that has the selected type. + std::vector<opt::Instruction*> callers = + fuzzerutil::GetCallers(GetIRContext(), function.result_id()); + if (!callers.empty()) { + uint32_t constant_id = + FindOrCreateZeroConstant(current_type_id, true); + for (auto* instr : + fuzzerutil::GetCallers(GetIRContext(), function.result_id())) { + call_parameter_ids[instr->result_id()] = constant_id; + } + } + } + ApplyTransformation(TransformationAddParameter( function.result_id(), GetFuzzerContext()->GetFreshId(), - // We mark the constant as irrelevant so that we can replace it with a - // more interesting value later. - FindOrCreateZeroConstant( - type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)], - true), + current_type_id, std::move(call_parameter_ids), GetFuzzerContext()->GetFreshId())); } } diff --git a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h index dad5dfc9..897b8216 100644 --- a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h +++ b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H -#define SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -36,4 +36,4 @@ class FuzzerPassAddRelaxedDecorations : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_ diff --git a/source/fuzz/fuzzer_pass_add_synonyms.cpp b/source/fuzz/fuzzer_pass_add_synonyms.cpp index e7bf6fab..2fa07000 100644 --- a/source/fuzz/fuzzer_pass_add_synonyms.cpp +++ b/source/fuzz/fuzzer_pass_add_synonyms.cpp @@ -36,6 +36,12 @@ void FuzzerPassAddSynonyms::Apply() { [this](opt::Function* function, opt::BasicBlock* block, opt::BasicBlock::iterator inst_it, const protobufs::InstructionDescriptor& instruction_descriptor) { + if (GetTransformationContext()->GetFactManager()->BlockIsDead( + block->id())) { + // Don't create synonyms in dead blocks. + return; + } + // Skip |inst_it| if we can't insert anything above it. OpIAdd is just // a representative of some instruction that might be produced by the // transformation. @@ -77,7 +83,7 @@ void FuzzerPassAddSynonyms::Apply() { case protobufs::TransformationAddSynonym::LOGICAL_OR: // Create a zero constant to be used as an operand of the synonymous // instruction. - FindOrCreateZeroConstant(existing_synonym->type_id()); + FindOrCreateZeroConstant(existing_synonym->type_id(), false); break; case protobufs::TransformationAddSynonym::MUL_ONE: case protobufs::TransformationAddSynonym::LOGICAL_AND: { @@ -97,13 +103,13 @@ void FuzzerPassAddSynonyms::Apply() { FindOrCreateCompositeConstant( std::vector<uint32_t>( vector->element_count(), - FindOrCreateConstant({one_word}, element_type_id)), - existing_synonym->type_id()); + FindOrCreateConstant({one_word}, element_type_id, false)), + existing_synonym->type_id(), false); } else { FindOrCreateConstant( {existing_synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u}, - existing_synonym->type_id()); + existing_synonym->type_id(), false); } } break; default: diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.h b/source/fuzz/fuzzer_pass_adjust_function_controls.h index e20541b1..5fdbe432 100644 --- a/source/fuzz/fuzzer_pass_adjust_function_controls.h +++ b/source/fuzz/fuzzer_pass_adjust_function_controls.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_ -#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -36,4 +36,4 @@ class FuzzerPassAdjustFunctionControls : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_ diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h index ee5cd483..f133b2d1 100644 --- a/source/fuzz/fuzzer_pass_adjust_loop_controls.h +++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_ -#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -36,4 +36,4 @@ class FuzzerPassAdjustLoopControls : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_ diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.h b/source/fuzz/fuzzer_pass_adjust_selection_controls.h index 820b30d4..910b40d6 100644 --- a/source/fuzz/fuzzer_pass_adjust_selection_controls.h +++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_ -#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -36,4 +36,4 @@ class FuzzerPassAdjustSelectionControls : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_ diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp index 2808ad56..9cbf590c 100644 --- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp +++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp @@ -60,7 +60,7 @@ void FuzzerPassApplyIdSynonyms::Apply() { } }); - for (auto& use : uses) { + for (const auto& use : uses) { auto use_inst = use.first; auto use_index = use.second; auto block_containing_use = GetIRContext()->get_instr_block(use_inst); @@ -76,13 +76,13 @@ void FuzzerPassApplyIdSynonyms::Apply() { // the index of the operand restricted to input operands only. uint32_t use_in_operand_index = fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); - if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym( - GetIRContext(), use_inst, use_in_operand_index)) { + if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst, + use_in_operand_index)) { continue; } std::vector<const protobufs::DataDescriptor*> synonyms_to_try; - for (auto& data_descriptor : + for (const auto* data_descriptor : GetTransformationContext()->GetFactManager()->GetSynonymsForId( id_with_known_synonyms)) { protobufs::DataDescriptor descriptor_for_this_id = @@ -91,7 +91,12 @@ void FuzzerPassApplyIdSynonyms::Apply() { // Exclude the fact that the id is synonymous with itself. continue; } - synonyms_to_try.push_back(data_descriptor); + + if (DataDescriptorsHaveCompatibleTypes( + use_inst->opcode(), use_in_operand_index, + descriptor_for_this_id, *data_descriptor)) { + synonyms_to_try.push_back(data_descriptor); + } } while (!synonyms_to_try.empty()) { auto synonym_to_try = @@ -162,5 +167,26 @@ void FuzzerPassApplyIdSynonyms::Apply() { } } +bool FuzzerPassApplyIdSynonyms::DataDescriptorsHaveCompatibleTypes( + SpvOp opcode, uint32_t use_in_operand_index, + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2) { + auto base_object_type_id_1 = + fuzzerutil::GetTypeId(GetIRContext(), dd1.object()); + auto base_object_type_id_2 = + fuzzerutil::GetTypeId(GetIRContext(), dd2.object()); + assert(base_object_type_id_1 && base_object_type_id_2 && + "Data descriptors are invalid"); + + auto type_id_1 = fuzzerutil::WalkCompositeTypeIndices( + GetIRContext(), base_object_type_id_1, dd1.index()); + auto type_id_2 = fuzzerutil::WalkCompositeTypeIndices( + GetIRContext(), base_object_type_id_2, dd2.index()); + assert(type_id_1 && type_id_2 && "Data descriptors have invalid types"); + + return TransformationReplaceIdWithSynonym::TypesAreCompatible( + GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2); +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.h b/source/fuzz/fuzzer_pass_apply_id_synonyms.h index 1a9213db..5deac105 100644 --- a/source/fuzz/fuzzer_pass_apply_id_synonyms.h +++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.h @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_ -#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_ #include "source/fuzz/fuzzer_pass.h" - #include "source/opt/ir_context.h" namespace spvtools { @@ -34,9 +33,19 @@ class FuzzerPassApplyIdSynonyms : public FuzzerPass { ~FuzzerPassApplyIdSynonyms() override; void Apply() override; + + private: + // Returns true if uses of |dd1| can be replaced with |dd2| and vice-versa + // with respect to the type. Concretely, returns true if |dd1| and |dd2| have + // the same type or both |dd1| and |dd2| are either a numerical or a vector + // type of integral components with possibly different signedness. + bool DataDescriptorsHaveCompatibleTypes(SpvOp opcode, + uint32_t use_in_operand_index, + const protobufs::DataDescriptor& dd1, + const protobufs::DataDescriptor& dd2); }; } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_ diff --git a/source/fuzz/fuzzer_pass_construct_composites.h b/source/fuzz/fuzzer_pass_construct_composites.h index 9853fadf..c140bded 100644 --- a/source/fuzz/fuzzer_pass_construct_composites.h +++ b/source/fuzz/fuzzer_pass_construct_composites.h @@ -15,11 +15,11 @@ #ifndef SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_ #define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_ -#include "source/fuzz/fuzzer_pass.h" - #include <map> #include <vector> +#include "source/fuzz/fuzzer_pass.h" + namespace spvtools { namespace fuzz { diff --git a/source/fuzz/fuzzer_pass_copy_objects.cpp b/source/fuzz/fuzzer_pass_copy_objects.cpp index 81326ac7..9f7bbd63 100644 --- a/source/fuzz/fuzzer_pass_copy_objects.cpp +++ b/source/fuzz/fuzzer_pass_copy_objects.cpp @@ -41,6 +41,12 @@ void FuzzerPassCopyObjects::Apply() { "The opcode of the instruction we might insert before must be " "the same as the opcode in the descriptor for the instruction"); + if (GetTransformationContext()->GetFactManager()->BlockIsDead( + block->id())) { + // Don't create synonyms in dead blocks. + return; + } + // Check whether it is legitimate to insert a copy before this // instruction. if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject, @@ -54,13 +60,13 @@ void FuzzerPassCopyObjects::Apply() { return; } - std::vector<opt::Instruction*> relevant_instructions = - FindAvailableInstructions( - function, block, inst_it, - [this](opt::IRContext* ir_context, opt::Instruction* inst) { - return fuzzerutil::CanMakeSynonymOf( - ir_context, *GetTransformationContext(), inst); - }); + const auto relevant_instructions = FindAvailableInstructions( + function, block, inst_it, + [this](opt::IRContext* ir_context, opt::Instruction* inst) { + return TransformationAddSynonym::IsInstructionValid( + ir_context, *GetTransformationContext(), inst, + protobufs::TransformationAddSynonym::COPY_OBJECT); + }); // At this point, |relevant_instructions| contains all the instructions // we might think of copying. diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp index 15201baa..afa22247 100644 --- a/source/fuzz/fuzzer_pass_donate_modules.cpp +++ b/source/fuzz/fuzzer_pass_donate_modules.cpp @@ -84,6 +84,16 @@ void FuzzerPassDonateModules::Apply() { void FuzzerPassDonateModules::DonateSingleModule( opt::IRContext* donor_ir_context, bool make_livesafe) { + // Check that the donated module has capabilities, supported by the recipient + // module. + for (const auto& capability_inst : donor_ir_context->capabilities()) { + auto capability = + static_cast<SpvCapability>(capability_inst.GetSingleWordInOperand(0)); + if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) { + return; + } + } + // The ids used by the donor module may very well clash with ids defined in // the recipient module. Furthermore, some instructions defined in the donor // module will be equivalent to instructions defined in the recipient module, @@ -328,7 +338,8 @@ void FuzzerPassDonateModules::HandleTypeOrValue( ApplyTransformation(TransformationAddTypeArray( new_result_id, original_id_to_donated_id->at(component_type_id), FindOrCreateIntegerConstant( - {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false))); + {GetFuzzerContext()->GetRandomSizeForNewArray()}, 32, false, + false))); } break; case SpvOpTypeStruct: { // Similar to SpvOpTypeArray. @@ -446,7 +457,7 @@ void FuzzerPassDonateModules::HandleTypeOrValue( auto value = type_or_value.opcode() == SpvOpConstantTrue || type_or_value.opcode() == SpvOpSpecConstantTrue; ApplyTransformation( - TransformationAddConstantBoolean(new_result_id, value)); + TransformationAddConstantBoolean(new_result_id, value, false)); } break; case SpvOpSpecConstant: case SpvOpConstant: { @@ -459,7 +470,7 @@ void FuzzerPassDonateModules::HandleTypeOrValue( }); ApplyTransformation(TransformationAddConstantScalar( new_result_id, original_id_to_donated_id->at(type_or_value.type_id()), - data_words)); + data_words, false)); } break; case SpvOpSpecConstantComposite: case SpvOpConstantComposite: { @@ -482,7 +493,7 @@ void FuzzerPassDonateModules::HandleTypeOrValue( }); ApplyTransformation(TransformationAddConstantComposite( new_result_id, original_id_to_donated_id->at(type_or_value.type_id()), - constituent_ids)); + constituent_ids, false)); } break; case SpvOpConstantNull: { if (!original_id_to_donated_id->count(type_or_value.type_id())) { @@ -544,7 +555,8 @@ void FuzzerPassDonateModules::HandleTypeOrValue( ? 0 : FindOrCreateZeroConstant( fuzzerutil::GetPointeeTypeIdFromPointerType( - GetIRContext(), remapped_pointer_type)); + GetIRContext(), remapped_pointer_type), + false); } else { // The variable already had an initializer; use its remapped id. initializer_id = original_id_to_donated_id->at( @@ -586,7 +598,7 @@ void FuzzerPassDonateModules::HandleFunctions( // Get the ids of functions in the donor module, topologically sorted // according to the donor's call graph. auto topological_order = - GetFunctionsInCallGraphTopologicalOrder(donor_ir_context); + CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder(); // Donate the functions in reverse topological order. This ensures that a // function gets donated before any function that depends on it. This allows @@ -644,12 +656,13 @@ void FuzzerPassDonateModules::HandleFunctions( } }); - if (make_livesafe) { - // Make the function livesafe and then add it. - AddLivesafeFunction(*function_to_donate, donor_ir_context, - *original_id_to_donated_id, donated_instructions); - } else { - // Add the function in a non-livesafe manner. + // If |make_livesafe| is true, try to add the function in a livesafe manner. + // Otherwise (if |make_lifesafe| is false or an attempt to make the function + // livesafe has failed), add the function in a non-livesafe manner. + if (!make_livesafe || + !MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context, + *original_id_to_donated_id, + donated_instructions)) { ApplyTransformation(TransformationAddFunction(donated_instructions)); } } @@ -771,6 +784,7 @@ bool FuzzerPassDonateModules::IsBasicType( const opt::Instruction& instruction) const { switch (instruction.opcode()) { case SpvOpTypeArray: + case SpvOpTypeBool: case SpvOpTypeFloat: case SpvOpTypeInt: case SpvOpTypeMatrix: @@ -782,52 +796,6 @@ bool FuzzerPassDonateModules::IsBasicType( } } -std::vector<uint32_t> -FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder( - opt::IRContext* context) { - CallGraph call_graph(context); - - // This is an implementation of Kahn’s algorithm for topological sorting. - - // This is the sorted order of function ids that we will eventually return. - std::vector<uint32_t> result; - - // Get a copy of the initial in-degrees of all functions. The algorithm - // involves decrementing these values, hence why we work on a copy. - std::map<uint32_t, uint32_t> function_in_degree = - call_graph.GetFunctionInDegree(); - - // Populate a queue with all those function ids with in-degree zero. - std::queue<uint32_t> queue; - for (auto& entry : function_in_degree) { - if (entry.second == 0) { - queue.push(entry.first); - } - } - - // Pop ids from the queue, adding them to the sorted order and decreasing the - // in-degrees of their successors. A successor who's in-degree becomes zero - // gets added to the queue. - while (!queue.empty()) { - auto next = queue.front(); - queue.pop(); - result.push_back(next); - for (auto successor : call_graph.GetDirectCallees(next)) { - assert(function_in_degree.at(successor) > 0 && - "The in-degree cannot be zero if the function is a successor."); - function_in_degree[successor] = function_in_degree.at(successor) - 1; - if (function_in_degree.at(successor) == 0) { - queue.push(successor); - } - } - } - - assert(result.size() == function_in_degree.size() && - "Every function should appear in the sort."); - - return result; -} - void FuzzerPassDonateModules::HandleOpArrayLength( const opt::Instruction& instruction, std::map<uint32_t, uint32_t>* original_id_to_donated_id, @@ -979,9 +947,11 @@ void FuzzerPassDonateModules::PrepareInstructionForDonation( // This is an uninitialized local variable. Initialize it to zero. input_operands.push_back( {SPV_OPERAND_TYPE_ID, - {FindOrCreateZeroConstant(fuzzerutil::GetPointeeTypeIdFromPointerType( - GetIRContext(), - original_id_to_donated_id->at(instruction.type_id())))}}); + {FindOrCreateZeroConstant( + fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), + original_id_to_donated_id->at(instruction.type_id())), + false)}}); } if (instruction.result_id() && @@ -1003,7 +973,96 @@ void FuzzerPassDonateModules::PrepareInstructionForDonation( input_operands)); } -void FuzzerPassDonateModules::AddLivesafeFunction( +bool FuzzerPassDonateModules::CreateLoopLimiterInfo( + opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header, + const std::map<uint32_t, uint32_t>& original_id_to_donated_id, + protobufs::LoopLimiterInfo* out) { + assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header"); + + // Grab the loop header's id, mapped to its donated value. + out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id())); + + // Get fresh ids that will be used to load the loop limiter, increment + // it, compare it with the loop limit, and an id for a new block that + // will contain the loop's original terminator. + out->set_load_id(GetFuzzerContext()->GetFreshId()); + out->set_increment_id(GetFuzzerContext()->GetFreshId()); + out->set_compare_id(GetFuzzerContext()->GetFreshId()); + out->set_logical_op_id(GetFuzzerContext()->GetFreshId()); + + // We are creating a branch from the back-edge block to the merge block. Thus, + // if merge block has any OpPhi instructions, we might need to adjust + // them. + + // Note that the loop might have an unreachable back-edge block. This means + // that the loop can't iterate, so we don't need to adjust anything. + const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId( + donor_ir_context, loop_header.id()); + if (!back_edge_block_id) { + return true; + } + + auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id); + assert(back_edge_block && "|back_edge_block_id| is invalid"); + + const auto* merge_block = + donor_ir_context->cfg()->block(loop_header.MergeBlockId()); + assert(merge_block && "Loop header has invalid merge block id"); + + // We don't need to adjust anything if there is already a branch from + // the back-edge block to the merge block. + if (back_edge_block->IsSuccessor(merge_block)) { + return true; + } + + // Adjust OpPhi instructions in the |merge_block|. + for (const auto& inst : *merge_block) { + if (inst.opcode() != SpvOpPhi) { + break; + } + + // There is no simple way to ensure that a chosen operand for the OpPhi + // instruction will never cause any problems (e.g. if we choose an + // integer id, it might have a zero value when we branch from the back + // edge block. This might cause a division by 0 later in the function.). + // Thus, we ignore possible problems and proceed as follows: + // - if any of the existing OpPhi operands dominates the back-edge + // block - use it + // - if OpPhi has a basic type (see IsBasicType method) - create + // a zero constant + // - otherwise, we can't add a livesafe function. + uint32_t suitable_operand_id = 0; + for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) { + auto dependency_inst_id = inst.GetSingleWordInOperand(i); + + if (fuzzerutil::IdIsAvailableBeforeInstruction( + donor_ir_context, back_edge_block->terminator(), + dependency_inst_id)) { + suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id); + break; + } + } + + if (suitable_operand_id == 0 && + IsBasicType( + *donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) { + // We mark this constant as irrelevant so that we can replace it + // with more interesting value later. + suitable_operand_id = FindOrCreateZeroConstant( + original_id_to_donated_id.at(inst.type_id()), true); + } + + if (suitable_operand_id == 0) { + return false; + } + + out->add_phi_id(suitable_operand_id); + } + + return true; +} + +bool FuzzerPassDonateModules::MaybeAddLivesafeFunction( const opt::Function& function_to_donate, opt::IRContext* donor_ir_context, const std::map<uint32_t, uint32_t>& original_id_to_donated_id, const std::vector<protobufs::Instruction>& donated_instructions) { @@ -1012,9 +1071,9 @@ void FuzzerPassDonateModules::AddLivesafeFunction( FindOrCreateBoolType(); // Needed for comparisons FindOrCreatePointerToIntegerType( 32, false, SpvStorageClassFunction); // Needed for adding loop limiters - FindOrCreateIntegerConstant({0}, 32, + FindOrCreateIntegerConstant({0}, 32, false, false); // Needed for initializing loop limiters - FindOrCreateIntegerConstant({1}, 32, + FindOrCreateIntegerConstant({1}, 32, false, false); // Needed for incrementing loop limiters // Get a fresh id for the variable that will be used as a loop limiter. @@ -1022,7 +1081,7 @@ void FuzzerPassDonateModules::AddLivesafeFunction( // Choose a random loop limit, and add the required constant to the // module if not already there. const uint32_t loop_limit = FindOrCreateIntegerConstant( - {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false); + {GetFuzzerContext()->GetRandomLoopLimit()}, 32, false, false); // Consider every loop header in the function to donate, and create a // structure capturing the ids to be used for manipulating the loop @@ -1031,16 +1090,13 @@ void FuzzerPassDonateModules::AddLivesafeFunction( for (auto& block : function_to_donate) { if (block.IsLoopHeader()) { protobufs::LoopLimiterInfo loop_limiter; - // Grab the loop header's id, mapped to its donated value. - loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id())); - // Get fresh ids that will be used to load the loop limiter, increment - // it, compare it with the loop limit, and an id for a new block that - // will contain the loop's original terminator. - loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId()); - loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId()); - loop_limiters.emplace_back(loop_limiter); + + if (!CreateLoopLimiterInfo(donor_ir_context, block, + original_id_to_donated_id, &loop_limiter)) { + return false; + } + + loop_limiters.emplace_back(std::move(loop_limiter)); } } @@ -1096,11 +1152,11 @@ void FuzzerPassDonateModules::AddLivesafeFunction( "A runtime array type in the donor should have been " "replaced by a fixed-sized array in the recipient."); // The size of this fixed-size array is a suitable bound. - bound = TransformationAddFunction::GetBoundForCompositeIndex( - GetIRContext(), *fixed_size_array_type); + bound = fuzzerutil::GetBoundForCompositeIndex( + *fixed_size_array_type, GetIRContext()); } else { - bound = TransformationAddFunction::GetBoundForCompositeIndex( - donor_ir_context, *should_be_composite_type); + bound = fuzzerutil::GetBoundForCompositeIndex( + *should_be_composite_type, donor_ir_context); } const uint32_t index_id = inst.GetSingleWordInOperand(index); auto index_inst = @@ -1117,7 +1173,7 @@ void FuzzerPassDonateModules::AddLivesafeFunction( // whose value is one less than the bound, to compare // against and to use as the clamped value. FindOrCreateIntegerConstant({bound - 1}, 32, - index_int_type->IsSigned()); + index_int_type->IsSigned(), false); } should_be_composite_type = TransformationAddFunction::FollowCompositeIndex( @@ -1132,26 +1188,24 @@ void FuzzerPassDonateModules::AddLivesafeFunction( } } - // If the function contains OpKill or OpUnreachable instructions, and has - // non-void return type, then we need a value %v to use in order to turn - // these into instructions of the form OpReturn %v. - uint32_t kill_unreachable_return_value_id; + // If |function_to_donate| has non-void return type and contains an + // OpKill/OpUnreachable instruction, then a value is needed in order to turn + // these into instructions of the form OpReturnValue %value_id. + uint32_t kill_unreachable_return_value_id = 0; auto function_return_type_inst = donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id()); - if (function_return_type_inst->opcode() == SpvOpTypeVoid) { - // The return type is void, so we don't need a return value. - kill_unreachable_return_value_id = 0; - } else { - // We do need a return value; we use zero. - assert(function_return_type_inst->opcode() != SpvOpTypePointer && - "Function return type must not be a pointer."); + if (function_return_type_inst->opcode() != SpvOpTypeVoid && + fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) { kill_unreachable_return_value_id = FindOrCreateZeroConstant( - original_id_to_donated_id.at(function_return_type_inst->result_id())); + original_id_to_donated_id.at(function_return_type_inst->result_id()), + false); } + // Add the function in a livesafe manner. ApplyTransformation(TransformationAddFunction( donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters, kill_unreachable_return_value_id, access_chain_clamping_info)); + return true; } } // namespace fuzz diff --git a/source/fuzz/fuzzer_pass_donate_modules.h b/source/fuzz/fuzzer_pass_donate_modules.h index c59ad71d..0424cece 100644 --- a/source/fuzz/fuzzer_pass_donate_modules.h +++ b/source/fuzz/fuzzer_pass_donate_modules.h @@ -128,14 +128,24 @@ class FuzzerPassDonateModules : public FuzzerPass { std::map<uint32_t, uint32_t>* original_id_to_donated_id, std::vector<protobufs::Instruction>* donated_instructions); + // Tries to create a protobufs::LoopLimiterInfo given a loop header basic + // block. Returns true if successful and outputs loop limiter into the |out| + // variable. Otherwise, returns false. |out| contains an undefined value when + // this function returns false. + bool CreateLoopLimiterInfo( + opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header, + const std::map<uint32_t, uint32_t>& original_id_to_donated_id, + protobufs::LoopLimiterInfo* out); + // Requires that |donated_instructions| represents a prepared version of the // instructions of |function_to_donate| (which comes from |donor_ir_context|) // ready for donation, and |original_id_to_donated_id| maps ids from // |donor_ir_context| to their corresponding ids in the recipient module. // - // Adds a livesafe version of the function, based on |donated_instructions|, - // to the recipient module. - void AddLivesafeFunction( + // Attempts to add a livesafe version of the function, based on + // |donated_instructions|, to the recipient module. Returns true if the + // donation was successful, false otherwise. + bool MaybeAddLivesafeFunction( const opt::Function& function_to_donate, opt::IRContext* donor_ir_context, const std::map<uint32_t, uint32_t>& original_id_to_donated_id, const std::vector<protobufs::Instruction>& donated_instructions); @@ -144,12 +154,6 @@ class FuzzerPassDonateModules : public FuzzerPass { // array or struct; i.e. it is not an opaque type. bool IsBasicType(const opt::Instruction& instruction) const; - // Returns the ids of all functions in |context| in a topological order in - // relation to the call graph of |context|, which is assumed to be recursion- - // free. - static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder( - opt::IRContext* context); - // Functions that supply SPIR-V modules std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_; }; diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp new file mode 100644 index 00000000..59ab1aa5 --- /dev/null +++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp @@ -0,0 +1,134 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_duplicate_region_with_selection.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassDuplicateRegionsWithSelections:: + FuzzerPassDuplicateRegionsWithSelections( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassDuplicateRegionsWithSelections:: + ~FuzzerPassDuplicateRegionsWithSelections() = default; + +void FuzzerPassDuplicateRegionsWithSelections::Apply() { + // Iterate over all of the functions in the module. + for (auto& function : *GetIRContext()->module()) { + // Randomly decide whether to apply the transformation. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfDuplicatingRegionWithSelection())) { + continue; + } + std::vector<opt::BasicBlock*> candidate_entry_blocks; + for (auto& block : function) { + // We don't consider the first block to be the entry block, since it + // could contain OpVariable instructions that would require additional + // operations to be reassigned. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3778): + // Consider extending this fuzzer pass to allow the first block to be + // used in duplication. + if (&block == &*function.begin()) { + continue; + } + candidate_entry_blocks.push_back(&block); + } + if (candidate_entry_blocks.empty()) { + continue; + } + // Randomly choose the entry block. + auto entry_block = candidate_entry_blocks[GetFuzzerContext()->RandomIndex( + candidate_entry_blocks)]; + auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function); + auto postdominator_analysis = + GetIRContext()->GetPostDominatorAnalysis(&function); + std::vector<opt::BasicBlock*> candidate_exit_blocks; + for (auto postdominates_entry_block = entry_block; + postdominates_entry_block != nullptr; + postdominates_entry_block = postdominator_analysis->ImmediateDominator( + postdominates_entry_block)) { + // The candidate exit block must be dominated by the entry block and the + // entry block must be post-dominated by the candidate exit block. Ignore + // the block if it heads a selection construct or a loop construct. + if (dominator_analysis->Dominates(entry_block, + postdominates_entry_block) && + !postdominates_entry_block->GetLoopMergeInst()) { + candidate_exit_blocks.push_back(postdominates_entry_block); + } + } + if (candidate_exit_blocks.empty()) { + continue; + } + // Randomly choose the exit block. + auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex( + candidate_exit_blocks)]; + + auto region_blocks = + TransformationDuplicateRegionWithSelection::GetRegionBlocks( + GetIRContext(), entry_block, exit_block); + + // Construct |original_label_to_duplicate_label| by iterating over all + // blocks in the region. Construct |original_id_to_duplicate_id| and + // |original_id_to_phi_id| by iterating over all instructions in each block. + std::map<uint32_t, uint32_t> original_label_to_duplicate_label; + std::map<uint32_t, uint32_t> original_id_to_duplicate_id; + std::map<uint32_t, uint32_t> original_id_to_phi_id; + for (auto& block : region_blocks) { + original_label_to_duplicate_label[block->id()] = + GetFuzzerContext()->GetFreshId(); + for (auto& instr : *block) { + if (instr.result_id()) { + original_id_to_duplicate_id[instr.result_id()] = + GetFuzzerContext()->GetFreshId(); + auto final_instruction = &*exit_block->tail(); + // &*exit_block->tail() is the final instruction of the region. + // The instruction is available at the end of the region if and only + // if it is available before this final instruction or it is the final + // instruction. + if ((&instr == final_instruction || + fuzzerutil::IdIsAvailableBeforeInstruction( + GetIRContext(), final_instruction, instr.result_id()))) { + original_id_to_phi_id[instr.result_id()] = + GetFuzzerContext()->GetFreshId(); + } + } + } + } + // Randomly decide between value "true" or "false" for a bool constant. + // Make sure the transformation has access to a bool constant to be used + // while creating conditional construct. + auto condition_id = + FindOrCreateBoolConstant(GetFuzzerContext()->ChooseEven(), true); + + TransformationDuplicateRegionWithSelection transformation = + TransformationDuplicateRegionWithSelection( + GetFuzzerContext()->GetFreshId(), condition_id, + GetFuzzerContext()->GetFreshId(), entry_block->id(), + exit_block->id(), std::move(original_label_to_duplicate_label), + std::move(original_id_to_duplicate_id), + std::move(original_id_to_phi_id)); + MaybeApplyTransformation(transformation); + } +} +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h new file mode 100644 index 00000000..3fae6983 --- /dev/null +++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h @@ -0,0 +1,42 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that iterates over the whole module. For each function it +// finds a single-entry, single-exit region with its constructs and their merge +// blocks either completely within or completely outside the region. It +// duplicates this region using the corresponding transformation. +class FuzzerPassDuplicateRegionsWithSelections : public FuzzerPass { + public: + FuzzerPassDuplicateRegionsWithSelections( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassDuplicateRegionsWithSelections() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_ diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp new file mode 100644 index 00000000..132d50a4 --- /dev/null +++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp @@ -0,0 +1,124 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" + +#include "source/fuzz/comparator_deep_blocks_first.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_flatten_conditional_branch.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that randomly selects conditional branches to flatten and +// flattens them, if possible. +FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassFlattenConditionalBranches::~FuzzerPassFlattenConditionalBranches() = + default; + +void FuzzerPassFlattenConditionalBranches::Apply() { + // Get all the selection headers that we want to flatten. We need to collect + // all of them first, because, since we are changing the structure of the + // module, it's not safe to modify them while iterating. + std::vector<opt::BasicBlock*> selection_headers; + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Randomly decide whether to consider this block. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfFlatteningConditionalBranch())) { + continue; + } + + // Only consider this block if it is the header of a conditional. + if (block.GetMergeInst() && + block.GetMergeInst()->opcode() == SpvOpSelectionMerge && + block.terminator()->opcode() == SpvOpBranchConditional) { + selection_headers.emplace_back(&block); + } + } + } + + // Sort the headers so that those that are more deeply nested are considered + // first, possibly enabling outer conditionals to be flattened. + std::sort(selection_headers.begin(), selection_headers.end(), + ComparatorDeepBlocksFirst(GetIRContext())); + + // Apply the transformation to the headers which can be flattened. + for (auto header : selection_headers) { + // Make a set to keep track of the instructions that need fresh ids. + std::set<opt::Instruction*> instructions_that_need_ids; + + // Do not consider this header if the conditional cannot be flattened. + if (!TransformationFlattenConditionalBranch:: + GetProblematicInstructionsIfConditionalCanBeFlattened( + GetIRContext(), header, &instructions_that_need_ids)) { + continue; + } + + // Some instructions will require to be enclosed inside conditionals because + // they have side effects (for example, loads and stores). Some of this have + // no result id, so we require instruction descriptors to identify them. + // Each of them is associated with the necessary ids for it via a + // SideEffectWrapperInfo message. + std::vector<protobufs::SideEffectWrapperInfo> wrappers_info; + + for (auto instruction : instructions_that_need_ids) { + protobufs::SideEffectWrapperInfo wrapper_info; + *wrapper_info.mutable_instruction() = + MakeInstructionDescriptor(GetIRContext(), instruction); + wrapper_info.set_merge_block_id(GetFuzzerContext()->GetFreshId()); + wrapper_info.set_execute_block_id(GetFuzzerContext()->GetFreshId()); + + // If the instruction has a non-void result id, we need to define more + // fresh ids and provide an id of the suitable type whose value can be + // copied in order to create a placeholder id. + if (TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder( + GetIRContext(), *instruction)) { + wrapper_info.set_actual_result_id(GetFuzzerContext()->GetFreshId()); + wrapper_info.set_alternative_block_id(GetFuzzerContext()->GetFreshId()); + wrapper_info.set_placeholder_result_id( + GetFuzzerContext()->GetFreshId()); + + // The id will be a zero constant if the type allows it, and an OpUndef + // otherwise. We want to avoid using OpUndef, if possible, to avoid + // undefined behaviour in the module as much as possible. + if (fuzzerutil::CanCreateConstant( + *GetIRContext()->get_type_mgr()->GetType( + instruction->type_id()))) { + wrapper_info.set_value_to_copy_id( + FindOrCreateZeroConstant(instruction->type_id(), true)); + } else { + wrapper_info.set_value_to_copy_id( + FindOrCreateGlobalUndef(instruction->type_id())); + } + } + + wrappers_info.emplace_back(wrapper_info); + } + + // Apply the transformation, evenly choosing whether to lay out the true + // branch or the false branch first. + ApplyTransformation(TransformationFlattenConditionalBranch( + header->id(), GetFuzzerContext()->ChooseEven(), wrappers_info)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.h b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h new file mode 100644 index 00000000..715385ae --- /dev/null +++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h @@ -0,0 +1,36 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_FLATTEN_CONDITIONAL_BRANCHES_H +#define SOURCE_FUZZ_FUZZER_PASS_FLATTEN_CONDITIONAL_BRANCHES_H + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +class FuzzerPassFlattenConditionalBranches : public FuzzerPass { + public: + FuzzerPassFlattenConditionalBranches( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassFlattenConditionalBranches() override; + + void Apply() override; +}; +} // namespace fuzz +} // namespace spvtools +#endif // SOURCE_FUZZ_FUZZER_PASS_FLATTEN_CONDITIONAL_BRANCHES_H diff --git a/source/fuzz/fuzzer_pass_inline_functions.cpp b/source/fuzz/fuzzer_pass_inline_functions.cpp new file mode 100644 index 00000000..90160d83 --- /dev/null +++ b/source/fuzz/fuzzer_pass_inline_functions.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_inline_functions.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_inline_function.h" +#include "source/fuzz/transformation_split_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassInlineFunctions::FuzzerPassInlineFunctions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default; + +void FuzzerPassInlineFunctions::Apply() { + // |function_call_instructions| are the instructions that will be inlined. + // First, they will be collected and then do the inlining in another loop. + // This avoids changing the module while it is being inspected. + std::vector<opt::Instruction*> function_call_instructions; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfInliningFunction())) { + continue; + } + + // |instruction| must be suitable for inlining. + if (!TransformationInlineFunction::IsSuitableForInlining( + GetIRContext(), &instruction)) { + continue; + } + + function_call_instructions.push_back(&instruction); + } + } + } + + // Once the function calls have been collected, it's time to actually create + // and apply the inlining transformations. + for (auto& function_call_instruction : function_call_instructions) { + // If |function_call_instruction| is not the penultimate instruction in its + // block or its block termination instruction is not OpBranch, then try to + // split |function_call_block| such that the conditions are met. + auto* function_call_block = + GetIRContext()->get_instr_block(function_call_instruction); + if ((function_call_instruction != &*--function_call_block->tail() || + function_call_block->terminator()->opcode() != SpvOpBranch) && + !MaybeApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), + function_call_instruction->NextNode()), + GetFuzzerContext()->GetFreshId()))) { + continue; + } + + auto* called_function = fuzzerutil::FindFunction( + GetIRContext(), function_call_instruction->GetSingleWordInOperand(0)); + + // Mapping the called function instructions. + std::map<uint32_t, uint32_t> result_id_map; + for (auto& called_function_block : *called_function) { + // The called function entry block label will not be inlined. + if (&called_function_block != &*called_function->entry()) { + result_id_map[called_function_block.GetLabelInst()->result_id()] = + GetFuzzerContext()->GetFreshId(); + } + + for (auto& instruction_to_inline : called_function_block) { + // The instructions are mapped to fresh ids. + if (instruction_to_inline.HasResultId()) { + result_id_map[instruction_to_inline.result_id()] = + GetFuzzerContext()->GetFreshId(); + } + } + } + + // Applies the inline function transformation. + ApplyTransformation(TransformationInlineFunction( + function_call_instruction->result_id(), result_id_map)); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_inline_functions.h b/source/fuzz/fuzzer_pass_inline_functions.h new file mode 100644 index 00000000..37295d1c --- /dev/null +++ b/source/fuzz/fuzzer_pass_inline_functions.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Looks for OpFunctionCall instructions and randomly decides which ones to +// inline. If the instructions of the called function are going to be inlined, +// then a mapping, between their result ids and suitable ids, is done. +class FuzzerPassInlineFunctions : public FuzzerPass { + public: + FuzzerPassInlineFunctions(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassInlineFunctions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_ diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp new file mode 100644 index 00000000..0e40b496 --- /dev/null +++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2020 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 "fuzzer_pass_interchange_signedness_of_integer_operands.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/id_use_descriptor.h" +#include "source/fuzz/transformation_record_synonymous_constants.h" +#include "source/fuzz/transformation_replace_id_with_synonym.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassInterchangeSignednessOfIntegerOperands:: + FuzzerPassInterchangeSignednessOfIntegerOperands( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassInterchangeSignednessOfIntegerOperands:: + ~FuzzerPassInterchangeSignednessOfIntegerOperands() = default; + +void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() { + // Make vector keeping track of all the uses we want to replace. + // This is a vector of pairs, where the first element is an id use descriptor + // identifying the use of a constant id and the second is the id that should + // be used to replace it. + std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace; + + for (auto constant : GetIRContext()->GetConstants()) { + uint32_t constant_id = constant->result_id(); + + // We want to record the synonymity of an integer constant with another + // constant with opposite signedness, and this can only be done if they are + // not irrelevant. + if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + constant_id)) { + continue; + } + + uint32_t toggled_id = + FindOrCreateToggledIntegerConstant(constant->result_id()); + if (!toggled_id) { + // Not an integer constant + continue; + } + + assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant( + toggled_id) && + "FindOrCreateToggledConstant can't produce an irrelevant id"); + + // Record synonymous constants + ApplyTransformation( + TransformationRecordSynonymousConstants(constant_id, toggled_id)); + + // Find all the uses of the constant and, for each, probabilistically + // decide whether to replace it. + GetIRContext()->get_def_use_mgr()->ForEachUse( + constant_id, + [this, toggled_id, &uses_to_replace](opt::Instruction* use_inst, + uint32_t use_index) -> void { + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfInterchangingSignednessOfIntegerOperands())) { + MaybeAddUseToReplace(use_inst, use_index, toggled_id, + &uses_to_replace); + } + }); + } + + // Replace the ids if it is allowed. + for (auto use_to_replace : uses_to_replace) { + MaybeApplyTransformation(TransformationReplaceIdWithSynonym( + use_to_replace.first, use_to_replace.second)); + } +} + +uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands:: + FindOrCreateToggledIntegerConstant(uint32_t id) { + // |id| must not be a specialization constant because we do not know the value + // of specialization constants. + if (opt::IsSpecConstantInst( + GetIRContext()->get_def_use_mgr()->GetDef(id)->opcode())) { + return 0; + } + + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id); + + // This pass only toggles integer constants. + if (!constant->AsIntConstant() && + (!constant->AsVectorConstant() || + !constant->AsVectorConstant()->component_type()->AsInteger())) { + return 0; + } + + if (auto integer = constant->AsIntConstant()) { + auto type = integer->type()->AsInteger(); + + // Find or create and return the toggled constant. + return FindOrCreateIntegerConstant(std::vector<uint32_t>(integer->words()), + type->width(), !type->IsSigned(), false); + } + + // The constant is an integer vector. + + // Find the component type. + auto component_type = + constant->AsVectorConstant()->component_type()->AsInteger(); + + // Find or create the toggled component type. + uint32_t toggled_component_type = FindOrCreateIntegerType( + component_type->width(), !component_type->IsSigned()); + + // Get the information about the toggled components. We need to extract this + // information now because the analyses might be invalidated, which would make + // the constant and component_type variables invalid. + std::vector<std::vector<uint32_t>> component_words; + + for (auto component : constant->AsVectorConstant()->GetComponents()) { + component_words.push_back(component->AsIntConstant()->words()); + } + uint32_t width = component_type->width(); + bool is_signed = !component_type->IsSigned(); + + std::vector<uint32_t> toggled_components; + + // Find or create the toggled components. + for (auto words : component_words) { + toggled_components.push_back( + FindOrCreateIntegerConstant(words, width, is_signed, false)); + } + + // Find or create the required toggled vector type. + uint32_t toggled_type = FindOrCreateVectorType( + toggled_component_type, (uint32_t)toggled_components.size()); + + // Find or create and return the toggled vector constant. + return FindOrCreateCompositeConstant(toggled_components, toggled_type, false); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h new file mode 100644 index 00000000..06882f47 --- /dev/null +++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h @@ -0,0 +1,51 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A pass that: +// - Finds all the integer constant (scalar and vector) definitions in the +// module and adds the definitions of the integer with the same data words but +// opposite signedness. If the synonym is already in the module, it does not +// add a new one. +// - For each use of an integer constant where its signedness does not matter, +// decides whether to change it to the id of the toggled constant. +class FuzzerPassInterchangeSignednessOfIntegerOperands : public FuzzerPass { + public: + FuzzerPassInterchangeSignednessOfIntegerOperands( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassInterchangeSignednessOfIntegerOperands() override; + + void Apply() override; + + private: + // Given the id of an integer constant (scalar or vector), it finds or creates + // the corresponding toggled constant (the integer with the same data words + // but opposite signedness). Returns the id of the toggled instruction if the + // constant is an integer scalar or vector, 0 otherwise. + uint32_t FindOrCreateToggledIntegerConstant(uint32_t id); +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_ diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp index f44416ae..20575e11 100644 --- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp +++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp @@ -34,6 +34,12 @@ FuzzerPassInterchangeZeroLikeConstants:: uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant( opt::Instruction* declaration) { + // |declaration| must not be a specialization constant because we do not know + // the value of specialization constants. + if (opt::IsSpecConstantInst(declaration->opcode())) { + return 0; + } + auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant( declaration->result_id()); @@ -50,31 +56,13 @@ uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant( if (kind == opt::analysis::Type::kBool || kind == opt::analysis::Type::kInteger || kind == opt::analysis::Type::kFloat) { - return FindOrCreateZeroConstant(declaration->type_id()); + return FindOrCreateZeroConstant(declaration->type_id(), false); } } return 0; } -void FuzzerPassInterchangeZeroLikeConstants::MaybeAddUseToReplace( - opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, - std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>* - uses_to_replace) { - // Only consider this use if it is in a block - if (!GetIRContext()->get_instr_block(use_inst)) { - return; - } - - // Get the index of the operand restricted to input operands. - uint32_t in_operand_index = - fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); - auto id_use_descriptor = - MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index); - uses_to_replace->emplace_back( - std::make_pair(id_use_descriptor, replacement_id)); -} - void FuzzerPassInterchangeZeroLikeConstants::Apply() { // Make vector keeping track of all the uses we want to replace. // This is a vector of pairs, where the first element is an id use descriptor @@ -118,11 +106,11 @@ void FuzzerPassInterchangeZeroLikeConstants::Apply() { }); } - // Replace the ids + // Replace the ids if it is allowed. for (auto use_to_replace : uses_to_replace) { MaybeApplyTransformation(TransformationReplaceIdWithSynonym( use_to_replace.first, use_to_replace.second)); } } } // namespace fuzz -} // namespace spvtools
\ No newline at end of file +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h index 4fcc44e0..ef0f7655 100644 --- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h +++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h @@ -13,8 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_ -#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -46,18 +46,8 @@ class FuzzerPassInterchangeZeroLikeConstants : public FuzzerPass { // Returns the id of the toggled instruction if the constant is zero-like, // 0 otherwise. uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration); - - // Given an id use (described by an instruction and an index) and an id with - // which the original one should be replaced, adds a pair (with the elements - // being the corresponding id use descriptor and the replacement id) to - // |uses_to_replace| if the use is in an instruction block, otherwise does - // nothing. - void MaybeAddUseToReplace( - opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id, - std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>* - uses_to_replace); }; } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_ diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp new file mode 100644 index 00000000..f4f2a802 --- /dev/null +++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassMakeVectorOperationsDynamic:: + ~FuzzerPassMakeVectorOperationsDynamic() = default; + +void FuzzerPassMakeVectorOperationsDynamic::Apply() { + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + // Randomly decide whether to try applying the transformation. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfMakingVectorOperationDynamic())) { + continue; + } + + // |instruction| must be a vector operation. + if (!TransformationMakeVectorOperationDynamic::IsVectorOperation( + GetIRContext(), &instruction)) { + continue; + } + + // Make sure |instruction| has only one indexing operand. + assert(instruction.NumInOperands() == + (instruction.opcode() == SpvOpCompositeExtract ? 2 : 3) && + "FuzzerPassMakeVectorOperationsDynamic: the composite " + "instruction must have " + "only one indexing operand."); + + // Applies the make vector operation dynamic transformation. + ApplyTransformation(TransformationMakeVectorOperationDynamic( + instruction.result_id(), + FindOrCreateIntegerConstant( + {instruction.GetSingleWordInOperand( + instruction.opcode() == SpvOpCompositeExtract ? 1 : 2)}, + 32, GetFuzzerContext()->ChooseEven(), false))); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h new file mode 100644 index 00000000..dd51cde7 --- /dev/null +++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ +#define SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Looks for OpCompositeExtract/Insert instructions on vectors, and replaces +// them with OpVectorExtract/InsertDynamic. +class FuzzerPassMakeVectorOperationsDynamic : public FuzzerPass { + public: + FuzzerPassMakeVectorOperationsDynamic( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassMakeVectorOperationsDynamic() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_ diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.cpp b/source/fuzz/fuzzer_pass_mutate_pointers.cpp new file mode 100644 index 00000000..89f5f5c0 --- /dev/null +++ b/source/fuzz/fuzzer_pass_mutate_pointers.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_mutate_pointers.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_mutate_pointer.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassMutatePointers::FuzzerPassMutatePointers( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassMutatePointers::~FuzzerPassMutatePointers() = default; + +void FuzzerPassMutatePointers::Apply() { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* function, opt::BasicBlock* block, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& instruction_descriptor) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfMutatingPointer())) { + return; + } + + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) { + return; + } + + auto available_pointers = FindAvailableInstructions( + function, block, inst_it, + [](opt::IRContext* ir_context, opt::Instruction* inst) { + return TransformationMutatePointer::IsValidPointerInstruction( + ir_context, *inst); + }); + + if (available_pointers.empty()) { + return; + } + + const auto* pointer_inst = + available_pointers[GetFuzzerContext()->RandomIndex( + available_pointers)]; + + // Make sure there is an irrelevant constant in the module. + FindOrCreateZeroConstant(fuzzerutil::GetPointeeTypeIdFromPointerType( + GetIRContext(), pointer_inst->type_id()), + true); + + ApplyTransformation(TransformationMutatePointer( + pointer_inst->result_id(), GetFuzzerContext()->GetFreshId(), + instruction_descriptor)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.h b/source/fuzz/fuzzer_pass_mutate_pointers.h new file mode 100644 index 00000000..f77523ef --- /dev/null +++ b/source/fuzz/fuzzer_pass_mutate_pointers.h @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_MUTATE_POINTERS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_MUTATE_POINTERS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Randomly mutates the value of each pointer instruction in the module. +class FuzzerPassMutatePointers : public FuzzerPass { + public: + FuzzerPassMutatePointers(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassMutatePointers() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_MUTATE_POINTERS_H_ diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp index c92639a2..d87662ee 100644 --- a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp +++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp @@ -311,9 +311,9 @@ void FuzzerPassObfuscateConstants::ObfuscateBoolConstant( } while (constant_index_1 == constant_index_2); auto constant_id_1 = FindOrCreateConstant( - available_constant_words[constant_index_1], chosen_type_id); + available_constant_words[constant_index_1], chosen_type_id, false); auto constant_id_2 = FindOrCreateConstant( - available_constant_words[constant_index_2], chosen_type_id); + available_constant_words[constant_index_2], chosen_type_id, false); assert(constant_id_1 != 0 && constant_id_2 != 0 && "We should not find an available constant with an id of 0."); @@ -347,8 +347,7 @@ void FuzzerPassObfuscateConstants::ObfuscateScalarConstant( auto uniform_descriptors = GetTransformationContext() ->GetFactManager() - ->GetUniformDescriptorsForConstant(GetIRContext(), - constant_use.id_of_interest()); + ->GetUniformDescriptorsForConstant(constant_use.id_of_interest()); if (uniform_descriptors.empty()) { // No relevant uniforms, so do not obfuscate. return; @@ -361,7 +360,7 @@ void FuzzerPassObfuscateConstants::ObfuscateScalarConstant( // Make sure the module has OpConstant instructions for each index used to // access a uniform. for (auto index : uniform_descriptor.index()) { - FindOrCreateIntegerConstant({index}, 32, true); + FindOrCreateIntegerConstant({index}, 32, true, false); } // Make sure the module has OpTypePointer that points to the element type of diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h index 52d8efe5..d48b37f6 100644 --- a/source/fuzz/fuzzer_pass_obfuscate_constants.h +++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_ -#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_ #include <vector> @@ -109,4 +109,4 @@ class FuzzerPassObfuscateConstants : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_ diff --git a/source/fuzz/fuzzer_pass_outline_functions.cpp b/source/fuzz/fuzzer_pass_outline_functions.cpp index e4281d10..3bd0a9f5 100644 --- a/source/fuzz/fuzzer_pass_outline_functions.cpp +++ b/source/fuzz/fuzzer_pass_outline_functions.cpp @@ -17,7 +17,9 @@ #include <vector> #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/transformation_outline_function.h" +#include "source/fuzz/transformation_split_block.h" namespace spvtools { namespace fuzz { @@ -45,7 +47,15 @@ void FuzzerPassOutlineFunctions::Apply() { for (auto& block : *function) { blocks.push_back(&block); } - auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)]; + auto entry_block = MaybeGetEntryBlockSuitableForOutlining( + blocks[GetFuzzerContext()->RandomIndex(blocks)]); + + if (!entry_block) { + // The chosen block is not suitable to be the entry block of a region that + // will be outlined. + continue; + } + auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function); auto postdominator_analysis = GetIRContext()->GetPostDominatorAnalysis(function); @@ -54,16 +64,26 @@ void FuzzerPassOutlineFunctions::Apply() { postdominates_entry_block != nullptr; postdominates_entry_block = postdominator_analysis->ImmediateDominator( postdominates_entry_block)) { + // Consider the block if it is dominated by the entry block, ignore it if + // it is a continue target. if (dominator_analysis->Dominates(entry_block, - postdominates_entry_block)) { + postdominates_entry_block) && + !GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + postdominates_entry_block->id())) { candidate_exit_blocks.push_back(postdominates_entry_block); } } if (candidate_exit_blocks.empty()) { continue; } - auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex( - candidate_exit_blocks)]; + auto exit_block = MaybeGetExitBlockSuitableForOutlining( + candidate_exit_blocks[GetFuzzerContext()->RandomIndex( + candidate_exit_blocks)]); + + if (!exit_block) { + // The block chosen is not suitable + continue; + } auto region_blocks = TransformationOutlineFunction::GetRegionBlocks( GetIRContext(), entry_block, exit_block); @@ -93,5 +113,84 @@ void FuzzerPassOutlineFunctions::Apply() { } } +opt::BasicBlock* +FuzzerPassOutlineFunctions::MaybeGetEntryBlockSuitableForOutlining( + opt::BasicBlock* entry_block) { + // If the entry block is a loop header, we need to get or create its + // preheader and make it the entry block, if possible. + if (entry_block->IsLoopHeader()) { + auto predecessors = + GetIRContext()->cfg()->preds(entry_block->GetLabel()->result_id()); + + if (predecessors.size() < 2) { + // The header only has one predecessor (the back-edge block) and thus + // it is unreachable. The block cannot be adjusted to be suitable for + // outlining. + return nullptr; + } + + // Get or create a suitable preheader and make it become the entry block. + entry_block = + GetOrCreateSimpleLoopPreheader(entry_block->GetLabel()->result_id()); + } + + assert(!entry_block->IsLoopHeader() && + "The entry block cannot be a loop header at this point."); + + // If the entry block starts with OpPhi or OpVariable, try to split it. + if (entry_block->begin()->opcode() == SpvOpPhi || + entry_block->begin()->opcode() == SpvOpVariable) { + // Find the first non-OpPhi and non-OpVariable instruction. + auto non_phi_or_var_inst = &*entry_block->begin(); + while (non_phi_or_var_inst->opcode() == SpvOpPhi || + non_phi_or_var_inst->opcode() == SpvOpVariable) { + non_phi_or_var_inst = non_phi_or_var_inst->NextNode(); + } + + // Split the block. + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst), + new_block_id)); + + // The new entry block is the newly-created block. + entry_block = &*entry_block->GetParent()->FindBlock(new_block_id); + } + + return entry_block; +} + +opt::BasicBlock* +FuzzerPassOutlineFunctions::MaybeGetExitBlockSuitableForOutlining( + opt::BasicBlock* exit_block) { + // The exit block must not be a continue target. + assert(!GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock( + exit_block->id()) && + "A candidate exit block cannot be a continue target."); + + // If the exit block is a merge block, try to split it and return the second + // block in the pair as the exit block. + if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock( + exit_block->id())) { + uint32_t new_block_id = GetFuzzerContext()->GetFreshId(); + + // Find the first non-OpPhi instruction, after which to split. + auto split_before = &*exit_block->begin(); + while (split_before->opcode() == SpvOpPhi) { + split_before = split_before->NextNode(); + } + + if (!MaybeApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), split_before), + new_block_id))) { + return nullptr; + } + + return &*exit_block->GetParent()->FindBlock(new_block_id); + } + + return exit_block; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_outline_functions.h b/source/fuzz/fuzzer_pass_outline_functions.h index 6532ed9a..02022aa7 100644 --- a/source/fuzz/fuzzer_pass_outline_functions.h +++ b/source/fuzz/fuzzer_pass_outline_functions.h @@ -32,6 +32,31 @@ class FuzzerPassOutlineFunctions : public FuzzerPass { ~FuzzerPassOutlineFunctions(); void Apply() override; + + // Returns a block suitable to be an entry block for a region that can be + // outlined, i.e. a block that is not a loop header and that does not start + // with OpPhi or OpVariable. In particular, it returns: + // - |entry_block| if it is suitable + // - otherwise, a block found by: + // - looking for or creating a new preheader, if |entry_block| is a loop + // header + // - splitting the candidate entry block, if it starts with OpPhi or + // OpVariable. + // Returns nullptr if a suitable block cannot be found following the + // instructions above. + opt::BasicBlock* MaybeGetEntryBlockSuitableForOutlining( + opt::BasicBlock* entry_block); + + // Returns: + // - |exit_block| if it is not a merge block + // - the second block obtained by splitting |exit_block|, if |exit_block| is a + // merge block. + // Assumes that |exit_block| is not a continue target. + // The block returned by this function should be suitable to be the exit block + // of a region that can be outlined. + // Returns nullptr if |exit_block| is a merge block and it cannot be split. + opt::BasicBlock* MaybeGetExitBlockSuitableForOutlining( + opt::BasicBlock* exit_block); }; } // namespace fuzz diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h index f2d3b398..e5a672cb 100644 --- a/source/fuzz/fuzzer_pass_permute_blocks.h +++ b/source/fuzz/fuzzer_pass_permute_blocks.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_ -#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -37,4 +37,4 @@ class FuzzerPassPermuteBlocks : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_ diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp index e15aef6e..de6b03f3 100644 --- a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp +++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/fuzzer_pass_permute_function_parameters.h" + #include <numeric> #include <vector> #include "source/fuzz/fuzzer_context.h" -#include "source/fuzz/fuzzer_pass_permute_function_parameters.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/transformation_permute_function_parameters.h" diff --git a/source/fuzz/fuzzer_pass_permute_instructions.cpp b/source/fuzz/fuzzer_pass_permute_instructions.cpp new file mode 100644 index 00000000..6867053c --- /dev/null +++ b/source/fuzz/fuzzer_pass_permute_instructions.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_permute_instructions.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_move_instruction_down.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default; + +void FuzzerPassPermuteInstructions::Apply() { + // We are iterating over all instructions in all basic blocks. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // We need to collect all instructions in the block into a separate vector + // since application of the transformation below might invalidate + // iterators. + std::vector<opt::Instruction*> instructions; + for (auto& instruction : block) { + instructions.push_back(&instruction); + } + + // We consider all instructions in reverse to increase the possible number + // of applied transformations. + for (auto it = instructions.rbegin(); it != instructions.rend(); ++it) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPermutingInstructions())) { + continue; + } + + while (MaybeApplyTransformation(TransformationMoveInstructionDown( + MakeInstructionDescriptor(GetIRContext(), *it)))) { + // Apply the transformation as many times as possible. + } + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_permute_instructions.h b/source/fuzz/fuzzer_pass_permute_instructions.h new file mode 100644 index 00000000..e02ddfae --- /dev/null +++ b/source/fuzz/fuzzer_pass_permute_instructions.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Permutes instructions in every block of all while preserving the module's +// semantics. +class FuzzerPassPermuteInstructions : public FuzzerPass { + public: + FuzzerPassPermuteInstructions( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPermuteInstructions() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_ diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp index c241d9d3..c379c535 100644 --- a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp +++ b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/fuzzer_pass_permute_phi_operands.h" + #include <numeric> #include <vector> #include "source/fuzz/fuzzer_context.h" -#include "source/fuzz/fuzzer_pass_permute_phi_operands.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/transformation_permute_phi_operands.h" diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp new file mode 100644 index 00000000..2042d7c2 --- /dev/null +++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_propagate_instruction_up.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassPropagateInstructionsUp::FuzzerPassPropagateInstructionsUp( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassPropagateInstructionsUp::~FuzzerPassPropagateInstructionsUp() = + default; + +void FuzzerPassPropagateInstructionsUp::Apply() { + for (const auto& function : *GetIRContext()->module()) { + for (const auto& block : function) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfPropagatingInstructionsUp())) { + continue; + } + + if (TransformationPropagateInstructionUp::IsApplicableToBlock( + GetIRContext(), block.id())) { + std::map<uint32_t, uint32_t> fresh_ids; + for (auto id : GetIRContext()->cfg()->preds(block.id())) { + auto& fresh_id = fresh_ids[id]; + + if (!fresh_id) { + // Create a fresh id if it hasn't been created yet. |fresh_id| will + // be default-initialized to 0 in this case. + fresh_id = GetFuzzerContext()->GetFreshId(); + } + } + + ApplyTransformation( + TransformationPropagateInstructionUp(block.id(), fresh_ids)); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.h b/source/fuzz/fuzzer_pass_propagate_instructions_up.h new file mode 100644 index 00000000..d915b31e --- /dev/null +++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_ +#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Decides whether to propagate instructions from some block into its +// predecessors. +class FuzzerPassPropagateInstructionsUp : public FuzzerPass { + public: + FuzzerPassPropagateInstructionsUp( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassPropagateInstructionsUp() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_ diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp index 1391976f..8d9acaa0 100644 --- a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp +++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp @@ -91,7 +91,13 @@ void FuzzerPassPushIdsThroughVariables::Apply() { return false; } - if (!fuzzerutil::CanMakeSynonymOf(ir_context, + // If the id is irrelevant, we can use it since it will not + // participate in DataSynonym fact. Otherwise, we should be + // able to produce a synonym out of the id. + if (!GetTransformationContext() + ->GetFactManager() + ->IdIsIrrelevant(instruction->result_id()) && + !fuzzerutil::CanMakeSynonymOf(ir_context, *GetTransformationContext(), instruction)) { return false; @@ -132,7 +138,7 @@ void FuzzerPassPushIdsThroughVariables::Apply() { // Create a constant to initialize the variable from. This might update // module's id bound so it must be done before any fresh ids are // computed. - auto initializer_id = FindOrCreateZeroConstant(basic_type_id); + auto initializer_id = FindOrCreateZeroConstant(basic_type_id, false); // Applies the push id through variable transformation. ApplyTransformation(TransformationPushIdThroughVariable( diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp new file mode 100644 index 00000000..139dc6e2 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h" + +namespace spvtools { +namespace fuzz { + +namespace { +const uint32_t kArithmeticInstructionIndexLeftInOperand = 0; +} // namespace + +FuzzerPassReplaceAddsSubsMulsWithCarryingExtended:: + FuzzerPassReplaceAddsSubsMulsWithCarryingExtended( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceAddsSubsMulsWithCarryingExtended:: + ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default; + +void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() { + std::vector<opt::Instruction> instructions_for_transformation; + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + // Randomly decide whether to apply the transformation. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingAddSubMulWithCarryingExtended())) { + continue; + } + + // Check if the transformation can be applied to this instruction. + if (!TransformationReplaceAddSubMulWithCarryingExtended:: + IsInstructionSuitable(GetIRContext(), instruction)) { + continue; + } + instructions_for_transformation.push_back(instruction); + } + } + } + for (auto& instruction : instructions_for_transformation) { + // Get the operand type id. We know that both operands have the same + // type. + uint32_t operand_type_id = + GetIRContext() + ->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + // Ensure the required struct type exists. The struct type is based on + // the operand type. + FindOrCreateStructType({operand_type_id, operand_type_id}); + + ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended( + GetFuzzerContext()->GetFreshId(), instruction.result_id())); + } +} +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h new file mode 100644 index 00000000..dd39e6b0 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h @@ -0,0 +1,42 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that replaces instructions OpIAdd, OpISub, OpIMul with pairs of +// instructions. The first one (OpIAddCarry, OpISubBorrow, OpUMulExtended, +// OpSMulExtended) computes the result into a struct. The second one extracts +// the appropriate component from the struct to yield the original result. +class FuzzerPassReplaceAddsSubsMulsWithCarryingExtended : public FuzzerPass { + public: + FuzzerPassReplaceAddsSubsMulsWithCarryingExtended( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_ diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp new file mode 100644 index 00000000..68471466 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceCopyMemoriesWithLoadsStores:: + FuzzerPassReplaceCopyMemoriesWithLoadsStores( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceCopyMemoriesWithLoadsStores:: + ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() = default; + +void FuzzerPassReplaceCopyMemoriesWithLoadsStores::Apply() { + GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) { + // Randomly decide whether to replace the OpCopyMemory. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingCopyMemoryWithLoadStore())) { + return; + } + + // The instruction must be OpCopyMemory. + if (instruction->opcode() != SpvOpCopyMemory) { + return; + } + + // Apply the transformation replacing OpCopyMemory with OpLoad and OpStore. + ApplyTransformation(TransformationReplaceCopyMemoryWithLoadStore( + GetFuzzerContext()->GetFreshId(), + MakeInstructionDescriptor(GetIRContext(), instruction))); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h new file mode 100644 index 00000000..9c24ac73 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Replaces instructions OpCopyMemory with loading the source variable to +// an intermediate value and storing this value into the target variable of +// the original OpCopyMemory instruction. +class FuzzerPassReplaceCopyMemoriesWithLoadsStores : public FuzzerPass { + public: + FuzzerPassReplaceCopyMemoriesWithLoadsStores( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_ diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp new file mode 100644 index 00000000..51cb5696 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_replace_copy_object_with_store_load.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceCopyObjectsWithStoresLoads:: + FuzzerPassReplaceCopyObjectsWithStoresLoads( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceCopyObjectsWithStoresLoads:: + ~FuzzerPassReplaceCopyObjectsWithStoresLoads() = default; + +void FuzzerPassReplaceCopyObjectsWithStoresLoads::Apply() { + GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) { + // Randomly decide whether to replace OpCopyObject. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingCopyObjectWithStoreLoad())) { + return; + } + // The instruction must be OpCopyObject. + if (instruction->opcode() != SpvOpCopyObject) { + return; + } + // The opcode of the type_id instruction cannot be a OpTypePointer, + // because we cannot define a pointer to pointer. + if (GetIRContext() + ->get_def_use_mgr() + ->GetDef(instruction->type_id()) + ->opcode() == SpvOpTypePointer) { + return; + } + // It must be valid to insert OpStore and OpLoad instructions + // before the instruction OpCopyObject. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore, + instruction) || + !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, instruction)) { + return; + } + + // Randomly decides whether a global or local variable will be added. + auto variable_storage_class = GetFuzzerContext()->ChooseEven() + ? SpvStorageClassPrivate + : SpvStorageClassFunction; + + // Find or create a constant to initialize the variable from. The type of + // |instruction| must be such that the function FindOrCreateConstant can be + // called. + auto instruction_type = + GetIRContext()->get_type_mgr()->GetType(instruction->type_id()); + if (!fuzzerutil::CanCreateConstant(*instruction_type)) { + return; + } + auto variable_initializer_id = + FindOrCreateZeroConstant(instruction->type_id(), false); + + // Make sure that pointer type is defined. + FindOrCreatePointerType(instruction->type_id(), variable_storage_class); + // Apply the transformation replacing OpCopyObject with Store and Load. + ApplyTransformation(TransformationReplaceCopyObjectWithStoreLoad( + instruction->result_id(), GetFuzzerContext()->GetFreshId(), + variable_storage_class, variable_initializer_id)); + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h new file mode 100644 index 00000000..ae03a450 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Replaces instructions OpCopyObject with storing into a new variable +// and immediately loading this variable to |result_id| of the +// original OpCopyObject instruction. +class FuzzerPassReplaceCopyObjectsWithStoresLoads : public FuzzerPass { + public: + FuzzerPassReplaceCopyObjectsWithStoresLoads( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceCopyObjectsWithStoresLoads() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_ diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp new file mode 100644 index 00000000..cc92e283 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp @@ -0,0 +1,165 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/id_use_descriptor.h" +#include "source/fuzz/transformation_replace_irrelevant_id.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that, for every use of an id that has been recorded as +// irrelevant, randomly decides whether to replace it with another id of the +// same type. +FuzzerPassReplaceIrrelevantIds::FuzzerPassReplaceIrrelevantIds( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceIrrelevantIds::~FuzzerPassReplaceIrrelevantIds() = default; + +void FuzzerPassReplaceIrrelevantIds::Apply() { + // Keep track of the irrelevant ids. This includes all the ids that are + // irrelevant according to the fact manager and that are still present in the + // module (some of them may have been removed by previously-run + // transformations). + std::vector<uint32_t> irrelevant_ids; + + // Keep a map from the type ids of irrelevant ids to all the ids with that + // type. + std::unordered_map<uint32_t, std::vector<uint32_t>> types_to_ids; + + // Find all the irrelevant ids that still exist in the module and all the + // types for which irrelevant ids exist. + for (auto id : + GetTransformationContext()->GetFactManager()->GetIrrelevantIds()) { + // Check that the id still exists in the module. + auto declaration = GetIRContext()->get_def_use_mgr()->GetDef(id); + if (!declaration) { + continue; + } + + irrelevant_ids.push_back(id); + + // If the type of this id has not been seen before, add a mapping from this + // type id to an empty list in |types_to_ids|. The list will be filled later + // on. + if (types_to_ids.count(declaration->type_id()) == 0) { + types_to_ids.insert({declaration->type_id(), {}}); + } + } + + // If no irrelevant ids were found, return. + if (irrelevant_ids.empty()) { + return; + } + + // For every type for which we have at least one irrelevant id, record all ids + // in the module which have that type. + for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) { + uint32_t type_id = pair.second->type_id(); + if (type_id && types_to_ids.count(type_id)) { + types_to_ids[type_id].push_back(pair.first); + } + } + + // Keep a list of all the transformations to perform. We avoid applying the + // transformations while traversing the uses since applying the transformation + // invalidates all analyses, and we want to avoid invalidating and recomputing + // them every time. + std::vector<TransformationReplaceIrrelevantId> transformations_to_apply; + + // Loop through all the uses of irrelevant ids, check that the id can be + // replaced and randomly decide whether to apply the transformation. + for (auto irrelevant_id : irrelevant_ids) { + uint32_t type_id = + GetIRContext()->get_def_use_mgr()->GetDef(irrelevant_id)->type_id(); + + GetIRContext()->get_def_use_mgr()->ForEachUse( + irrelevant_id, [this, &irrelevant_id, &type_id, &types_to_ids, + &transformations_to_apply](opt::Instruction* use_inst, + uint32_t use_index) { + // Randomly decide whether to consider this use. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfReplacingIrrelevantId())) { + return; + } + + // The id must be used as an input operand. + if (use_index < use_inst->NumOperands() - use_inst->NumInOperands()) { + // The id is used as an output operand, so we cannot replace this + // usage. + return; + } + + // Get the input operand index for this use, from the absolute operand + // index. + uint32_t in_index = + fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index); + + // Only go ahead if this id use can be replaced in principle. + if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(), use_inst, + in_index)) { + return; + } + + // Find out which ids could be used to replace this use. + std::vector<uint32_t> available_replacement_ids; + + for (auto replacement_id : types_to_ids[type_id]) { + // We cannot replace an id with itself. + if (replacement_id == irrelevant_id) { + continue; + } + + // Only consider this replacement if it is available at the id use + // point. + if (fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst, + in_index, replacement_id)) { + available_replacement_ids.push_back(replacement_id); + } + } + + // Only go ahead if there is at least one id with which this use can + // be replaced. + if (available_replacement_ids.empty()) { + return; + } + + // Choose the replacement id randomly. + uint32_t replacement_id = + available_replacement_ids[GetFuzzerContext()->RandomIndex( + available_replacement_ids)]; + + // Add this replacement to the list of transformations to apply. + transformations_to_apply.emplace_back( + TransformationReplaceIrrelevantId( + MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, + in_index), + replacement_id)); + }); + } + + // Apply all the transformations. + for (const auto& transformation : transformations_to_apply) { + ApplyTransformation(transformation); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h new file mode 100644 index 00000000..ab3f01d1 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that, for every use of an irrelevant id, checks if it is +// possible to replace it with other ids of the same type and randomly decides +// whether to do it. +class FuzzerPassReplaceIrrelevantIds : public FuzzerPass { + public: + FuzzerPassReplaceIrrelevantIds( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceIrrelevantIds(); + + void Apply() override; +}; +} // namespace fuzz +} // namespace spvtools +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_H_ diff --git a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp index 1e5d697f..c3e65789 100644 --- a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp +++ b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp @@ -34,34 +34,29 @@ FuzzerPassReplaceLinearAlgebraInstructions:: ~FuzzerPassReplaceLinearAlgebraInstructions() = default; void FuzzerPassReplaceLinearAlgebraInstructions::Apply() { - // For each instruction, checks whether it is a supported linear algebra - // instruction. In this case, the transformation is randomly applied. - GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354): - // Right now we only support certain operations. When this issue is - // addressed the following conditional can use the function - // |spvOpcodeIsLinearAlgebra|. - if (instruction->opcode() != SpvOpVectorTimesScalar && - instruction->opcode() != SpvOpMatrixTimesScalar && - instruction->opcode() != SpvOpVectorTimesMatrix && - instruction->opcode() != SpvOpMatrixTimesVector && - instruction->opcode() != SpvOpMatrixTimesMatrix && - instruction->opcode() != SpvOpDot) { - return; + // For each instruction, checks whether it is a linear algebra instruction. In + // this case, the transformation is randomly applied. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + for (auto& instruction : block) { + if (!spvOpcodeIsLinearAlgebra(instruction.opcode())) { + continue; + } + + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingLinearAlgebraInstructions())) { + continue; + } + + ApplyTransformation(TransformationReplaceLinearAlgebraInstruction( + GetFuzzerContext()->GetFreshIds( + TransformationReplaceLinearAlgebraInstruction:: + GetRequiredFreshIdCount(GetIRContext(), &instruction)), + MakeInstructionDescriptor(GetIRContext(), &instruction))); + } } - - if (!GetFuzzerContext()->ChoosePercentage( - GetFuzzerContext() - ->GetChanceOfReplacingLinearAlgebraInstructions())) { - return; - } - - ApplyTransformation(TransformationReplaceLinearAlgebraInstruction( - GetFuzzerContext()->GetFreshIds( - TransformationReplaceLinearAlgebraInstruction:: - GetRequiredFreshIdCount(GetIRContext(), instruction)), - MakeInstructionDescriptor(GetIRContext(), instruction))); - }); + } } } // namespace fuzz diff --git a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp new file mode 100644 index 00000000..7690ac41 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp @@ -0,0 +1,105 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h" +#include "source/opt/instruction.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceLoadsStoresWithCopyMemories:: + FuzzerPassReplaceLoadsStoresWithCopyMemories( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceLoadsStoresWithCopyMemories:: + ~FuzzerPassReplaceLoadsStoresWithCopyMemories() = default; + +void FuzzerPassReplaceLoadsStoresWithCopyMemories::Apply() { + // We look for matching pairs of instructions OpLoad and + // OpStore within the same block. Potential instructions OpLoad to be matched + // are stored in a hash map. If we encounter instructions that write to memory + // or instructions of memory barriers that could operate on variables within + // unsafe storage classes we need to erase the hash map to avoid unsafe + // operations. + + // A vector of matching OpLoad and OpStore instructions. + std::vector<std::pair<opt::Instruction*, opt::Instruction*>> + op_load_store_pairs; + + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // A hash map storing potential OpLoad instructions. + std::unordered_map<uint32_t, opt::Instruction*> current_op_loads; + for (auto& instruction : block) { + // Add a potential OpLoad instruction. + if (instruction.opcode() == SpvOpLoad) { + current_op_loads[instruction.result_id()] = &instruction; + } else if (instruction.opcode() == SpvOpStore) { + if (current_op_loads.find(instruction.GetSingleWordOperand(1)) != + current_op_loads.end()) { + // We have found the matching OpLoad instruction to the current + // OpStore instruction. + op_load_store_pairs.push_back(std::make_pair( + current_op_loads[instruction.GetSingleWordOperand(1)], + &instruction)); + } + } + if (TransformationReplaceLoadStoreWithCopyMemory::IsMemoryWritingOpCode( + instruction.opcode())) { + current_op_loads.clear(); + } else if (TransformationReplaceLoadStoreWithCopyMemory:: + IsMemoryBarrierOpCode(instruction.opcode())) { + for (auto it = current_op_loads.begin(); + it != current_op_loads.end();) { + // Get the storage class. + opt::Instruction* source_id = + GetIRContext()->get_def_use_mgr()->GetDef( + it->second->GetSingleWordOperand(2)); + SpvStorageClass storage_class = + fuzzerutil::GetStorageClassFromPointerType( + GetIRContext(), source_id->type_id()); + if (!TransformationReplaceLoadStoreWithCopyMemory:: + IsStorageClassSafeAcrossMemoryBarriers(storage_class)) { + it = current_op_loads.erase(it); + } else { + it++; + } + } + } + } + } + } + for (auto instr_pair : op_load_store_pairs) { + // Randomly decide to apply the transformation for the + // potential pairs. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingLoadStoreWithCopyMemory())) { + ApplyTransformation(TransformationReplaceLoadStoreWithCopyMemory( + MakeInstructionDescriptor(GetIRContext(), instr_pair.first), + MakeInstructionDescriptor(GetIRContext(), instr_pair.second))); + } + } +} // namespace fuzz +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h new file mode 100644 index 00000000..67871db2 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h @@ -0,0 +1,41 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that takes pairs of instruction descriptors to OpLoad and +// OpStore that have the same intermediate value and in each pair replaces the +// OpStore with an equivalent OpCopyMemory. +class FuzzerPassReplaceLoadsStoresWithCopyMemories : public FuzzerPass { + public: + FuzzerPassReplaceLoadsStoresWithCopyMemories( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceLoadsStoresWithCopyMemories() override; + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_ diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp new file mode 100644 index 00000000..080ace85 --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" + +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceOpPhiIdsFromDeadPredecessors:: + FuzzerPassReplaceOpPhiIdsFromDeadPredecessors( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceOpPhiIdsFromDeadPredecessors:: + ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors() = default; + +void FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::Apply() { + // Keep a vector of the transformations to apply. + std::vector<TransformationReplaceOpPhiIdFromDeadPredecessor> transformations; + + // Loop through the blocks in the module. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Only consider dead blocks. + if (!GetTransformationContext()->GetFactManager()->BlockIsDead( + block.id())) { + continue; + } + + // Find all the uses of the label id of the block inside OpPhi + // instructions. + GetIRContext()->get_def_use_mgr()->ForEachUse( + block.id(), [this, &function, &block, &transformations]( + opt::Instruction* instruction, uint32_t) { + // Only consider OpPhi instructions. + if (instruction->opcode() != SpvOpPhi) { + return; + } + + // Randomly decide whether to consider this use. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingOpPhiIdFromDeadPredecessor())) { + return; + } + + // Get the current id corresponding to the predecessor. + uint32_t current_id = 0; + for (uint32_t i = 1; i < instruction->NumInOperands(); i += 2) { + if (instruction->GetSingleWordInOperand(i) == block.id()) { + // The corresponding id is at the index of the block - 1. + current_id = instruction->GetSingleWordInOperand(i - 1); + break; + } + } + assert(current_id != 0 && + "The predecessor - and corresponding id - should always be " + "found."); + + uint32_t type_id = instruction->type_id(); + + // Find all the suitable instructions to replace the id. + const auto& candidates = FindAvailableInstructions( + &function, &block, block.end(), + [type_id, current_id](opt::IRContext* /* unused */, + opt::Instruction* candidate) -> bool { + // Only consider instructions with a result id different from + // the currently-used one, and with the right type. + return candidate->HasResultId() && + candidate->type_id() == type_id && + candidate->result_id() != current_id; + }); + + // If there is no possible replacement, we cannot apply any + // transformation. + if (candidates.empty()) { + return; + } + + // Choose one of the candidates. + uint32_t replacement_id = + candidates[GetFuzzerContext()->RandomIndex(candidates)] + ->result_id(); + + // Add a new transformation to the list of transformations to apply. + transformations.emplace_back( + TransformationReplaceOpPhiIdFromDeadPredecessor( + instruction->result_id(), block.id(), replacement_id)); + }); + } + } + + // Apply all the transformations. + for (const auto& transformation : transformations) { + ApplyTransformation(transformation); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h new file mode 100644 index 00000000..972c5f9d --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h @@ -0,0 +1,39 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Replaces id operands in OpPhi instructions with other available ids of the +// right type, where the corresponding predecessor is dead. +class FuzzerPassReplaceOpPhiIdsFromDeadPredecessors : public FuzzerPass { + public: + FuzzerPassReplaceOpPhiIdsFromDeadPredecessors( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(); + + void Apply() override; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_ diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp new file mode 100644 index 00000000..0496268c --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h" +#include "source/fuzz/transformation_split_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassReplaceOpSelectsWithConditionalBranches:: + FuzzerPassReplaceOpSelectsWithConditionalBranches( + opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + +FuzzerPassReplaceOpSelectsWithConditionalBranches:: + ~FuzzerPassReplaceOpSelectsWithConditionalBranches() = default; + +void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() { + // Keep track of the instructions that we want to replace. We need to collect + // them in a vector, since it's not safe to modify the module while iterating + // over it. + std::vector<uint32_t> replaceable_opselect_instruction_ids; + + // Loop over all the instructions in the module. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // We cannot split loop headers, so we don't need to consider instructions + // in loop headers that are also merge blocks (since they would need to be + // split). + if (block.IsLoopHeader() && + GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock( + block.id())) { + continue; + } + + for (auto& instruction : block) { + // We only care about OpSelect instructions. + if (instruction.opcode() != SpvOpSelect) { + continue; + } + + // Randomly choose whether to consider this instruction for replacement. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfReplacingOpselectWithConditionalBranch())) { + continue; + } + + // If the block is a loop header and we need to split it, the + // transformation cannot be applied because loop headers cannot be + // split. We can break out of this loop because the transformation can + // only be applied to at most the first instruction in a loop header. + if (block.IsLoopHeader() && InstructionNeedsSplitBefore(&instruction)) { + break; + } + + // If the instruction separates an OpSampledImage from its use, the + // block cannot be split around it and the instruction cannot be + // replaced. + if (fuzzerutil:: + SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse( + &block, &instruction)) { + continue; + } + + // We can apply the transformation to this instruction. + replaceable_opselect_instruction_ids.push_back(instruction.result_id()); + } + } + } + + // Apply the transformations, splitting the blocks containing the + // instructions, if necessary. + for (uint32_t instruction_id : replaceable_opselect_instruction_ids) { + auto instruction = + GetIRContext()->get_def_use_mgr()->GetDef(instruction_id); + + // If the instruction requires the block containing it to be split before + // it, split the block. + if (InstructionNeedsSplitBefore(instruction)) { + ApplyTransformation(TransformationSplitBlock( + MakeInstructionDescriptor(GetIRContext(), instruction), + GetFuzzerContext()->GetFreshId())); + } + + // Decide whether to have two branches or just one. + bool two_branches = GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfAddingBothBranchesWhenReplacingOpSelect()); + + // If there will be only one branch, decide whether it will be the true + // branch or the false branch. + bool true_branch_id_zero = + !two_branches && + GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext() + ->GetChanceOfAddingTrueBranchWhenReplacingOpSelect()); + bool false_branch_id_zero = !two_branches && !true_branch_id_zero; + + uint32_t true_branch_id = + true_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId(); + uint32_t false_branch_id = + false_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId(); + + ApplyTransformation(TransformationReplaceOpSelectWithConditionalBranch( + instruction_id, true_branch_id, false_branch_id)); + } +} + +bool FuzzerPassReplaceOpSelectsWithConditionalBranches:: + InstructionNeedsSplitBefore(opt::Instruction* instruction) { + assert(instruction && instruction->opcode() == SpvOpSelect && + "The instruction must be OpSelect."); + + auto block = GetIRContext()->get_instr_block(instruction); + assert(block && "The instruction must be contained in a block."); + + // We need to split the block if the instruction is not the first in its + // block. + if (instruction->unique_id() != block->begin()->unique_id()) { + return true; + } + + // We need to split the block if it is a merge block. + if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) { + return true; + } + + // We need to split the block if it has more than one predecessor. + if (GetIRContext()->cfg()->preds(block->id()).size() != 1) { + return true; + } + + // We need to split the block if its predecessor is a header or it does not + // branch unconditionally to the block. + auto predecessor = GetIRContext()->get_instr_block( + GetIRContext()->cfg()->preds(block->id())[0]); + return predecessor->MergeBlockIdIfAny() || + predecessor->terminator()->opcode() != SpvOpBranch; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h new file mode 100644 index 00000000..04c6cc6d --- /dev/null +++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h @@ -0,0 +1,53 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass to replace OpSelect instructions (where the condition is a +// scalar boolean) with conditional branches and OpPhi instructions. +class FuzzerPassReplaceOpSelectsWithConditionalBranches : public FuzzerPass { + public: + FuzzerPassReplaceOpSelectsWithConditionalBranches( + opt::IRContext* ir_context, TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassReplaceOpSelectsWithConditionalBranches() override; + + void Apply() override; + + private: + // Returns true if any of the following holds: + // - the instruction is not the first in its block + // - the block containing it is a merge block + // - the block does not have a unique predecessor + // - the predecessor of the block is the header of a construct + // - the predecessor does not branch unconditionally to the block + // If this function returns true, the block must be split before the + // instruction for TransformationReplaceOpSelectWithConditionalBranch to be + // applicable. + // Assumes that the instruction is OpSelect. + bool InstructionNeedsSplitBefore(opt::Instruction* instruction); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_ diff --git a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp index 907c5b41..8672a3bb 100644 --- a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp +++ b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp @@ -81,7 +81,7 @@ void FuzzerPassReplaceParameterWithGlobal::Apply() { FindOrCreatePointerType(replaced_param->type_id(), SpvStorageClassPrivate); // Make sure initializer for the global variable exists in the module. - FindOrCreateZeroConstant(replaced_param->type_id()); + FindOrCreateZeroConstant(replaced_param->type_id(), false); ApplyTransformation(TransformationReplaceParameterWithGlobal( GetFuzzerContext()->GetFreshId(), replaced_param->result_id(), diff --git a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp index e49eacb7..86d6d06b 100644 --- a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp +++ b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp @@ -98,7 +98,7 @@ void FuzzerPassReplaceParamsWithStruct::Apply() { parameter_id.push_back(params[index]->result_id()); } - std::unordered_map<uint32_t, uint32_t> caller_id_to_fresh_id; + std::map<uint32_t, uint32_t> caller_id_to_fresh_id; for (const auto* inst : fuzzerutil::GetCallers(GetIRContext(), function.result_id())) { caller_id_to_fresh_id[inst->result_id()] = diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h index 278ec6dc..0ece48a0 100644 --- a/source/fuzz/fuzzer_pass_split_blocks.h +++ b/source/fuzz/fuzzer_pass_split_blocks.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_ -#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_ +#ifndef SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_ #include "source/fuzz/fuzzer_pass.h" @@ -37,4 +37,4 @@ class FuzzerPassSplitBlocks : public FuzzerPass { } // namespace fuzz } // namespace spvtools -#endif // SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_ +#endif // SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_ diff --git a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp index dc8b1eb8..9433a61f 100644 --- a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp +++ b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" + #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index 639c2fa0..7944d233 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -271,6 +271,11 @@ bool CanMakeSynonymOf(opt::IRContext* ir_context, return false; } auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id()); + if (type_inst->opcode() == SpvOpTypeVoid) { + // We only make synonyms of instructions that define objects, and an object + // cannot have void type. + return false; + } if (type_inst->opcode() == SpvOpTypePointer) { switch (inst->opcode()) { case SpvOpConstantNull: @@ -373,6 +378,28 @@ uint32_t GetArraySize(const opt::Instruction& array_type_instruction, return array_length_constant->GetU32(); } +uint32_t GetBoundForCompositeIndex(const opt::Instruction& composite_type_inst, + opt::IRContext* ir_context) { + switch (composite_type_inst.opcode()) { + case SpvOpTypeArray: + return fuzzerutil::GetArraySize(composite_type_inst, ir_context); + case SpvOpTypeMatrix: + case SpvOpTypeVector: + return composite_type_inst.GetSingleWordInOperand(1); + case SpvOpTypeStruct: { + return fuzzerutil::GetNumberOfStructMembers(composite_type_inst); + } + case SpvOpTypeRuntimeArray: + assert(false && + "GetBoundForCompositeIndex should not be invoked with an " + "OpTypeRuntimeArray, which does not have a static bound."); + return 0; + default: + assert(false && "Unknown composite type."); + return 0; + } +} + bool IsValid(opt::IRContext* context, spv_validator_options validator_options) { std::vector<uint32_t> binary; context->module()->ToBinary(&binary, false); @@ -454,6 +481,16 @@ opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) { return nullptr; } +bool FunctionContainsOpKillOrUnreachable(const opt::Function& function) { + for (auto& block : function) { + if (block.terminator()->opcode() == SpvOpKill || + block.terminator()->opcode() == SpvOpUnreachable) { + return true; + } + } + return false; +} + bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) { for (auto& entry_point : context->module()->entry_points()) { if (entry_point.GetSingleWordInOperand(1) == function_id) { @@ -466,6 +503,9 @@ bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) { bool IdIsAvailableAtUse(opt::IRContext* context, opt::Instruction* use_instruction, uint32_t use_input_operand_index, uint32_t id) { + assert(context->get_instr_block(use_instruction) && + "|use_instruction| must be in a basic block"); + auto defining_instruction = context->get_def_use_mgr()->GetDef(id); auto enclosing_function = context->get_instr_block(use_instruction)->GetParent(); @@ -484,6 +524,12 @@ bool IdIsAvailableAtUse(opt::IRContext* context, return false; } auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function); + if (!dominator_analysis->IsReachable( + context->get_instr_block(use_instruction)) || + !dominator_analysis->IsReachable(context->get_instr_block(id))) { + // Skip unreachable blocks. + return false; + } if (use_instruction->opcode() == SpvOpPhi) { // In the case where the use is an operand to OpPhi, it is actually the // *parent* block associated with the operand that must be dominated by @@ -499,6 +545,9 @@ bool IdIsAvailableAtUse(opt::IRContext* context, bool IdIsAvailableBeforeInstruction(opt::IRContext* context, opt::Instruction* instruction, uint32_t id) { + assert(context->get_instr_block(instruction) && + "|instruction| must be in a basic block"); + auto defining_instruction = context->get_def_use_mgr()->GetDef(id); auto enclosing_function = context->get_instr_block(instruction)->GetParent(); // If the id a function parameter, it needs to be associated with the @@ -515,8 +564,12 @@ bool IdIsAvailableBeforeInstruction(opt::IRContext* context, // The instruction is not available right before its own definition. return false; } - return context->GetDominatorAnalysis(enclosing_function) - ->Dominates(defining_instruction, instruction); + const auto* dominator_analysis = + context->GetDominatorAnalysis(enclosing_function); + return dominator_analysis->IsReachable( + context->get_instr_block(instruction)) && + dominator_analysis->IsReachable(context->get_instr_block(id)) && + dominator_analysis->Dominates(defining_instruction, instruction); } bool InstructionIsFunctionParameter(opt::Instruction* instruction, @@ -535,7 +588,9 @@ bool InstructionIsFunctionParameter(opt::Instruction* instruction, } uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id) { - return context->get_def_use_mgr()->GetDef(result_id)->type_id(); + const auto* inst = context->get_def_use_mgr()->GetDef(result_id); + assert(inst && "|result_id| is invalid"); + return inst->type_id(); } uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst) { @@ -735,6 +790,20 @@ std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context, return result; } +void RemoveParameter(opt::IRContext* ir_context, uint32_t parameter_id) { + auto* function = GetFunctionFromParameterId(ir_context, parameter_id); + assert(function && "|parameter_id| is invalid"); + assert(!FunctionIsEntryPoint(ir_context, function->result_id()) && + "Can't remove parameter from an entry point function"); + + function->RemoveParameter(parameter_id); + + // We've just removed parameters from the function and cleared their memory. + // Make sure analyses have no dangling pointers. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context, uint32_t function_id) { assert(FindFunction(ir_context, function_id) && @@ -766,6 +835,75 @@ opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context, return nullptr; } +uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id, + uint32_t new_function_type_result_id, + uint32_t return_type_id, + const std::vector<uint32_t>& parameter_type_ids) { + // Check some initial constraints. + assert(ir_context->get_type_mgr()->GetType(return_type_id) && + "Return type is invalid"); + for (auto id : parameter_type_ids) { + const auto* type = ir_context->get_type_mgr()->GetType(id); + (void)type; // Make compilers happy in release mode. + // Parameters can't be OpTypeVoid. + assert(type && !type->AsVoid() && "Parameter has invalid type"); + } + + auto* function = FindFunction(ir_context, function_id); + assert(function && "|function_id| is invalid"); + + auto* old_function_type = GetFunctionType(ir_context, function); + assert(old_function_type && "Function has invalid type"); + + std::vector<uint32_t> operand_ids = {return_type_id}; + operand_ids.insert(operand_ids.end(), parameter_type_ids.begin(), + parameter_type_ids.end()); + + // A trivial case - we change nothing. + if (FindFunctionType(ir_context, operand_ids) == + old_function_type->result_id()) { + return old_function_type->result_id(); + } + + if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 && + FindFunctionType(ir_context, operand_ids) == 0) { + // We can change |old_function_type| only if it's used once in the module + // and we are certain we won't create a duplicate as a result of the change. + + // Update |old_function_type| in-place. + opt::Instruction::OperandList operands; + for (auto id : operand_ids) { + operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); + } + + old_function_type->SetInOperands(std::move(operands)); + + // |operands| may depend on result ids defined below the |old_function_type| + // in the module. + old_function_type->RemoveFromList(); + ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type)); + return old_function_type->result_id(); + } else { + // We can't modify the |old_function_type| so we have to either use an + // existing one or create a new one. + auto type_id = FindOrCreateFunctionType( + ir_context, new_function_type_result_id, operand_ids); + assert(type_id != old_function_type->result_id() && + "We should've handled this case above"); + + function->DefInst().SetInOperand(1, {type_id}); + + // DefUseManager hasn't been updated yet, so if the following condition is + // true, then |old_function_type| will have no users when this function + // returns. We might as well remove it. + if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { + ir_context->KillInst(old_function_type); + } + + return type_id; + } +} + void AddFunctionType(opt::IRContext* ir_context, uint32_t result_id, const std::vector<uint32_t>& type_ids) { assert(result_id != 0 && "Result id can't be 0"); @@ -855,35 +993,34 @@ uint32_t MaybeGetZeroConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, uint32_t scalar_or_composite_type_id, bool is_irrelevant) { - const auto* type = - ir_context->get_type_mgr()->GetType(scalar_or_composite_type_id); - assert(type && "|scalar_or_composite_type_id| is invalid"); + const auto* type_inst = + ir_context->get_def_use_mgr()->GetDef(scalar_or_composite_type_id); + assert(type_inst && "|scalar_or_composite_type_id| is invalid"); - switch (type->kind()) { - case opt::analysis::Type::kBool: + switch (type_inst->opcode()) { + case SpvOpTypeBool: return MaybeGetBoolConstant(ir_context, transformation_context, false, is_irrelevant); - case opt::analysis::Type::kFloat: - case opt::analysis::Type::kInteger: { + case SpvOpTypeFloat: + case SpvOpTypeInt: { + const auto width = type_inst->GetSingleWordInOperand(0); std::vector<uint32_t> words = {0}; - if ((type->AsInteger() && type->AsInteger()->width() > 32) || - (type->AsFloat() && type->AsFloat()->width() > 32)) { + if (width > 32) { words.push_back(0); } return MaybeGetScalarConstant(ir_context, transformation_context, words, scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kStruct: { + case SpvOpTypeStruct: { std::vector<uint32_t> component_ids; - for (const auto* component_type : type->AsStruct()->element_types()) { - auto component_type_id = - ir_context->get_type_mgr()->GetId(component_type); - assert(component_type_id && "Component type is invalid"); + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + const auto component_type_id = type_inst->GetSingleWordInOperand(i); auto component_id = MaybeGetZeroConstant(ir_context, transformation_context, component_type_id, is_irrelevant); + if (component_id == 0 && is_irrelevant) { // Irrelevant constants can use either relevant or irrelevant // constituents. @@ -902,14 +1039,9 @@ uint32_t MaybeGetZeroConstant( ir_context, transformation_context, component_ids, scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kMatrix: - case opt::analysis::Type::kVector: { - const auto* component_type = type->AsVector() - ? type->AsVector()->element_type() - : type->AsMatrix()->element_type(); - auto component_type_id = - ir_context->get_type_mgr()->GetId(component_type); - assert(component_type_id && "Component type is invalid"); + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + const auto component_type_id = type_inst->GetSingleWordInOperand(0); auto component_id = MaybeGetZeroConstant( ir_context, transformation_context, component_type_id, is_irrelevant); @@ -925,23 +1057,21 @@ uint32_t MaybeGetZeroConstant( return 0; } - auto component_count = type->AsVector() - ? type->AsVector()->element_count() - : type->AsMatrix()->element_count(); + const auto component_count = type_inst->GetSingleWordInOperand(1); return MaybeGetCompositeConstant( ir_context, transformation_context, std::vector<uint32_t>(component_count, component_id), scalar_or_composite_type_id, is_irrelevant); } - case opt::analysis::Type::kArray: { - auto component_type_id = - ir_context->get_type_mgr()->GetId(type->AsArray()->element_type()); - assert(component_type_id && "Component type is invalid"); + case SpvOpTypeArray: { + const auto component_type_id = type_inst->GetSingleWordInOperand(0); auto component_id = MaybeGetZeroConstant( ir_context, transformation_context, component_type_id, is_irrelevant); if (component_id == 0 && is_irrelevant) { + // Irrelevant constants can use either relevant or irrelevant + // constituents. component_id = MaybeGetZeroConstant(ir_context, transformation_context, component_type_id, false); } @@ -950,12 +1080,6 @@ uint32_t MaybeGetZeroConstant( return 0; } - auto type_id = ir_context->get_type_mgr()->GetId(type); - assert(type_id && "|type| is invalid"); - - const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id); - assert(type_inst && "Array's type id is invalid"); - return MaybeGetCompositeConstant( ir_context, transformation_context, std::vector<uint32_t>(GetArraySize(*type_inst, ir_context), @@ -968,6 +1092,27 @@ uint32_t MaybeGetZeroConstant( } } +bool CanCreateConstant(const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kBool: + case opt::analysis::Type::kInteger: + case opt::analysis::Type::kFloat: + case opt::analysis::Type::kMatrix: + case opt::analysis::Type::kVector: + return true; + case opt::analysis::Type::kArray: + return CanCreateConstant(*type.AsArray()->element_type()); + case opt::analysis::Type::kStruct: + return std::all_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return CanCreateConstant(*element_type); + }); + default: + return false; + } +} + uint32_t MaybeGetScalarConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, @@ -998,10 +1143,7 @@ uint32_t MaybeGetCompositeConstant( bool is_irrelevant) { const auto* type = ir_context->get_type_mgr()->GetType(composite_type_id); (void)type; // Make compilers happy in release mode. - assert(type && - (type->AsArray() || type->AsStruct() || type->AsVector() || - type->AsMatrix()) && - "|composite_type_id| is invalid"); + assert(IsCompositeType(type) && "|composite_type_id| is invalid"); for (const auto& inst : ir_context->types_values()) { if (inst.opcode() == SpvOpConstantComposite && @@ -1040,6 +1182,32 @@ uint32_t MaybeGetIntegerConstant( return 0; } +uint32_t MaybeGetIntegerConstantFromValueAndType(opt::IRContext* ir_context, + uint32_t value, + uint32_t int_type_id) { + auto int_type_inst = ir_context->get_def_use_mgr()->GetDef(int_type_id); + + assert(int_type_inst && "The given type id must exist."); + + auto int_type = ir_context->get_type_mgr() + ->GetType(int_type_inst->result_id()) + ->AsInteger(); + + assert(int_type && int_type->width() == 32 && + "The given type id must correspond to an 32-bit integer type."); + + opt::analysis::IntConstant constant(int_type, {value}); + + // Check that the constant exists in the module. + if (!ir_context->get_constant_mgr()->FindConstant(&constant)) { + return 0; + } + + return ir_context->get_constant_mgr() + ->GetDefiningInstruction(&constant) + ->result_id(); +} + uint32_t MaybeGetFloatConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, @@ -1120,6 +1288,15 @@ void AddStructType(opt::IRContext* ir_context, uint32_t result_id, const auto* type = ir_context->get_type_mgr()->GetType(type_id); (void)type; // Make compiler happy in release mode. assert(type && !type->AsFunction() && "Component's type id is invalid"); + + if (type->AsStruct()) { + // From the spec for the BuiltIn decoration: + // - When applied to a structure-type member, that structure type cannot + // be contained as a member of another structure type. + assert(!MembersHaveBuiltInDecoration(ir_context, type_id) && + "A member struct has BuiltIn members"); + } + operands.push_back({SPV_OPERAND_TYPE_ID, {type_id}}); } @@ -1129,7 +1306,373 @@ void AddStructType(opt::IRContext* ir_context, uint32_t result_id, UpdateModuleIdBound(ir_context, result_id); } -} // namespace fuzzerutil +std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width, + bool is_signed) { + assert(width <= 64 && "The bit width should not be more than 64 bits"); + // Sign-extend or zero-extend the last |width| bits of |value|, depending on + // |is_signed|. + if (is_signed) { + // Sign-extend by shifting left and then shifting right, interpreting the + // integer as signed. + value = static_cast<int64_t>(value << (64 - width)) >> (64 - width); + } else { + // Zero-extend by shifting left and then shifting right, interpreting the + // integer as unsigned. + value = (value << (64 - width)) >> (64 - width); + } + + std::vector<uint32_t> result; + result.push_back(static_cast<uint32_t>(value)); + if (width > 32) { + result.push_back(static_cast<uint32_t>(value >> 32)); + } + return result; +} + +bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id, + uint32_t type2_id) { + if (type1_id == type2_id) { + return true; + } + + auto type1 = ir_context->get_type_mgr()->GetType(type1_id); + auto type2 = ir_context->get_type_mgr()->GetType(type2_id); + + // Integer scalar types must have the same width + if (type1->AsInteger() && type2->AsInteger()) { + return type1->AsInteger()->width() == type2->AsInteger()->width(); + } + + // Integer vector types must have the same number of components and their + // component types must be integers with the same width. + if (type1->AsVector() && type2->AsVector()) { + auto component_type1 = type1->AsVector()->element_type()->AsInteger(); + auto component_type2 = type2->AsVector()->element_type()->AsInteger(); + + // Only check the component count and width if they are integer. + if (component_type1 && component_type2) { + return type1->AsVector()->element_count() == + type2->AsVector()->element_count() && + component_type1->width() == component_type2->width(); + } + } + + // In all other cases, the types cannot be considered equal. + return false; +} + +std::map<uint32_t, uint32_t> RepeatedUInt32PairToMap( + const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>& data) { + std::map<uint32_t, uint32_t> result; + + for (const auto& entry : data) { + result[entry.first()] = entry.second(); + } + + return result; +} + +google::protobuf::RepeatedPtrField<protobufs::UInt32Pair> +MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data) { + google::protobuf::RepeatedPtrField<protobufs::UInt32Pair> result; + + for (const auto& entry : data) { + protobufs::UInt32Pair pair; + pair.set_first(entry.first); + pair.set_second(entry.second); + *result.Add() = std::move(pair); + } + + return result; +} + +opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, + uint32_t block_id, + SpvOp opcode) { + // CFG::block uses std::map::at which throws an exception when |block_id| is + // invalid. The error message is unhelpful, though. Thus, we test that + // |block_id| is valid here. + const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id); + (void)label_inst; // Make compilers happy in release mode. + assert(label_inst && label_inst->opcode() == SpvOpLabel && + "|block_id| is invalid"); + + auto* block = ir_context->cfg()->block(block_id); + auto it = block->rbegin(); + assert(it != block->rend() && "Basic block can't be empty"); + + if (block->GetMergeInst()) { + ++it; + assert(it != block->rend() && + "|block| must have at least two instructions:" + "terminator and a merge instruction"); + } + + return CanInsertOpcodeBeforeInstruction(opcode, &*it) ? &*it : nullptr; +} + +bool IdUseCanBeReplaced(opt::IRContext* ir_context, + opt::Instruction* use_instruction, + uint32_t use_in_operand_index) { + if (spvOpcodeIsAccessChain(use_instruction->opcode()) && + use_in_operand_index > 0) { + // This is an access chain index. If the (sub-)object being accessed by the + // given index has struct type then we cannot replace the use, as it needs + // to be an OpConstant. + + // Get the top-level composite type that is being accessed. + auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef( + use_instruction->GetSingleWordInOperand(0)); + auto pointer_type = + ir_context->get_type_mgr()->GetType(object_being_accessed->type_id()); + assert(pointer_type->AsPointer()); + auto composite_type_being_accessed = + pointer_type->AsPointer()->pointee_type(); + + // Now walk the access chain, tracking the type of each sub-object of the + // composite that is traversed, until the index of interest is reached. + for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index; + index_in_operand++) { + // For vectors, matrices and arrays, getting the type of the sub-object is + // trivial. For the struct case, the sub-object type is field-sensitive, + // and depends on the constant index that is used. + if (composite_type_being_accessed->AsVector()) { + composite_type_being_accessed = + composite_type_being_accessed->AsVector()->element_type(); + } else if (composite_type_being_accessed->AsMatrix()) { + composite_type_being_accessed = + composite_type_being_accessed->AsMatrix()->element_type(); + } else if (composite_type_being_accessed->AsArray()) { + composite_type_being_accessed = + composite_type_being_accessed->AsArray()->element_type(); + } else if (composite_type_being_accessed->AsRuntimeArray()) { + composite_type_being_accessed = + composite_type_being_accessed->AsRuntimeArray()->element_type(); + } else { + assert(composite_type_being_accessed->AsStruct()); + auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef( + use_instruction->GetSingleWordInOperand(index_in_operand)); + assert(constant_index_instruction->opcode() == SpvOpConstant); + uint32_t member_index = + constant_index_instruction->GetSingleWordInOperand(0); + composite_type_being_accessed = + composite_type_being_accessed->AsStruct() + ->element_types()[member_index]; + } + } + + // We have found the composite type being accessed by the index we are + // considering replacing. If it is a struct, then we cannot do the + // replacement as struct indices must be constants. + if (composite_type_being_accessed->AsStruct()) { + return false; + } + } + + if (use_instruction->opcode() == SpvOpFunctionCall && + use_in_operand_index > 0) { + // This is a function call argument. It is not allowed to have pointer + // type. + + // Get the definition of the function being called. + auto function = ir_context->get_def_use_mgr()->GetDef( + use_instruction->GetSingleWordInOperand(0)); + // From the function definition, get the function type. + auto function_type = ir_context->get_def_use_mgr()->GetDef( + function->GetSingleWordInOperand(1)); + // OpTypeFunction's 0-th input operand is the function return type, and the + // function argument types follow. Because the arguments to OpFunctionCall + // start from input operand 1, we can use |use_in_operand_index| to get the + // type associated with this function argument. + auto parameter_type = ir_context->get_type_mgr()->GetType( + function_type->GetSingleWordInOperand(use_in_operand_index)); + if (parameter_type->AsPointer()) { + return false; + } + } + + if (use_instruction->opcode() == SpvOpImageTexelPointer && + use_in_operand_index == 2) { + // The OpImageTexelPointer instruction has a Sample parameter that in some + // situations must be an id for the value 0. To guard against disrupting + // that requirement, we do not replace this argument to that instruction. + return false; + } + + return true; +} + +bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context, + uint32_t struct_type_id) { + const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(struct_type_id); + assert(type_inst && type_inst->opcode() == SpvOpTypeStruct && + "|struct_type_id| is not a result id of an OpTypeStruct"); + + uint32_t builtin_count = 0; + ir_context->get_def_use_mgr()->ForEachUser( + type_inst, + [struct_type_id, &builtin_count](const opt::Instruction* user) { + if (user->opcode() == SpvOpMemberDecorate && + user->GetSingleWordInOperand(0) == struct_type_id && + static_cast<SpvDecoration>(user->GetSingleWordInOperand(2)) == + SpvDecorationBuiltIn) { + ++builtin_count; + } + }); + + assert((builtin_count == 0 || builtin_count == type_inst->NumInOperands()) && + "The module is invalid: either none or all of the members of " + "|struct_type_id| may be builtin"); + + return builtin_count != 0; +} + +bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse( + opt::BasicBlock* block_to_split, opt::Instruction* split_before) { + std::set<uint32_t> sampled_image_result_ids; + bool before_split = true; + + // Check all the instructions in the block to split. + for (auto& instruction : *block_to_split) { + if (&instruction == &*split_before) { + before_split = false; + } + if (before_split) { + // If the instruction comes before the split and its opcode is + // OpSampledImage, record its result id. + if (instruction.opcode() == SpvOpSampledImage) { + sampled_image_result_ids.insert(instruction.result_id()); + } + } else { + // If the instruction comes after the split, check if ids + // corresponding to OpSampledImage instructions defined before the split + // are used, and return true if they are. + if (!instruction.WhileEachInId( + [&sampled_image_result_ids](uint32_t* id) -> bool { + return !sampled_image_result_ids.count(*id); + })) { + return true; + } + } + } + + // No usage that would be separated from the definition has been found. + return false; +} + +bool InstructionHasNoSideEffects(const opt::Instruction& instruction) { + switch (instruction.opcode()) { + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpArrayLength: + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + case SpvOpPhi: + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + return true; + default: + return false; + } +} + +} // namespace fuzzerutil } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index 42ef5cc6..981d2295 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -15,6 +15,7 @@ #ifndef SOURCE_FUZZ_FUZZER_UTIL_H_ #define SOURCE_FUZZ_FUZZER_UTIL_H_ +#include <map> #include <vector> #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" @@ -141,6 +142,13 @@ uint32_t GetNumberOfStructMembers( uint32_t GetArraySize(const opt::Instruction& array_type_instruction, opt::IRContext* context); +// Returns the bound for indexing into a composite of type +// |composite_type_inst|, i.e. the number of fields of a struct, the size of an +// array, the number of components of a vector, or the number of columns of a +// matrix. |composite_type_inst| must be the type of a composite. +uint32_t GetBoundForCompositeIndex(const opt::Instruction& composite_type_inst, + opt::IRContext* ir_context); + // Returns true if and only if |context| is valid, according to the validator // instantiated with |validator_options|. bool IsValid(opt::IRContext* context, spv_validator_options validator_options); @@ -171,18 +179,23 @@ opt::Instruction* GetFunctionType(opt::IRContext* context, // function exists. opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id); +// Returns true if |function| has a block that the termination instruction is +// OpKill or OpUnreachable. +bool FunctionContainsOpKillOrUnreachable(const opt::Function& function); + // Returns |true| if one of entry points has function id |function_id|. bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id); // Checks whether |id| is available (according to dominance rules) at the use // point defined by input operand |use_input_operand_index| of -// |use_instruction|. +// |use_instruction|. |use_instruction| must be a in some basic block. bool IdIsAvailableAtUse(opt::IRContext* context, opt::Instruction* use_instruction, uint32_t use_input_operand_index, uint32_t id); // Checks whether |id| is available (according to dominance rules) at the -// program point directly before |instruction|. +// program point directly before |instruction|. |instruction| must be in some +// basic block. bool IdIsAvailableBeforeInstruction(opt::IRContext* context, opt::Instruction* instruction, uint32_t id); @@ -281,6 +294,15 @@ bool IsPermutationOfRange(const std::vector<uint32_t>& arr, uint32_t lo, std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context, uint32_t function_id); +// Removes an OpFunctionParameter instruction with result id |parameter_id| +// from the its function. Parameter's function must not be an entry-point +// function. The function must have a parameter with result id |parameter_id|. +// +// Prefer using this function to opt::Function::RemoveParameter since +// this function also guarantees that |ir_context| has no invalid pointers +// to the removed parameter. +void RemoveParameter(opt::IRContext* ir_context, uint32_t parameter_id); + // Returns all OpFunctionCall instructions that call a function with result id // |function_id|. std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context, @@ -291,6 +313,23 @@ std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context, opt::Function* GetFunctionFromParameterId(opt::IRContext* ir_context, uint32_t param_id); +// Changes the type of function |function_id| so that its return type is +// |return_type_id| and its parameters' types are |parameter_type_ids|. If a +// suitable function type already exists in the module, it is used, otherwise +// |new_function_type_result_id| is used as the result id of a suitable new +// function type instruction. If the old type of the function doesn't have any +// more users, it is removed from the module. Returns the result id of the +// OpTypeFunction instruction that is used as a type of the function with +// |function_id|. +// +// CAUTION: When the old type of the function is removed from the module, its +// memory is deallocated. Be sure not to use any pointers to the old +// type when this function returns. +uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id, + uint32_t new_function_type_result_id, + uint32_t return_type_id, + const std::vector<uint32_t>& parameter_type_ids); + // Creates new OpTypeFunction instruction in the module. |type_ids| may not be // empty. It may not contain result ids of OpTypeFunction instructions. // |type_ids[i]| may not be a result id of OpTypeVoid instruction for |i >= 1|. @@ -348,7 +387,12 @@ uint32_t MaybeGetStructType(opt::IRContext* ir_context, uint32_t MaybeGetZeroConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, - uint32_t scalar_or_composite_type_id, bool is_irrelevant = false); + uint32_t scalar_or_composite_type_id, bool is_irrelevant); + +// Returns true if it is possible to create an OpConstant or an +// OpConstantComposite instruction of |type|. That is, returns true if |type| +// and all its constituents are either scalar or composite. +bool CanCreateConstant(const opt::analysis::Type& type); // Returns the result id of an OpConstant instruction. |scalar_type_id| must be // a result id of a scalar type (i.e. int, float or bool). Returns 0 if no such @@ -358,7 +402,7 @@ uint32_t MaybeGetScalarConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, const std::vector<uint32_t>& words, uint32_t scalar_type_id, - bool is_irrelevant = false); + bool is_irrelevant); // Returns the result id of an OpConstantComposite instruction. // |composite_type_id| must be a result id of a composite type (i.e. vector, @@ -369,7 +413,7 @@ uint32_t MaybeGetCompositeConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, const std::vector<uint32_t>& component_ids, uint32_t composite_type_id, - bool is_irrelevant = false); + bool is_irrelevant); // Returns the result id of an OpConstant instruction of integral type. // Returns 0 if no such instruction or type is present in the module. @@ -379,7 +423,15 @@ uint32_t MaybeGetIntegerConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, const std::vector<uint32_t>& words, uint32_t width, bool is_signed, - bool is_irrelevant = false); + bool is_irrelevant); + +// Returns the id of a 32-bit integer constant in the module with type +// |int_type_id| and value |value|, or 0 if no such constant exists in the +// module. |int_type_id| must exist in the module and it must correspond to a +// 32-bit integer type. +uint32_t MaybeGetIntegerConstantFromValueAndType(opt::IRContext* ir_context, + uint32_t value, + uint32_t int_type_id); // Returns the result id of an OpConstant instruction of floating-point type. // Returns 0 if no such instruction or type is present in the module. @@ -388,8 +440,7 @@ uint32_t MaybeGetIntegerConstant( uint32_t MaybeGetFloatConstant( opt::IRContext* ir_context, const TransformationContext& transformation_context, - const std::vector<uint32_t>& words, uint32_t width, - bool is_irrelevant = false); + const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant); // Returns the id of a boolean constant with value |value| if it exists in the // module, or 0 otherwise. The returned id either participates in IdIsIrrelevant @@ -397,7 +448,7 @@ uint32_t MaybeGetFloatConstant( uint32_t MaybeGetBoolConstant( opt::IRContext* context, const TransformationContext& transformation_context, bool value, - bool is_irrelevant = false); + bool is_irrelevant); // Creates a new OpTypeInt instruction in the module. Updates module's id bound // to accommodate for |result_id|. @@ -418,10 +469,22 @@ void AddVectorType(opt::IRContext* ir_context, uint32_t result_id, // Creates a new OpTypeStruct instruction in the module. Updates module's id // bound to accommodate for |result_id|. |component_type_ids| may not contain -// a result id of an OpTypeFunction. +// a result id of an OpTypeFunction. if |component_type_ids| contains a result +// of an OpTypeStruct instruction, that struct may not have BuiltIn members. void AddStructType(opt::IRContext* ir_context, uint32_t result_id, const std::vector<uint32_t>& component_type_ids); +// Returns a vector of words representing the integer |value|, only considering +// the last |width| bits. The last |width| bits are sign-extended if the value +// is signed, zero-extended if it is unsigned. +// |width| must be <= 64. +// If |width| <= 32, returns a vector containing one value. If |width| > 64, +// returns a vector containing two values, with the first one representing the +// lower-order word of the value and the second one representing the +// higher-order word. +std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width, + bool is_signed); + // Returns a bit pattern that represents a floating-point |value|. inline uint32_t FloatToWord(float value) { uint32_t result; @@ -429,8 +492,62 @@ inline uint32_t FloatToWord(float value) { return result; } -} // namespace fuzzerutil +// Returns true if any of the following is true: +// - |type1_id| and |type2_id| are the same id +// - |type1_id| and |type2_id| refer to integer scalar or vector types, only +// differing by their signedness. +bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id, + uint32_t type2_id); + +// Converts repeated field of UInt32Pair to a map. If two or more equal values +// of |UInt32Pair::first()| are available in |data|, the last value of +// |UInt32Pair::second()| is used. +std::map<uint32_t, uint32_t> RepeatedUInt32PairToMap( + const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>& data); + +// Converts a map into a repeated field of UInt32Pair. +google::protobuf::RepeatedPtrField<protobufs::UInt32Pair> +MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data); + +// Returns the last instruction in |block_id| before which an instruction with +// opcode |opcode| can be inserted, or nullptr if there is no such instruction. +opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context, + uint32_t block_id, + SpvOp opcode); + +// Checks whether various conditions hold related to the acceptability of +// replacing the id use at |use_in_operand_index| of |use_instruction| with a +// synonym or another id of appropriate type if the original id is irrelevant. +// In particular, this checks that: +// - the id use is not an index into a struct field in an OpAccessChain - such +// indices must be constants, so it is dangerous to replace them. +// - the id use is not a pointer function call argument, on which there are +// restrictions that make replacement problematic. +// - the id use is not the Sample parameter of an OpImageTexelPointer +// instruction, as this must satisfy particular requirements. +bool IdUseCanBeReplaced(opt::IRContext* ir_context, + opt::Instruction* use_instruction, + uint32_t use_in_operand_index); + +// Requires that |struct_type_id| is the id of a struct type, and (as per the +// SPIR-V spec) that either all or none of the members of |struct_type_id| have +// the BuiltIn decoration. Returns true if and only if all members have the +// BuiltIn decoration. +bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context, + uint32_t struct_type_id); + +// Returns true iff splitting block |block_to_split| just before the instruction +// |split_before| would separate an OpSampledImage instruction from its usage. +bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse( + opt::BasicBlock* block_to_split, opt::Instruction* split_before); + +// Returns true if the instruction given has no side effects. +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3758): Add any +// missing instructions to the list. In particular, GLSL extended instructions +// (called using OpExtInst) have not been considered. +bool InstructionHasNoSideEffects(const opt::Instruction& instruction); +} // namespace fuzzerutil } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/id_use_descriptor.cpp b/source/fuzz/id_use_descriptor.cpp index eb8589df..4e10146f 100644 --- a/source/fuzz/id_use_descriptor.cpp +++ b/source/fuzz/id_use_descriptor.cpp @@ -52,7 +52,7 @@ protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse( opt::IRContext* context, opt::Instruction* inst, uint32_t in_operand_index) { const auto& in_operand = inst->GetInOperand(in_operand_index); - assert(in_operand.type == SPV_OPERAND_TYPE_ID); + assert(spvIsInIdType(in_operand.type)); return MakeIdUseDescriptor(in_operand.words[0], MakeInstructionDescriptor(context, inst), in_operand_index); diff --git a/source/fuzz/overflow_id_source.cpp b/source/fuzz/overflow_id_source.cpp new file mode 100644 index 00000000..d9004554 --- /dev/null +++ b/source/fuzz/overflow_id_source.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/overflow_id_source.h" + +namespace spvtools { +namespace fuzz { + +OverflowIdSource::~OverflowIdSource() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/overflow_id_source.h b/source/fuzz/overflow_id_source.h new file mode 100644 index 00000000..b428fa54 --- /dev/null +++ b/source/fuzz/overflow_id_source.h @@ -0,0 +1,106 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ +#define SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ + +#include <cstdint> + +namespace spvtools { +namespace fuzz { + +// An implementation of this interface can be used to provide fresh ids on +// demand when applying a transformation. +// +// During fuzzing this should never be required: a fuzzer pass should determine +// all the fresh ids it requires to apply a transformation. +// +// However, during shrinking we can have the situation where, after removing +// an early transformation, a later transformation needs more ids. +// +// As an example, suppose a SPIR-V function originally has this form: +// +// main() { +// stmt1; +// stmt2; +// stmt3; +// stmt4; +// } +// +// Now suppose two *outlining* transformations are applied. The first +// transformation, T1, outlines "stmt1; stmt2;" into a function foo, giving us: +// +// foo() { +// stmt1; +// stmt2; +// } +// +// main() { +// foo(); +// stmt3; +// stmt4; +// } +// +// The second transformation, T2, outlines "foo(); stmt3;" from main into a +// function bar, giving us: +// +// foo() { +// stmt1; +// stmt2; +// } +// +// bar() { +// foo(); +// stmt3; +// } +// +// main() { +// bar(); +// stmt4; +// } +// +// Suppose that T2 used a set of fresh ids, FRESH, in order to perform its +// outlining. +// +// Now suppose that during shrinking we remove T1, but still want to apply T2. +// The fresh ids used by T2 - FRESH - are sufficient to outline "foo(); stmt3;". +// However, because we did not apply T1, "foo();" does not exist and instead the +// task of T2 is to outline "stmt1; stmt2; stmt3;". The set FRESH contains +// *some* of the fresh ids required to do this (those for "stmt3;"), but not all +// of them (those for "stmt1; stmt2;" are missing). +// +// A source of overflow ids can be used to allow the shrinker to proceed +// nevertheless. +// +// It is desirable to use overflow ids only when needed. In our worked example, +// T2 should still use the ids from FRESH when handling "stmt3;", because later +// transformations might refer to those ids and will become inapplicable if +// overflow ids are used instead. +class OverflowIdSource { + public: + virtual ~OverflowIdSource(); + + // Returns true if and only if this source is capable of providing overflow + // ids. + virtual bool HasOverflowIds() const = 0; + + // Precondition: HasOverflowIds() must hold. Returns the next available + // overflow id. + virtual uint32_t GetNextOverflowId() = 0; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_ diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h new file mode 100644 index 00000000..57820a24 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_instances.h @@ -0,0 +1,175 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_ +#define SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_ + +#include "source/fuzz/fuzzer_pass_add_access_chains.h" +#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" +#include "source/fuzz/fuzzer_pass_add_composite_types.h" +#include "source/fuzz/fuzzer_pass_add_copy_memory.h" +#include "source/fuzz/fuzzer_pass_add_dead_blocks.h" +#include "source/fuzz/fuzzer_pass_add_dead_breaks.h" +#include "source/fuzz/fuzzer_pass_add_dead_continues.h" +#include "source/fuzz/fuzzer_pass_add_equation_instructions.h" +#include "source/fuzz/fuzzer_pass_add_function_calls.h" +#include "source/fuzz/fuzzer_pass_add_global_variables.h" +#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h" +#include "source/fuzz/fuzzer_pass_add_loads.h" +#include "source/fuzz/fuzzer_pass_add_local_variables.h" +#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h" +#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_parameters.h" +#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h" +#include "source/fuzz/fuzzer_pass_add_stores.h" +#include "source/fuzz/fuzzer_pass_add_synonyms.h" +#include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h" +#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h" +#include "source/fuzz/fuzzer_pass_construct_composites.h" +#include "source/fuzz/fuzzer_pass_copy_objects.h" +#include "source/fuzz/fuzzer_pass_donate_modules.h" +#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h" +#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h" +#include "source/fuzz/fuzzer_pass_inline_functions.h" +#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h" +#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h" +#include "source/fuzz/fuzzer_pass_merge_blocks.h" +#include "source/fuzz/fuzzer_pass_mutate_pointers.h" +#include "source/fuzz/fuzzer_pass_obfuscate_constants.h" +#include "source/fuzz/fuzzer_pass_outline_functions.h" +#include "source/fuzz/fuzzer_pass_permute_blocks.h" +#include "source/fuzz/fuzzer_pass_permute_function_parameters.h" +#include "source/fuzz/fuzzer_pass_permute_instructions.h" +#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h" +#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h" +#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h" +#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h" +#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h" +#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h" +#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h" +#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h" +#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h" +#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" +#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h" +#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h" +#include "source/fuzz/fuzzer_pass_split_blocks.h" +#include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h" + +namespace spvtools { +namespace fuzz { + +// This class has a distinct member for each repeated fuzzer pass (i.e., a +// fuzzer pass that it makes sense to run multiple times). If a member is null +// then we do not have an instance of that fuzzer pass, i.e. it is disabled. +// The class also provides access to the set of passes that are enabled. +class RepeatedPassInstances { +// This macro should be invoked below for every repeated fuzzer pass. If a +// repeated fuzzer pass is called FuzzerPassFoo then the macro invocation: +// +// REPEATED_PASS_INSTANCE(Foo); +// +// should be used. This adds a private member of type FuzzerPassFoo*, and +// provides the following public methods: +// +// // Requires that SetPass has not been called previously with FuzzerPassFoo. +// // Adds |pass| to the set of known pass instances. +// void SetPass(std::unique_ptr<FuzzerPassFoo> pass); +// +// // Returns a pointer to a pass instance of type FuzzerPassFoo that was +// // previously registered via SetPass(), or nullptr if no such instance was +// // registered +// FuzzerPassFoo* GetFoo(); +#define REPEATED_PASS_INSTANCE(NAME) \ + public: \ + FuzzerPass##NAME* Get##NAME() const { return NAME##_; } \ + void SetPass(std::unique_ptr<FuzzerPass##NAME> pass) { \ + assert(NAME##_ == nullptr && "Attempt to set pass multiple times."); \ + NAME##_ = pass.get(); \ + passes_.push_back(std::move(pass)); \ + } \ + \ + private: \ + FuzzerPass##NAME* NAME##_ = nullptr + + REPEATED_PASS_INSTANCE(AddAccessChains); + REPEATED_PASS_INSTANCE(AddBitInstructionSynonyms); + REPEATED_PASS_INSTANCE(AddCompositeInserts); + REPEATED_PASS_INSTANCE(AddCompositeTypes); + REPEATED_PASS_INSTANCE(AddCopyMemory); + REPEATED_PASS_INSTANCE(AddDeadBlocks); + REPEATED_PASS_INSTANCE(AddDeadBreaks); + REPEATED_PASS_INSTANCE(AddDeadContinues); + REPEATED_PASS_INSTANCE(AddEquationInstructions); + REPEATED_PASS_INSTANCE(AddFunctionCalls); + REPEATED_PASS_INSTANCE(AddGlobalVariables); + REPEATED_PASS_INSTANCE(AddImageSampleUnusedComponents); + REPEATED_PASS_INSTANCE(AddLoads); + REPEATED_PASS_INSTANCE(AddLocalVariables); + REPEATED_PASS_INSTANCE(AddLoopPreheaders); + REPEATED_PASS_INSTANCE(AddLoopsToCreateIntConstantSynonyms); + REPEATED_PASS_INSTANCE(AddOpPhiSynonyms); + REPEATED_PASS_INSTANCE(AddParameters); + REPEATED_PASS_INSTANCE(AddRelaxedDecorations); + REPEATED_PASS_INSTANCE(AddStores); + REPEATED_PASS_INSTANCE(AddSynonyms); + REPEATED_PASS_INSTANCE(AddVectorShuffleInstructions); + REPEATED_PASS_INSTANCE(ApplyIdSynonyms); + REPEATED_PASS_INSTANCE(ConstructComposites); + REPEATED_PASS_INSTANCE(CopyObjects); + REPEATED_PASS_INSTANCE(DonateModules); + REPEATED_PASS_INSTANCE(DuplicateRegionsWithSelections); + REPEATED_PASS_INSTANCE(FlattenConditionalBranches); + REPEATED_PASS_INSTANCE(InlineFunctions); + REPEATED_PASS_INSTANCE(InvertComparisonOperators); + REPEATED_PASS_INSTANCE(MakeVectorOperationsDynamic); + REPEATED_PASS_INSTANCE(MergeBlocks); + REPEATED_PASS_INSTANCE(MutatePointers); + REPEATED_PASS_INSTANCE(ObfuscateConstants); + REPEATED_PASS_INSTANCE(OutlineFunctions); + REPEATED_PASS_INSTANCE(PermuteBlocks); + REPEATED_PASS_INSTANCE(PermuteFunctionParameters); + REPEATED_PASS_INSTANCE(PermuteInstructions); + REPEATED_PASS_INSTANCE(PropagateInstructionsUp); + REPEATED_PASS_INSTANCE(PushIdsThroughVariables); + REPEATED_PASS_INSTANCE(ReplaceAddsSubsMulsWithCarryingExtended); + REPEATED_PASS_INSTANCE(ReplaceCopyMemoriesWithLoadsStores); + REPEATED_PASS_INSTANCE(ReplaceCopyObjectsWithStoresLoads); + REPEATED_PASS_INSTANCE(ReplaceLoadsStoresWithCopyMemories); + REPEATED_PASS_INSTANCE(ReplaceIrrelevantIds); + REPEATED_PASS_INSTANCE(ReplaceOpPhiIdsFromDeadPredecessors); + REPEATED_PASS_INSTANCE(ReplaceOpSelectsWithConditionalBranches); + REPEATED_PASS_INSTANCE(ReplaceParameterWithGlobal); + REPEATED_PASS_INSTANCE(ReplaceLinearAlgebraInstructions); + REPEATED_PASS_INSTANCE(ReplaceParamsWithStruct); + REPEATED_PASS_INSTANCE(SplitBlocks); + REPEATED_PASS_INSTANCE(SwapBranchConditionalOperands); +#undef REPEATED_PASS_INSTANCE + + public: + // Yields the sequence of fuzzer pass instances that have been registered. + const std::vector<std::unique_ptr<FuzzerPass>>& GetPasses() const { + return passes_; + } + + private: + // The distinct fuzzer pass instances that have been registered via SetPass(). + std::vector<std::unique_ptr<FuzzerPass>> passes_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager.cpp b/source/fuzz/pass_management/repeated_pass_manager.cpp new file mode 100644 index 00000000..032f2645 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_manager.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManager::RepeatedPassManager(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances) + : fuzzer_context_(fuzzer_context), pass_instances_(pass_instances) {} + +RepeatedPassManager::~RepeatedPassManager() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h new file mode 100644 index 00000000..29b5fcc9 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager.h @@ -0,0 +1,55 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_ + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" + +namespace spvtools { +namespace fuzz { + +// An interface to encapsulate the manner in which the sequence of repeated +// passes that are applied during fuzzing is chosen. An implementation of this +// interface could, for example, keep track of the history of passes that have +// been run and bias the selection of future passes according to this history. +class RepeatedPassManager { + public: + RepeatedPassManager(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances); + + virtual ~RepeatedPassManager(); + + // Returns the fuzzer pass instance that should be run next. + virtual FuzzerPass* ChoosePass() = 0; + + protected: + FuzzerContext* GetFuzzerContext() { return fuzzer_context_; } + + RepeatedPassInstances* GetPassInstances() { return pass_instances_; } + + private: + // Provided in order to allow the pass manager to make random decisions. + FuzzerContext* fuzzer_context_; + + // The repeated fuzzer passes that are enabled. + RepeatedPassInstances* pass_instances_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp new file mode 100644 index 00000000..e8bd5458 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerLoopedWithRecommendations:: + RepeatedPassManagerLoopedWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender) + : RepeatedPassManager(fuzzer_context, pass_instances), next_pass_index_(0) { + auto& passes = GetPassInstances()->GetPasses(); + do { + FuzzerPass* current_pass = + passes[GetFuzzerContext()->RandomIndex(passes)].get(); + pass_loop_.push_back(current_pass); + for (auto future_pass : + pass_recommender->GetFuturePassRecommendations(*current_pass)) { + pass_loop_.push_back(future_pass); + } + } while (fuzzer_context->ChoosePercentage( + fuzzer_context->GetChanceOfAddingAnotherPassToPassLoop())); +} + +RepeatedPassManagerLoopedWithRecommendations:: + ~RepeatedPassManagerLoopedWithRecommendations() = default; + +FuzzerPass* RepeatedPassManagerLoopedWithRecommendations::ChoosePass() { + auto result = pass_loop_[next_pass_index_]; + next_pass_index_ = + (next_pass_index_ + 1) % static_cast<uint32_t>(pass_loop_.size()); + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h new file mode 100644 index 00000000..25cef061 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h @@ -0,0 +1,58 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ + +#include <vector> + +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// On construction, this pass manager creates a sequence of fuzzer passes which +// is not changed thereafter. Passes from this sequence are served up in round +// robin fashion each time ChoosePass is invoked - i.e., the sequence is a "pass +// loop". +// +// The pass loop is constructed by repeatedly: +// - Randomly adding an enabled pass +// - Adding all recommended follow-on passes for this pass +// and probabilistically terminating this process. +class RepeatedPassManagerLoopedWithRecommendations + : public RepeatedPassManager { + public: + RepeatedPassManagerLoopedWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender); + + ~RepeatedPassManagerLoopedWithRecommendations() override; + + FuzzerPass* ChoosePass() override; + + private: + // The loop of fuzzer passes to be applied, populated on construction. + std::vector<FuzzerPass*> pass_loop_; + + // An index into |pass_loop_| specifying which pass should be served up next + // time ChoosePass is invoked. + uint32_t next_pass_index_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp new file mode 100644 index 00000000..48b1e6a1 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerRandomWithRecommendations:: + RepeatedPassManagerRandomWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender) + : RepeatedPassManager(fuzzer_context, pass_instances), + pass_recommender_(pass_recommender) {} + +RepeatedPassManagerRandomWithRecommendations:: + ~RepeatedPassManagerRandomWithRecommendations() = default; + +FuzzerPass* RepeatedPassManagerRandomWithRecommendations::ChoosePass() { + FuzzerPass* result; + if (recommended_passes_.empty() || GetFuzzerContext()->ChooseEven()) { + auto& passes = GetPassInstances()->GetPasses(); + result = passes[GetFuzzerContext()->RandomIndex(passes)].get(); + } else { + result = recommended_passes_.front(); + recommended_passes_.pop_front(); + } + for (auto future_pass : + pass_recommender_->GetFuturePassRecommendations(*result)) { + recommended_passes_.push_back(future_pass); + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h new file mode 100644 index 00000000..acd207b8 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h @@ -0,0 +1,59 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ + +#include <deque> + +#include "source/fuzz/pass_management/repeated_pass_manager.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// This repeated pass manager uses a pass recommender to recommend future passes +// each time a fuzzer pass is run. It keeps a queue of recommended passes. +// +// Each time a fuzzer pass is requested, the manager either selects an enabled +// fuzzer pass at random, or selects the pass at the front of the recommendation +// queue, removing it from the queue. The decision of which of these pass +// selection methods to use is made randomly each time ChoosePass is called. +// +// Either way, recommended follow-on passes for the chosen pass are added to +// the recommendation queue. +class RepeatedPassManagerRandomWithRecommendations + : public RepeatedPassManager { + public: + RepeatedPassManagerRandomWithRecommendations( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances, + RepeatedPassRecommender* pass_recommender); + + ~RepeatedPassManagerRandomWithRecommendations() override; + + FuzzerPass* ChoosePass() override; + + private: + // The queue of passes that have been recommended based on previously-chosen + // passes. + std::deque<FuzzerPass*> recommended_passes_; + + // Used to recommend future passes. + RepeatedPassRecommender* pass_recommender_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_ diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.cpp b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp new file mode 100644 index 00000000..a85a7321 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_manager_simple.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassManagerSimple::RepeatedPassManagerSimple( + FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances) + : RepeatedPassManager(fuzzer_context, pass_instances) {} + +RepeatedPassManagerSimple::~RepeatedPassManagerSimple() = default; + +FuzzerPass* RepeatedPassManagerSimple::ChoosePass() { + auto& passes = GetPassInstances()->GetPasses(); + return passes[GetFuzzerContext()->RandomIndex(passes)].get(); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.h b/source/fuzz/pass_management/repeated_pass_manager_simple.h new file mode 100644 index 00000000..548f77b6 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_manager_simple.h @@ -0,0 +1,38 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ +#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ + +#include "source/fuzz/pass_management/repeated_pass_manager.h" + +namespace spvtools { +namespace fuzz { + +// Selects the next pass to run uniformly at random from the enabled repeated +// passes. Recommendations are not used. +class RepeatedPassManagerSimple : public RepeatedPassManager { + public: + RepeatedPassManagerSimple(FuzzerContext* fuzzer_context, + RepeatedPassInstances* pass_instances); + + ~RepeatedPassManagerSimple() override; + + FuzzerPass* ChoosePass() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_ diff --git a/source/fuzz/pass_management/repeated_pass_recommender.cpp b/source/fuzz/pass_management/repeated_pass_recommender.cpp new file mode 100644 index 00000000..c7789dc0 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +RepeatedPassRecommender::~RepeatedPassRecommender() = default; + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_recommender.h b/source/fuzz/pass_management/repeated_pass_recommender.h new file mode 100644 index 00000000..a6b13385 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender.h @@ -0,0 +1,42 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ +#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ + +#include <vector> + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Interface for influencing interactions between repeated fuzzer passes, by +// allowing hints as to which passes are recommended to be run after one +// another. +class RepeatedPassRecommender { + public: + virtual ~RepeatedPassRecommender(); + + // Given a reference to a repeated pass, |pass|, returns a sequence of + // repeated pass instances that might be worth running soon after having + // run |pass|. + virtual std::vector<FuzzerPass*> GetFuturePassRecommendations( + const FuzzerPass& pass) = 0; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_ diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp new file mode 100644 index 00000000..8c038078 --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp @@ -0,0 +1,334 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/pass_management/repeated_pass_recommender_standard.h" + +#include <numeric> + +namespace spvtools { +namespace fuzz { + +RepeatedPassRecommenderStandard::RepeatedPassRecommenderStandard( + RepeatedPassInstances* pass_instances, FuzzerContext* fuzzer_context) + : pass_instances_(pass_instances), fuzzer_context_(fuzzer_context) {} + +RepeatedPassRecommenderStandard::~RepeatedPassRecommenderStandard() = default; + +std::vector<FuzzerPass*> +RepeatedPassRecommenderStandard::GetFuturePassRecommendations( + const FuzzerPass& pass) { + if (&pass == pass_instances_->GetAddAccessChains()) { + // - Adding access chains means there is more scope for loading and storing + // - It could be worth making more access chains from the recently-added + // access chains + return RandomOrderAndNonNull({pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores(), + pass_instances_->GetAddAccessChains()}); + } + if (&pass == pass_instances_->GetAddBitInstructionSynonyms()) { + // - Adding bit instruction synonyms creates opportunities to apply synonyms + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetAddCompositeInserts()) { + // - Having added inserts we will have more vectors, so there is scope for + // vector shuffling + // - Adding inserts creates synonyms, which we should try to use + // - Vector inserts can be made dynamic + return RandomOrderAndNonNull( + {pass_instances_->GetAddVectorShuffleInstructions(), + pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetMakeVectorOperationsDynamic()}); + } + if (&pass == pass_instances_->GetAddCompositeTypes()) { + // - More composite types gives more scope for constructing composites + return RandomOrderAndNonNull({pass_instances_->GetConstructComposites()}); + } + if (&pass == pass_instances_->GetAddCopyMemory()) { + // - Recently-added copy memories could be replace with load-store pairs + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()}); + } + if (&pass == pass_instances_->GetAddDeadBlocks()) { + // - Dead blocks are great for adding function calls + // - Dead blocks are also great for adding loads and stores + // - The guard associated with a dead block can be obfuscated + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores(), + pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddDeadBreaks()) { + // - The guard of the dead break is a good candidate for obfuscation + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddDeadContinues()) { + // - The guard of the dead continue is a good candidate for obfuscation + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()}); + } + if (&pass == pass_instances_->GetAddEquationInstructions()) { + // - Equation instructions can create synonyms, which we can apply + // - Equation instructions collaborate with one another to make synonyms, so + // having added some it is worth adding more + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetAddEquationInstructions()}); + } + if (&pass == pass_instances_->GetAddFunctionCalls()) { + // - Called functions can be inlined + // - Irrelevant ids are created, so they can be replaced + return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddGlobalVariables()) { + // - New globals provide new possibilities for making access chains + // - We can load from and store to new globals + return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores()}); + } + if (&pass == pass_instances_->GetAddImageSampleUnusedComponents()) { + // - This introduces an unused component whose id is irrelevant and can be + // replaced + return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddLoads()) { + // - Loads might end up with corresponding stores, so that pairs can be + // replaced with memory copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetAddLocalVariables()) { + // - New locals provide new possibilities for making access chains + // - We can load from and store to new locals + return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(), + pass_instances_->GetAddLoads(), + pass_instances_->GetAddStores()}); + } + if (&pass == pass_instances_->GetAddLoopPreheaders()) { + // - The loop preheader provides more scope for duplicating regions and + // outlining functions. + return RandomOrderAndNonNull( + {pass_instances_->GetDuplicateRegionsWithSelections(), + pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) { + // - New synonyms can be applied + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetAddOpPhiSynonyms()) { + // - New synonyms can be applied + // - If OpPhi synonyms are introduced for blocks with dead predecessors, the + // values consumed from dead predecessors can be replaced + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()}); + } + if (&pass == pass_instances_->GetAddParameters()) { + // - We might be able to create interesting synonyms of new parameters. + // - This introduces irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetAddSynonyms(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddRelaxedDecorations()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetAddStores()) { + // - Stores might end up with corresponding loads, so that pairs can be + // replaced with memory copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetAddSynonyms()) { + // - New synonyms can be applied + // - Synonym instructions use constants, which can be obfuscated + // - Synonym instructions use irrelevant ids, which can be replaced + // - Synonym instructions introduce addition/subtraction, which can be + // replaced with carrying/extended versions + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetObfuscateConstants(), + pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetAddVectorShuffleInstructions()) { + // - Vector shuffles create synonyms that can be applied + // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806) Extract + // from composites. + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetApplyIdSynonyms()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetConstructComposites()) { + // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806): Extract + // from composites. + return RandomOrderAndNonNull({}); + } + if (&pass == pass_instances_->GetCopyObjects()) { + // - Object copies create synonyms that can be applied + // - OpCopyObject can be replaced with a store/load pair + return RandomOrderAndNonNull( + {pass_instances_->GetApplyIdSynonyms(), + pass_instances_->GetReplaceCopyObjectsWithStoresLoads()}); + } + if (&pass == pass_instances_->GetDonateModules()) { + // - New functions in the module can be called + // - Donated dead functions produce irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) { + // - Parts of duplicated regions can be outlined + return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetFlattenConditionalBranches()) { + // - Parts of flattened selections can be outlined + // - The flattening transformation introduces constants and irrelevant ids + // for enclosing hard-to-flatten operations; these can be obfuscated or + // replaced + return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants(), + pass_instances_->GetOutlineFunctions(), + pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetInlineFunctions()) { + // - Parts of inlined functions can be outlined again + return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()}); + } + if (&pass == pass_instances_->GetInvertComparisonOperators()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetMakeVectorOperationsDynamic()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetMergeBlocks()) { + // - Having merged some blocks it may be interesting to split them in a + // different way + return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()}); + } + if (&pass == pass_instances_->GetMutatePointers()) { + // - This creates irrelevant ids, which can be replaced + return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()}); + } + if (&pass == pass_instances_->GetObfuscateConstants()) { + // - No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetOutlineFunctions()) { + // - This creates more functions, which can be called + // - Inlining the function for the region that was outlined might also be + // fruitful; it will be inlined in a different form + return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(), + pass_instances_->GetInlineFunctions()}); + } + if (&pass == pass_instances_->GetPermuteBlocks()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPermuteFunctionParameters()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPermuteInstructions()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPropagateInstructionsUp()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetPushIdsThroughVariables()) { + // - This pass creates synonyms, so it is worth applying them + return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()}); + } + if (&pass == pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceCopyObjectsWithStoresLoads()) { + // - We may end up with load/store pairs that could be used to create memory + // copies + return RandomOrderAndNonNull( + {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()}); + } + if (&pass == pass_instances_->GetReplaceIrrelevantIds()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceLinearAlgebraInstructions()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceLoadsStoresWithCopyMemories()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceOpSelectsWithConditionalBranches()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceParameterWithGlobal()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetReplaceParamsWithStruct()) { + // No obvious follow-on passes + return {}; + } + if (&pass == pass_instances_->GetSplitBlocks()) { + // - More blocks means more chances for adding dead breaks/continues, and + // for adding dead blocks + return RandomOrderAndNonNull({pass_instances_->GetAddDeadBreaks(), + pass_instances_->GetAddDeadContinues(), + pass_instances_->GetAddDeadBlocks()}); + } + if (&pass == pass_instances_->GetSwapBranchConditionalOperands()) { + // No obvious follow-on passes + return {}; + } + assert(false && "Unreachable: every fuzzer pass should be dealt with."); + return {}; +} + +std::vector<FuzzerPass*> RepeatedPassRecommenderStandard::RandomOrderAndNonNull( + const std::vector<FuzzerPass*>& passes) { + std::vector<uint32_t> indices(passes.size()); + std::iota(indices.begin(), indices.end(), 0); + std::vector<FuzzerPass*> result; + while (!indices.empty()) { + FuzzerPass* maybe_pass = + passes[fuzzer_context_->RemoveAtRandomIndex(&indices)]; + if (maybe_pass != nullptr && + fuzzer_context_->ChoosePercentage( + fuzzer_context_ + ->GetChanceOfAcceptingRepeatedPassRecommendation())) { + result.push_back(maybe_pass); + } + } + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.h b/source/fuzz/pass_management/repeated_pass_recommender_standard.h new file mode 100644 index 00000000..293b8e0f --- /dev/null +++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.h @@ -0,0 +1,50 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_ +#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_ + +#include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/pass_management/repeated_pass_instances.h" +#include "source/fuzz/pass_management/repeated_pass_recommender.h" + +namespace spvtools { +namespace fuzz { + +// A manually-crafter recommender of repeated passes, designed based on +// knowledge of how the various fuzzer passes work and speculation as to how +// they might interact in interesting ways. +class RepeatedPassRecommenderStandard : public RepeatedPassRecommender { + public: + RepeatedPassRecommenderStandard(RepeatedPassInstances* pass_instances, + FuzzerContext* fuzzer_context); + + ~RepeatedPassRecommenderStandard(); + + std::vector<FuzzerPass*> GetFuturePassRecommendations( + const FuzzerPass& pass) override; + + private: + std::vector<FuzzerPass*> RandomOrderAndNonNull( + const std::vector<FuzzerPass*>& passes); + + RepeatedPassInstances* pass_instances_; + + FuzzerContext* fuzzer_context_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_ diff --git a/source/fuzz/protobufs/spirvfuzz_protobufs.h b/source/fuzz/protobufs/spirvfuzz_protobufs.h index 26b8672f..eb8cb145 100644 --- a/source/fuzz/protobufs/spirvfuzz_protobufs.h +++ b/source/fuzz/protobufs/spirvfuzz_protobufs.h @@ -24,6 +24,7 @@ #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wshadow" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 5c1eada0..0c2d2a9b 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -306,6 +306,81 @@ message AccessChainClampingInfo { } +message SideEffectWrapperInfo { + // When flattening a conditional branch, it is necessary to enclose + // instructions that have side effects inside conditionals, so that + // they are only executed if the condition holds. Otherwise, there + // might be unintended changes in memory, or crashes that would not + // originally happen. + // For example, the instruction %id = OpLoad %type %ptr, found in + // the true branch of the conditional, will be enclosed in a new + // conditional (assuming that the block containing it can be split + // around it) as follows: + // + // [previous instructions in the block] + // OpSelectionMerge %merge_block_id None + // OpBranchConditional %cond %execute_block_id %alternative_block_id + // %execute_block_id = OpLabel + // %actual_result_id = OpLoad %type %ptr + // OpBranch %merge_block_id + // %alternative_block_id = OpLabel + // %placeholder_result_id = OpCopyObject %type %value_to_copy_id + // OpBranch %merge_block_id + // %merge_block_id = OpLabel + // %id = OpPhi %type %actual_result_id %execute_block_id %placeholder_result_id %alternative_block_id + // [following instructions from the original block] + // + // If the instruction does not have a result id, this is simplified. + // For example, OpStore %ptr %value, found in the true branch of a + // conditional, is enclosed as follows: + // + // [previous instructions in the block] + // OpSelectionMerge %merge_block None + // OpBranchConditional %cond %execute_block_id %merge_block_id + // %execute_block_id = OpLabel + // OpStore %ptr %value + // OpBranch %merge_block_id + // %merge_block_id = OpLabel + // [following instructions from the original block] + // + // The same happens if the instruction is found in the false branch + // of the conditional being flattened, except that the label ids in + // the OpBranchConditional are swapped. + + + // An instruction descriptor for identifying the instruction to be + // enclosed inside a conditional. An instruction descriptor is + // necessary because the instruction might not have a result id. + InstructionDescriptor instruction = 1; + + // A fresh id for the new merge block. + uint32 merge_block_id = 2; + + // A fresh id for the new block where the actual instruction is + // executed. + uint32 execute_block_id = 3; + + // The following fields are only needed if the original instruction has a + // result id. They can be set to 0 if not needed. + + // A fresh id for the result id of the instruction (the original + // one is used by the OpPhi instruction). + uint32 actual_result_id = 4; + + // A fresh id for the new block where the placeholder instruction + // is placed. + uint32 alternative_block_id = 5; + + // A fresh id for the placeholder instruction. + uint32 placeholder_result_id = 6; + + // An id present in the module, available to use at this point in + // the program and with the same type as the original instruction, + // that can be used to create a placeholder OpCopyObject + // instruction. + uint32 value_to_copy_id = 7; +} + message LoopLimiterInfo { // Structure capturing the information required to manipulate a loop limiter @@ -353,7 +428,7 @@ message Transformation { TransformationAddTypeInt add_type_int = 7; TransformationAddDeadBreak add_dead_break = 8; TransformationReplaceBooleanConstantWithConstantBinary - replace_boolean_constant_with_constant_binary = 9; + replace_boolean_constant_with_constant_binary = 9; TransformationAddTypePointer add_type_pointer = 10; TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11; TransformationAddDeadContinue add_dead_continue = 12; @@ -404,6 +479,25 @@ message Transformation { TransformationAddSynonym add_synonym = 57; TransformationAddRelaxedDecoration add_relaxed_decoration = 58; TransformationReplaceParamsWithStruct replace_params_with_struct = 59; + TransformationReplaceCopyObjectWithStoreLoad replace_copy_object_with_store_load = 60; + TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61; + TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62; + TransformationAddLoopPreheader add_loop_preheader = 63; + TransformationMoveInstructionDown move_instruction_down = 64; + TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65; + TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66; + TransformationPropagateInstructionUp propagate_instruction_up = 67; + TransformationCompositeInsert composite_insert = 68; + TransformationInlineFunction inline_function = 69; + TransformationAddOpPhiSynonym add_opphi_synonym = 70; + TransformationMutatePointer mutate_pointer = 71; + TransformationReplaceIrrelevantId replace_irrelevant_id = 72; + TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73; + TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74; + TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75; + TransformationFlattenConditionalBranch flatten_conditional_branch = 76; + TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77; + TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78; // Add additional option using the next available number. } } @@ -414,6 +508,13 @@ message TransformationAccessChain { // Adds an access chain instruction based on a given pointer and indices. + // When accessing a struct, the corresponding indices must be 32-bit integer constants. + // For any other composite, the indices can be any 32-bit integer, and the transformation + // adds two instructions for each such index to clamp it to the bound, as follows: + // + // %t1 = OpULessThanEqual %bool %index %bound_minus_one + // %t2 = OpSelect %int_type %t1 %index %bound_minus_one + // Result id for the access chain uint32 fresh_id = 1; @@ -427,6 +528,37 @@ message TransformationAccessChain { // OpAccessChain instruction should be inserted InstructionDescriptor instruction_to_insert_before = 4; + // Additional fresh ids, required to clamp index variables. A pair is needed + // for each access to a non-struct composite. + repeated UInt32Pair fresh_ids_for_clamping = 5; + +} + +message TransformationAddBitInstructionSynonym { + + // A transformation that adds synonyms for bit instructions by evaluating + // each bit with the corresponding operation. There is a SPIR-V code example in the + // header file of the transformation class that can help understand the transformation. + + // This transformation is only applicable if the described instruction has one of the following opcodes. + // Supported: + // OpBitwiseOr + // OpBitwiseXor + // OpBitwiseAnd + // To be supported in the future: + // OpShiftRightLogical + // OpShiftRightArithmetic + // OpShiftLeftLogical + // OpNot + // OpBitReverse + // OpBitCount + + // The bit instruction result id. + uint32 instruction_result_id = 1; + + // The fresh ids required to apply the transformation. + repeated uint32 fresh_ids = 2; + } message TransformationAddConstantBoolean { @@ -688,6 +820,125 @@ message TransformationAddLocalVariable { } +message TransformationAddLoopPreheader { + + // A transformation that adds a loop preheader block before the given loop header. + + // The id of the loop header block + uint32 loop_header_block = 1; + + // A fresh id for the preheader block + uint32 fresh_id = 2; + + // Fresh ids for splitting the OpPhi instructions in the header. + // A new OpPhi instruction in the preheader is needed for each OpPhi instruction in the header, + // if the header has more than one predecessor outside of the loop. + // This allows turning instructions of the form: + // + // %loop_header_block = OpLabel + // %id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id %val3 %backedge_block_id + // + // into: + // %fresh_id = OpLabel + // %phi_id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id + // OpBranch %header_id + // %loop_header_block = OpLabel + // %id1 = OpPhi %type %phi_id1 %fresh_id %val3 %backedge_block_id + repeated uint32 phi_id = 3; + +} + +message TransformationAddLoopToCreateIntConstantSynonym { + // A transformation that uses a loop to create a synonym for an integer + // constant C (scalar or vector) using an initial value I, a step value S and + // a number of iterations N such that C = I - N * S. For each iteration, S is + // added to the total. + // The loop can be made up of one or two blocks, and it is inserted before a + // block with a single predecessor. In the one-block case, it is of the form: + // + // %loop_id = OpLabel + // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id + // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id + // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id + // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1 + // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id + // OpLoopMerge %block_after_loop_id %loop_id %none + // OpBranchConditional %cond_id %loop_id %block_after_loop_id + // + // A new OpPhi instruction is then added to %block_after_loop_id, as follows: + // + // %block_after_loop_id = OpLabel + // %syn_id = OpPhi %type_of_I %eventual_syn_id %loop_id + // + // This can be translated, assuming that N > 0, to: + // int syn = I; + // for (int ctr = 0; ctr < N; ctr++) syn = syn - S; + // + // All existing OpPhi instructions in %block_after_loop_id are also updated + // to reflect the fact that its predecessor is now %loop_id. + + // The following are existing ids. + + // The id of the integer constant C that we want a synonym of. + uint32 constant_id = 1; + + // The id of the initial value integer constant I. + uint32 initial_val_id = 2; + + // The id of the step value integer constant S. + uint32 step_val_id = 3; + + // The id of the integer scalar constant, its value being the number of + // iterations N. + uint32 num_iterations_id = 4; + + // The label id of the block before which the loop must be inserted. + uint32 block_after_loop_id = 5; + + + // The following are fresh ids. + + // A fresh id for the synonym. + uint32 syn_id = 6; + + // A fresh id for the label of the loop, + uint32 loop_id = 7; + + // A fresh id for the counter. + uint32 ctr_id = 8; + + // A fresh id taking the value I - S * ctr at the ctr-th iteration. + uint32 temp_id = 9; + + // A fresh id taking the value I - S * (ctr + 1) at the ctr-th iteration, and + // thus I - S * N at the last iteration. + uint32 eventual_syn_id = 10; + + // A fresh id for the incremented counter. + uint32 incremented_ctr_id = 11; + + // A fresh id for the loop condition. + uint32 cond_id = 12; + + // The instructions in the loop can also be laid out in two basic blocks, as follows: + // + // %loop_id = OpLabel + // %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id + // %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id + // OpLoopMerge %block_after_loop_id %additional_block_id None + // OpBranch %additional_block_id + // + // %additional_block_id = OpLabel + // %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id + // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1 + // %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id + // OpBranchConditional %cond_id %loop_id %block_after_loop_id + + // A fresh id for the additional block. If this is 0, it means that only one + // block is to be created. + uint32 additional_block_id = 13; +} + message TransformationAddNoContractionDecoration { // Applies OpDecorate NoContraction to the given result id @@ -697,6 +948,24 @@ message TransformationAddNoContractionDecoration { } +message TransformationAddOpPhiSynonym { + + // Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n) + // and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous. + // The instruction will be of the form: + // %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n + // and fresh_id will be recorded as being synonymous with all the other ids. + + // Label id of the block + uint32 block_id = 1; + + // Pairs (pred_i, id_i) + repeated UInt32Pair pred_to_id = 2; + + // Fresh id for the new instruction + uint32 fresh_id = 3; +} + message TransformationAddParameter { // Adds a new parameter into the function. @@ -707,15 +976,17 @@ message TransformationAddParameter { // Fresh id for a new parameter. uint32 parameter_fresh_id = 2; - // Result id of the instruction, used to initializer new parameter - // in function calls. Type id of that instruction is the type id of the parameter. - // It may not be OpTypeVoid. - uint32 initializer_id = 3; + // Type id for a new parameter. + uint32 parameter_type_id = 3; + + // A map that maps from the OpFunctionCall id to the id that will be passed as the new + // parameter at that call site. It must have the same type as that of the new parameter. + repeated UInt32Pair call_parameter_ids = 4; // A fresh id for a new function type. This might not be used // if a required function type already exists or if we can change // the old function type. - uint32 function_type_fresh_id = 4; + uint32 function_type_fresh_id = 5; } @@ -972,6 +1243,29 @@ message TransformationCompositeExtract { } +message TransformationCompositeInsert { + + // A transformation that adds an instruction OpCompositeInsert which creates + // a new composite from an existing composite, with an element inserted. + + // A descriptor for an instruction before which the new instruction + // OpCompositeInsert should be inserted. + InstructionDescriptor instruction_to_insert_before = 1; + + // Result id of the inserted OpCompositeInsert instruction. + uint32 fresh_id = 2; + + // Id of the composite used as the basis for the insertion. + uint32 composite_id = 3; + + // Id of the object to be inserted. + uint32 object_id = 4; + + // Indices that indicate which part of the composite should be inserted into. + repeated uint32 index = 5; + +} + message TransformationComputeDataSynonymFactClosure { // A transformation that impacts the fact manager only, forcing a computation @@ -985,6 +1279,41 @@ message TransformationComputeDataSynonymFactClosure { } +message TransformationDuplicateRegionWithSelection { + + // A transformation that inserts a conditional statement with a boolean expression + // of arbitrary value and duplicates a given single-entry, single-exit region, so + // that it is present in each conditional branch and will be executed regardless + // of which branch will be taken. + + // Fresh id for a label of the new entry block. + uint32 new_entry_fresh_id = 1; + + // Id for a boolean expression. + uint32 condition_id = 2; + + // Fresh id for a label of the merge block of the conditional. + uint32 merge_label_fresh_id = 3; + + // Block id of the entry block of the original region. + uint32 entry_block_id = 4; + + // Block id of the exit block of the original region. + uint32 exit_block_id = 5; + + // Map that maps from a label in the original region to the corresponding label + // in the duplicated region. + repeated UInt32Pair original_label_to_duplicate_label = 6; + + // Map that maps from a result id in the original region to the corresponding + // result id in the duplicated region. + repeated UInt32Pair original_id_to_duplicate_id = 7; + + // Map that maps from a result id in the original region to the result id of the + // corresponding OpPhi instruction. + repeated UInt32Pair original_id_to_phi_id = 8; +} + message TransformationEquationInstruction { // A transformation that adds an instruction to the module that defines an @@ -1007,6 +1336,63 @@ message TransformationEquationInstruction { } +message TransformationFlattenConditionalBranch { + + // A transformation that takes a selection construct with a header + // containing an OpBranchConditional instruction and flattens it. + // For example, something of the form: + // + // %1 = OpLabel + // [header instructions] + // OpSelectionMerge %4 None + // OpBranchConditional %cond %2 %3 + // %2 = OpLabel + // [true branch instructions] + // OpBranch %4 + // %3 = OpLabel + // [false branch instructions] + // OpBranch %4 + // %4 = OpLabel + // ... + // + // becomes: + // + // %1 = OpLabel + // [header instructions] + // OpBranch %2 + // %2 = OpLabel + // [true branch instructions] + // OpBranch %3 + // %3 = OpLabel + // [false branch instructions] + // OpBranch %4 + // %4 = OpLabel + // ... + // + // If all of the instructions in the true or false branches have + // no side effects, this is semantics-preserving. + // Side-effecting instructions will instead be enclosed by smaller + // conditionals. For more details, look at the definition for the + // SideEffectWrapperInfo message. + // + // Nested conditionals or loops are not supported. The false branch + // could also be executed before the true branch, depending on the + // |true_branch_first| field. + + // The label id of the header block + uint32 header_block_id = 1; + + // A boolean field deciding the order in which the original branches + // will be laid out: the true branch will be laid out first iff this + // field is true. + bool true_branch_first = 2; + + // A list of instructions with side effects, which must be enclosed + // inside smaller conditionals before flattening the main one, and + // the corresponding fresh ids and module ids needed. + repeated SideEffectWrapperInfo side_effect_wrapper_info = 3; +} + message TransformationFunctionCall { // A transformation that introduces an OpFunctionCall instruction. The call @@ -1030,6 +1416,20 @@ message TransformationFunctionCall { } +message TransformationInlineFunction { + + // This transformation inlines a function by mapping the function instructions to fresh ids. + + // Result id of the function call instruction. + uint32 function_call_id = 1; + + // For each result id defined by the called function, + // this map provides an associated fresh id that can + // be used in the inlined version of the function call. + repeated UInt32Pair result_id_map = 2; + +} + message TransformationInvertComparisonOperator { // For some instruction with result id |operator_id| that @@ -1062,6 +1462,21 @@ message TransformationLoad { } +message TransformationMakeVectorOperationDynamic { + + // A transformation that replaces the OpCompositeExtract and OpCompositeInsert + // instructions with the OpVectorExtractDynamic and OpVectorInsertDynamic instructions. + + // The composite instruction result id. + uint32 instruction_result_id = 1; + + // The OpCompositeExtract/Insert instructions accept integer literals as indices to the composite object. + // However, the OpVectorInsert/ExtractDynamic instructions require its single index to be an integer instruction. + // This is the result id of the integer instruction. + uint32 constant_index_id = 2; + +} + message TransformationMergeBlocks { // A transformation that merges a block with its predecessor. @@ -1081,6 +1496,31 @@ message TransformationMoveBlockDown { uint32 block_id = 1; } +message TransformationMoveInstructionDown { + + // Swaps |instruction| with the next instruction in the block. + + // The instruction to move down. + InstructionDescriptor instruction = 1; + +} + +message TransformationMutatePointer { + + // Backs up value of the pointer, writes into the pointer and + // restores the original value. + + // Result id of the pointer instruction to mutate. + uint32 pointer_id = 1; + + // Fresh id for the OpLoad instruction. + uint32 fresh_id = 2; + + // Instruction to insert backup, mutation and restoration code before. + InstructionDescriptor insert_before = 3; + +} + message TransformationOutlineFunction { // A transformation that outlines a single-entry single-exit region of a @@ -1169,6 +1609,26 @@ message TransformationPermutePhiOperands { } +message TransformationPropagateInstructionUp { + + // Propagates an instruction in the block into the block's predecessors. + // Concretely, this transformation clones some particular instruction from + // the |block_id| into every block's predecessor and replaces the original + // instruction with OpPhi. Take a look at the transformation class to learn + // more about how we choose what instruction to propagate. + + // Id of the block to propagate an instruction from. + uint32 block_id = 1; + + // A map from the id of some predecessor of the |block_id| to the fresh id. + // The map contains a fresh id for at least every predecessor of the |block_id|. + // The instruction is propagated by creating a number of clones - one clone for + // each predecessor. Fresh ids from this field are used as result ids of cloned + // instructions. + repeated UInt32Pair predecessor_id_to_fresh_id = 2; + +} + message TransformationPushIdThroughVariable { // A transformation that makes |value_synonym_id| and |value_id| to be @@ -1216,6 +1676,24 @@ message TransformationRecordSynonymousConstants { } +message TransformationReplaceAddSubMulWithCarryingExtended { + + // Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul + // with OpUMulExtended or OpSMulExtended (depending on the signedness + // of the operands) and stores the result into a |struct_fresh_id|. + // In the original instruction the result type id and the type ids of + // the operands must be the same. Then the transformation extracts + // the first element of the result into the original |result_id|. + // This value is the same as the result of the original instruction. + + // The fresh id of the intermediate result. + uint32 struct_fresh_id = 1; + + // The result id of the original instruction. + uint32 result_id = 2; + +} + message TransformationReplaceParameterWithGlobal { // Removes parameter with result id |parameter_id| from its function @@ -1274,6 +1752,39 @@ message TransformationReplaceConstantWithUniform { } +message TransformationReplaceCopyMemoryWithLoadStore { + + // A transformation that replaces instructions OpCopyMemory with loading + // the source variable to an intermediate value and storing this value into the + // target variable of the original OpCopyMemory instruction. + + // The intermediate value. + uint32 fresh_id = 1; + + // The instruction descriptor to OpCopyMemory. It is necessary, because + // OpCopyMemory doesn't have a result id. + InstructionDescriptor copy_memory_instruction_descriptor = 2; +} + +message TransformationReplaceCopyObjectWithStoreLoad { + + // A transformation that replaces instruction OpCopyObject with + // storing into a new variable and immediately loading from this + // variable to |result_id| of the original OpCopyObject instruction. + + // The result id of initial OpCopyObject instruction + uint32 copy_object_result_id = 1; + + // A fresh id for the variable to be stored to. + uint32 fresh_variable_id = 2; + + // The variable storage class (Function or Private). + uint32 variable_storage_class = 3; + + // Constant to initialize the variable with. + uint32 variable_initializer_id = 4; +} + message TransformationReplaceIdWithSynonym { // Replaces a use of an id with an id that is known to be synonymous, e.g. @@ -1287,6 +1798,17 @@ message TransformationReplaceIdWithSynonym { } +message TransformationReplaceIrrelevantId { + + // Replaces an irrelevant id with another id of the same type. + + // The id use that is to be replaced + IdUseDescriptor id_use_descriptor = 1; + + // The replacement id + uint32 replacement_id = 2; +} + message TransformationReplaceLinearAlgebraInstruction { // Replaces a linear algebra instruction with its @@ -1296,24 +1818,75 @@ message TransformationReplaceLinearAlgebraInstruction { repeated uint32 fresh_ids = 1; // A descriptor for a linear algebra instruction. - // This transformation is only applicable if the described instruction has one of the following opcodes. - // Supported: - // OpVectorTimesScalar - // OpMatrixTimesScalar - // OpVectorTimesMatrix - // OpMatrixTimesVector - // OpMatrixTimesMatrix - // OpDot - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354): - // Right now we only support certain operations. When this issue is addressed - // the supporting comments can be removed. - // To be supported in the future: - // OpTranspose - // OpOuterProduct InstructionDescriptor instruction_descriptor = 2; } +message TransformationReplaceLoadStoreWithCopyMemory { + // A transformation that takes a pair of instruction descriptors + // to OpLoad and OpStore that have the same intermediate value + // and replaces the OpStore with an equivalent OpCopyMemory. + + // The instruction descriptor to OpLoad + InstructionDescriptor load_instruction_descriptor = 1; + + // The instruction descriptor to OpStore + InstructionDescriptor store_instruction_descriptor = 2; +} + +message TransformationReplaceOpPhiIdFromDeadPredecessor { + + // Replaces one of the ids used by an OpPhi instruction, when + // the corresponding predecessor is dead, with any available id + // of the correct type. + + // The result id of the OpPhi instruction. + uint32 opphi_id = 1; + + // The label id of one of the predecessors of the block containing + // the OpPhi instruction, corresponding to the id that we want to + // replace. + uint32 pred_label_id = 2; + + // The id that, after the transformation, will be associated with + // the given predecessor. + uint32 replacement_id = 3; + +} + +message TransformationReplaceOpSelectWithConditionalBranch { + + // A transformation that takes an OpSelect instruction with a + // scalar boolean condition and replaces it with a conditional + // branch and an OpPhi instruction. + // The OpSelect instruction must be the first instruction in its + // block, which must have a unique predecessor. The block will + // become the merge block of a new construct, while its predecessor + // will become the header. + // Given the original OpSelect instruction: + // %id = OpSelect %type %cond %then %else + // The branching instruction of the header will be: + // OpBranchConditional %cond %true_block_id %false_block_id + // and the OpSelect instruction will be turned into: + // %id = OpPhi %type %then %true_block_id %else %false_block_id + // At most one of |true_block_id| and |false_block_id| can be zero. In + // that case, there will be no such block and all references to it + // will be replaced by %merge_block (where %merge_block is the + // block containing the OpSelect instruction). + + // The result id of the OpSelect instruction. + uint32 select_id = 1; + + // A fresh id for the new block that the predecessor of the block + // containing |select_id| will branch to if the condition holds. + uint32 true_block_id = 2; + + // A fresh id for the new block that the predecessor of the block + // containing |select_id| will branch to if the condition does not + // hold. + uint32 false_block_id = 3; +} + message TransformationReplaceParamsWithStruct { // Replaces parameters of the function with a struct containing @@ -1330,12 +1903,9 @@ message TransformationReplaceParamsWithStruct { uint32 fresh_parameter_id = 3; // Fresh ids for struct objects containing values of replaced parameters. - // This map contains a fresh id for at least every result id of a relevant + // This field contains a fresh id for at least every result id of a relevant // OpFunctionCall instruction. - // - // While maps are not fully deterministic, the way this map is used does not - // exhibit nondeterminism. Change to repeated Uint32Pair if this changes. - map<uint32, uint32> caller_id_to_fresh_composite_id = 4; + repeated UInt32Pair caller_id_to_fresh_composite_id = 4; } diff --git a/source/fuzz/pseudo_random_generator.cpp b/source/fuzz/pseudo_random_generator.cpp index 9643264a..51f0538b 100644 --- a/source/fuzz/pseudo_random_generator.cpp +++ b/source/fuzz/pseudo_random_generator.cpp @@ -25,8 +25,12 @@ PseudoRandomGenerator::~PseudoRandomGenerator() = default; uint32_t PseudoRandomGenerator::RandomUint32(uint32_t bound) { assert(bound > 0 && "Bound must be positive"); - return static_cast<uint32_t>( - std::uniform_int_distribution<>(0, bound - 1)(mt_)); + return std::uniform_int_distribution<uint32_t>(0, bound - 1)(mt_); +} + +uint64_t PseudoRandomGenerator::RandomUint64(uint64_t bound) { + assert(bound > 0 && "Bound must be positive"); + return std::uniform_int_distribution<uint64_t>(0, bound - 1)(mt_); } bool PseudoRandomGenerator::RandomBool() { diff --git a/source/fuzz/pseudo_random_generator.h b/source/fuzz/pseudo_random_generator.h index d2f52920..7c19833f 100644 --- a/source/fuzz/pseudo_random_generator.h +++ b/source/fuzz/pseudo_random_generator.h @@ -31,6 +31,8 @@ class PseudoRandomGenerator : public RandomGenerator { uint32_t RandomUint32(uint32_t bound) override; + uint64_t RandomUint64(uint64_t bound) override; + uint32_t RandomPercentage() override; bool RandomBool() override; diff --git a/source/fuzz/random_generator.h b/source/fuzz/random_generator.h index 9c467983..8c1baaab 100644 --- a/source/fuzz/random_generator.h +++ b/source/fuzz/random_generator.h @@ -29,6 +29,9 @@ class RandomGenerator { // Returns a value in the half-open interval [0, bound). virtual uint32_t RandomUint32(uint32_t bound) = 0; + // Returns a value in the half-open interval [0, bound). + virtual uint64_t RandomUint64(uint64_t bound) = 0; + // Returns a value in the closed interval [0, 100]. virtual uint32_t RandomPercentage() = 0; diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp index bc680d89..74c14dca 100644 --- a/source/fuzz/replayer.cpp +++ b/source/fuzz/replayer.cpp @@ -14,107 +14,102 @@ #include "source/fuzz/replayer.h" +#include <memory> #include <utility> -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/counter_overflow_id_source.h" +#include "source/fuzz/fact_manager/fact_manager.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" -#include "source/fuzz/transformation_add_constant_boolean.h" -#include "source/fuzz/transformation_add_constant_scalar.h" -#include "source/fuzz/transformation_add_dead_break.h" -#include "source/fuzz/transformation_add_type_boolean.h" -#include "source/fuzz/transformation_add_type_float.h" -#include "source/fuzz/transformation_add_type_int.h" -#include "source/fuzz/transformation_add_type_pointer.h" #include "source/fuzz/transformation_context.h" -#include "source/fuzz/transformation_move_block_down.h" -#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h" -#include "source/fuzz/transformation_replace_constant_with_uniform.h" -#include "source/fuzz/transformation_split_block.h" #include "source/opt/build_module.h" #include "source/util/make_unique.h" namespace spvtools { namespace fuzz { -struct Replayer::Impl { - Impl(spv_target_env env, bool validate, spv_validator_options options) - : target_env(env), - validate_during_replay(validate), - validator_options(options) {} - - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const bool validate_during_replay; // Controls whether the validator should - // be run after every replay step. - spv_validator_options validator_options; // Options to control - // validation -}; - -Replayer::Replayer(spv_target_env env, bool validate_during_replay, - spv_validator_options validator_options) - : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {} - -Replayer::~Replayer() = default; - -void Replayer::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); -} - -Replayer::ReplayerResultStatus Replayer::Run( +Replayer::Replayer( + spv_target_env target_env, MessageConsumer consumer, const std::vector<uint32_t>& binary_in, const protobufs::FactSequence& initial_facts, const protobufs::TransformationSequence& transformation_sequence_in, - uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const { + uint32_t num_transformations_to_apply, uint32_t first_overflow_id, + bool validate_during_replay, spv_validator_options validator_options) + : target_env_(target_env), + consumer_(std::move(consumer)), + binary_in_(binary_in), + initial_facts_(initial_facts), + transformation_sequence_in_(transformation_sequence_in), + num_transformations_to_apply_(num_transformations_to_apply), + first_overflow_id_(first_overflow_id), + validate_during_replay_(validate_during_replay), + validator_options_(validator_options) {} + +Replayer::~Replayer() = default; + +Replayer::ReplayerResult Replayer::Run() { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; - if (num_transformations_to_apply > - static_cast<uint32_t>(transformation_sequence_in.transformation_size())) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "The number of transformations to be replayed must not " - "exceed the size of the transformation sequence."); - return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested; + if (num_transformations_to_apply_ > + static_cast<uint32_t>( + transformation_sequence_in_.transformation_size())) { + consumer_(SPV_MSG_ERROR, nullptr, {}, + "The number of transformations to be replayed must not " + "exceed the size of the transformation sequence."); + return {Replayer::ReplayerResultStatus::kTooManyTransformationsRequested, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } - spvtools::SpirvTools tools(impl_->target_env); + spvtools::SpirvTools tools(target_env_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); - return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface; + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return {Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is invalid; stopping."); - return Replayer::ReplayerResultStatus::kInitialBinaryInvalid; + if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); + return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // Build the module from the input binary. - std::unique_ptr<opt::IRContext> ir_context = BuildModule( - impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size()); + std::unique_ptr<opt::IRContext> ir_context = + BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size()); assert(ir_context); // For replay validation, we track the last valid SPIR-V binary that was // observed. Initially this is the input binary. std::vector<uint32_t> last_valid_binary; - if (impl_->validate_during_replay) { - last_valid_binary = binary_in; + if (validate_during_replay_) { + last_valid_binary = binary_in_; } - FactManager fact_manager; - fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get()); - TransformationContext transformation_context(&fact_manager, - impl_->validator_options); + FactManager fact_manager(ir_context.get()); + fact_manager.AddFacts(consumer_, initial_facts_); + std::unique_ptr<TransformationContext> transformation_context = + first_overflow_id_ == 0 + ? MakeUnique<TransformationContext>(&fact_manager, validator_options_) + : MakeUnique<TransformationContext>( + &fact_manager, validator_options_, + MakeUnique<CounterOverflowIdSource>(first_overflow_id_)); + + // We track the largest id bound observed, to ensure that it only increases + // as transformations are applied. + uint32_t max_observed_id_bound = ir_context->module()->id_bound(); + (void)(max_observed_id_bound); // Keep release-mode compilers happy. + + protobufs::TransformationSequence transformation_sequence_out; // Consider the transformation proto messages in turn. uint32_t counter = 0; - for (auto& message : transformation_sequence_in.transformation()) { - if (counter >= num_transformations_to_apply) { + for (auto& message : transformation_sequence_in_.transformation()) { + if (counter >= num_transformations_to_apply_) { break; } counter++; @@ -123,23 +118,29 @@ Replayer::ReplayerResultStatus Replayer::Run( // Check whether the transformation can be applied. if (transformation->IsApplicable(ir_context.get(), - transformation_context)) { + *transformation_context)) { // The transformation is applicable, so apply it, and copy it to the // sequence of transformations that were applied. - transformation->Apply(ir_context.get(), &transformation_context); - *transformation_sequence_out->add_transformation() = message; + transformation->Apply(ir_context.get(), transformation_context.get()); + *transformation_sequence_out.add_transformation() = message; + + assert(ir_context->module()->id_bound() >= max_observed_id_bound && + "The module's id bound should only increase due to applying " + "transformations."); + max_observed_id_bound = ir_context->module()->id_bound(); - if (impl_->validate_during_replay) { + if (validate_during_replay_) { std::vector<uint32_t> binary_to_validate; ir_context->module()->ToBinary(&binary_to_validate, false); // Check whether the latest transformation led to a valid binary. if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Binary became invalid during replay (set a " - "breakpoint to inspect); stopping."); - return Replayer::ReplayerResultStatus::kReplayValidationFailure; + validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Binary became invalid during replay (set a " + "breakpoint to inspect); stopping."); + return {Replayer::ReplayerResultStatus::kReplayValidationFailure, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // The binary was valid, so it becomes the latest valid binary. @@ -149,8 +150,10 @@ Replayer::ReplayerResultStatus Replayer::Run( } // Write out the module as a binary. - ir_context->module()->ToBinary(binary_out, false); - return Replayer::ReplayerResultStatus::kComplete; + std::vector<uint32_t> binary_out; + ir_context->module()->ToBinary(&binary_out, false); + return {Replayer::ReplayerResultStatus::kComplete, std::move(binary_out), + std::move(transformation_sequence_out)}; } } // namespace fuzz diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h index d6395aaa..5bc62d9d 100644 --- a/source/fuzz/replayer.h +++ b/source/fuzz/replayer.h @@ -29,7 +29,7 @@ namespace fuzz { class Replayer { public: // Possible statuses that can result from running the replayer. - enum ReplayerResultStatus { + enum class ReplayerResultStatus { kComplete, kFailedToCreateSpirvToolsInterface, kInitialBinaryInvalid, @@ -37,8 +37,18 @@ class Replayer { kTooManyTransformationsRequested, }; - // Constructs a replayer from the given target environment. - Replayer(spv_target_env env, bool validate_during_replay, + struct ReplayerResult { + ReplayerResultStatus status; + std::vector<uint32_t> transformed_binary; + protobufs::TransformationSequence applied_transformations; + }; + + Replayer(spv_target_env target_env, MessageConsumer consumer, + const std::vector<uint32_t>& binary_in, + const protobufs::FactSequence& initial_facts, + const protobufs::TransformationSequence& transformation_sequence_in, + uint32_t num_transformations_to_apply, uint32_t first_overflow_id, + bool validate_during_replay, spv_validator_options validator_options); // Disables copy/move constructor/assignment operations. @@ -49,26 +59,50 @@ class Replayer { ~Replayer(); - // Sets the message consumer to the given |consumer|. The |consumer| will be - // invoked once for each message communicated from the library. - void SetMessageConsumer(MessageConsumer consumer); - - // Transforms |binary_in| to |binary_out| by attempting to apply the first - // |num_transformations_to_apply| transformations from - // |transformation_sequence_in|. Initial facts about the input binary and the - // context in which it will execute are provided via |initial_facts|. The - // transformations that were successfully applied are returned via - // |transformation_sequence_out|. - ReplayerResultStatus Run( - const std::vector<uint32_t>& binary_in, - const protobufs::FactSequence& initial_facts, - const protobufs::TransformationSequence& transformation_sequence_in, - uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const; + // Attempts to apply the first |num_transformations_to_apply_| transformations + // from |transformation_sequence_in_| to |binary_in_|. Initial facts about + // the input binary and the context in which it will execute are provided via + // |initial_facts_|. + // + // |first_overflow_id_| should be set to 0 if overflow ids are not available + // during replay. Otherwise |first_overflow_id_| must be larger than any id + // referred to in |binary_in_| or |transformation_sequence_in_|, and overflow + // ids will be available during replay starting from this value. + // + // On success, returns a successful result status together with the + // transformations that were successfully applied and the binary resulting + // from applying them. Otherwise, returns an appropriate result status + // together with an empty binary and empty transformation sequence. + ReplayerResult Run(); private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr<Impl> impl_; // Unique pointer to internal data. + // Target environment. + const spv_target_env target_env_; + + // Message consumer. + MessageConsumer consumer_; + + // The binary to which transformations are to be applied. + const std::vector<uint32_t>& binary_in_; + + // Initial facts known to hold in advance of applying any transformations. + const protobufs::FactSequence& initial_facts_; + + // The transformations to be replayed. + const protobufs::TransformationSequence& transformation_sequence_in_; + + // The number of transformations that should be replayed. + const uint32_t num_transformations_to_apply_; + + // Zero if overflow ids are not available, otherwise hold the value of the + // smallest id that may be used for overflow purposes. + const uint32_t first_overflow_id_; + + // Controls whether the validator should be run after every replay step. + const bool validate_during_replay_; + + // Options to control validation + spv_validator_options validator_options_; }; } // namespace fuzz diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp index 85a06fd8..a88a1eaf 100644 --- a/source/fuzz/shrinker.cpp +++ b/source/fuzz/shrinker.cpp @@ -18,6 +18,8 @@ #include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/replayer.h" +#include "source/opt/build_module.h" +#include "source/opt/ir_context.h" #include "source/spirv_fuzzer_options.h" #include "source/util/make_unique.h" @@ -59,85 +61,85 @@ protobufs::TransformationSequence RemoveChunk( } // namespace -struct Shrinker::Impl { - Impl(spv_target_env env, uint32_t limit, bool validate, - spv_validator_options options) - : target_env(env), - step_limit(limit), - validate_during_replay(validate), - validator_options(options) {} - - const spv_target_env target_env; // Target environment. - MessageConsumer consumer; // Message consumer. - const uint32_t step_limit; // Step limit for reductions. - const bool validate_during_replay; // Determines whether to check for - // validity during the replaying of - // transformations. - spv_validator_options validator_options; // Options to control validation. -}; - -Shrinker::Shrinker(spv_target_env env, uint32_t step_limit, - bool validate_during_replay, - spv_validator_options validator_options) - : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay, - validator_options)) {} - -Shrinker::~Shrinker() = default; - -void Shrinker::SetMessageConsumer(MessageConsumer c) { - impl_->consumer = std::move(c); -} - -Shrinker::ShrinkerResultStatus Shrinker::Run( +Shrinker::Shrinker( + spv_target_env target_env, MessageConsumer consumer, const std::vector<uint32_t>& binary_in, const protobufs::FactSequence& initial_facts, const protobufs::TransformationSequence& transformation_sequence_in, - const Shrinker::InterestingnessFunction& interestingness_function, - std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const { + const InterestingnessFunction& interestingness_function, + uint32_t step_limit, bool validate_during_replay, + spv_validator_options validator_options) + : target_env_(target_env), + consumer_(consumer), + binary_in_(binary_in), + initial_facts_(initial_facts), + transformation_sequence_in_(transformation_sequence_in), + interestingness_function_(interestingness_function), + step_limit_(step_limit), + validate_during_replay_(validate_during_replay), + validator_options_(validator_options) {} + +Shrinker::~Shrinker() = default; + +Shrinker::ShrinkerResult Shrinker::Run() { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; - spvtools::SpirvTools tools(impl_->target_env); + SpirvTools tools(target_env_); if (!tools.IsValid()) { - impl_->consumer(SPV_MSG_ERROR, nullptr, {}, - "Failed to create SPIRV-Tools interface; stopping."); - return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface; + consumer_(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return {Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } // Initial binary should be valid. - if (!tools.Validate(&binary_in[0], binary_in.size(), - impl_->validator_options)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is invalid; stopping."); - return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid; + if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); + return {Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } - std::vector<uint32_t> current_best_binary; - protobufs::TransformationSequence current_best_transformations; - - // Run a replay of the initial transformation sequence to (a) check that it - // succeeds, (b) get the binary that results from running these - // transformations, and (c) get the subsequence of the initial transformations - // that actually apply (in principle this could be a strict subsequence). - if (Replayer(impl_->target_env, impl_->validate_during_replay, - impl_->validator_options) - .Run(binary_in, initial_facts, transformation_sequence_in, - transformation_sequence_in.transformation_size(), - ¤t_best_binary, ¤t_best_transformations) != + // Run a replay of the initial transformation sequence to check that it + // succeeds. + auto initial_replay_result = + Replayer(target_env_, consumer_, binary_in_, initial_facts_, + transformation_sequence_in_, + static_cast<uint32_t>( + transformation_sequence_in_.transformation_size()), + /* No overflow ids */ 0, validate_during_replay_, + validator_options_) + .Run(); + if (initial_replay_result.status != Replayer::ReplayerResultStatus::kComplete) { - return ShrinkerResultStatus::kReplayFailed; + return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(), + protobufs::TransformationSequence()}; } + // Get the binary that results from running these transformations, and the + // subsequence of the initial transformations that actually apply (in + // principle this could be a strict subsequence). + std::vector<uint32_t> current_best_binary = + std::move(initial_replay_result.transformed_binary); + protobufs::TransformationSequence current_best_transformations = + std::move(initial_replay_result.applied_transformations); // Check that the binary produced by applying the initial transformations is // indeed interesting. - if (!interestingness_function(current_best_binary, 0)) { - impl_->consumer(SPV_MSG_INFO, nullptr, {}, - "Initial binary is not interesting; stopping."); - return ShrinkerResultStatus::kInitialBinaryNotInteresting; + if (!interestingness_function_(current_best_binary, 0)) { + consumer_(SPV_MSG_INFO, nullptr, {}, + "Initial binary is not interesting; stopping."); + return {ShrinkerResultStatus::kInitialBinaryNotInteresting, + std::vector<uint32_t>(), protobufs::TransformationSequence()}; } + // The largest id used by the module before any shrinking has been applied + // serves as the first id that can be used for overflow purposes. + const uint32_t first_overflow_id = GetIdBound(current_best_binary); + assert(first_overflow_id >= GetIdBound(binary_in_) && + "Applying transformations should only increase a module's id bound."); + uint32_t attempt = 0; // Keeps track of the number of shrink attempts that // have been tried, whether successful or not. @@ -151,7 +153,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // - reach the step limit, // - run out of transformations to remove, or // - cannot make the chunk size any smaller. - while (attempt < impl_->step_limit && + while (attempt < step_limit_ && !current_best_transformations.transformation().empty() && chunk_size > 0) { bool progress_this_round = @@ -181,40 +183,46 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( // |chunk_size|, using |chunk_index| to track which chunk to try removing // next. The loop exits early if we reach the shrinking step limit. for (int chunk_index = num_chunks - 1; - attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) { + attempt < step_limit_ && chunk_index >= 0; chunk_index--) { // Remove a chunk of transformations according to the current index and // chunk size. auto transformations_with_chunk_removed = - RemoveChunk(current_best_transformations, chunk_index, chunk_size); + RemoveChunk(current_best_transformations, + static_cast<uint32_t>(chunk_index), chunk_size); // Replay the smaller sequence of transformations to get a next binary and // transformation sequence. Note that the transformations arising from // replay might be even smaller than the transformations with the chunk // removed, because removing those transformations might make further // transformations inapplicable. - std::vector<uint32_t> next_binary; - protobufs::TransformationSequence next_transformation_sequence; - if (Replayer(impl_->target_env, impl_->validate_during_replay, - impl_->validator_options) - .Run(binary_in, initial_facts, transformations_with_chunk_removed, - transformations_with_chunk_removed.transformation_size(), - &next_binary, &next_transformation_sequence) != - Replayer::ReplayerResultStatus::kComplete) { + auto replay_result = + Replayer( + target_env_, consumer_, binary_in_, initial_facts_, + transformations_with_chunk_removed, + static_cast<uint32_t>( + transformations_with_chunk_removed.transformation_size()), + first_overflow_id, validate_during_replay_, validator_options_) + .Run(); + if (replay_result.status != Replayer::ReplayerResultStatus::kComplete) { // Replay should not fail; if it does, we need to abort shrinking. - return ShrinkerResultStatus::kReplayFailed; + return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(), + protobufs::TransformationSequence()}; } - assert(NumRemainingTransformations(next_transformation_sequence) >= - chunk_index * chunk_size && - "Removing this chunk of transformations should not have an effect " - "on earlier chunks."); + assert( + NumRemainingTransformations(replay_result.applied_transformations) >= + chunk_index * chunk_size && + "Removing this chunk of transformations should not have an effect " + "on earlier chunks."); - if (interestingness_function(next_binary, attempt)) { + if (interestingness_function_(replay_result.transformed_binary, + attempt)) { // If the binary arising from the smaller transformation sequence is // interesting, this becomes our current best binary and transformation // sequence. - current_best_binary = next_binary; - current_best_transformations = next_transformation_sequence; + current_best_binary = std::move(replay_result.transformed_binary); + current_best_transformations = + std::move(replay_result.applied_transformations); progress_this_round = true; } // Either way, this was a shrink attempt, so increment our count of shrink @@ -234,22 +242,32 @@ Shrinker::ShrinkerResultStatus Shrinker::Run( } } - // The output from the shrinker is the best binary we saw, and the - // transformations that led to it. - *binary_out = current_best_binary; - *transformation_sequence_out = current_best_transformations; - // Indicate whether shrinking completed or was truncated due to reaching the // step limit. - assert(attempt <= impl_->step_limit); - if (attempt == impl_->step_limit) { + // + // Either way, the output from the shrinker is the best binary we saw, and the + // transformations that led to it. + assert(attempt <= step_limit_); + if (attempt == step_limit_) { std::stringstream strstream; - strstream << "Shrinking did not complete; step limit " << impl_->step_limit + strstream << "Shrinking did not complete; step limit " << step_limit_ << " was reached."; - impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str()); - return Shrinker::ShrinkerResultStatus::kStepLimitReached; + consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str()); + return {Shrinker::ShrinkerResultStatus::kStepLimitReached, + std::move(current_best_binary), + std::move(current_best_transformations)}; } - return Shrinker::ShrinkerResultStatus::kComplete; + return {Shrinker::ShrinkerResultStatus::kComplete, + std::move(current_best_binary), + std::move(current_best_transformations)}; +} + +uint32_t Shrinker::GetIdBound(const std::vector<uint32_t>& binary) const { + // Build the module from the input binary. + std::unique_ptr<opt::IRContext> ir_context = + BuildModule(target_env_, consumer_, binary.data(), binary.size()); + assert(ir_context && "Error building module."); + return ir_context->module()->id_bound(); } } // namespace fuzz diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h index 17b15bf8..982a8431 100644 --- a/source/fuzz/shrinker.h +++ b/source/fuzz/shrinker.h @@ -30,7 +30,7 @@ namespace fuzz { class Shrinker { public: // Possible statuses that can result from running the shrinker. - enum ShrinkerResultStatus { + enum class ShrinkerResultStatus { kComplete, kFailedToCreateSpirvToolsInterface, kInitialBinaryInvalid, @@ -39,6 +39,12 @@ class Shrinker { kStepLimitReached, }; + struct ShrinkerResult { + ShrinkerResultStatus status; + std::vector<uint32_t> transformed_binary; + protobufs::TransformationSequence applied_transformations; + }; + // The type for a function that will take a binary, |binary|, and return true // if and only if the binary is deemed interesting. (The function also takes // an integer argument, |counter|, that will be incremented each time the @@ -49,8 +55,12 @@ class Shrinker { using InterestingnessFunction = std::function<bool( const std::vector<uint32_t>& binary, uint32_t counter)>; - // Constructs a shrinker from the given target environment. - Shrinker(spv_target_env env, uint32_t step_limit, bool validate_during_replay, + Shrinker(spv_target_env target_env, MessageConsumer consumer, + const std::vector<uint32_t>& binary_in, + const protobufs::FactSequence& initial_facts, + const protobufs::TransformationSequence& transformation_sequence_in, + const InterestingnessFunction& interestingness_function, + uint32_t step_limit, bool validate_during_replay, spv_validator_options validator_options); // Disables copy/move constructor/assignment operations. @@ -61,29 +71,54 @@ class Shrinker { ~Shrinker(); - // Sets the message consumer to the given |consumer|. The |consumer| will be - // invoked once for each message communicated from the library. - void SetMessageConsumer(MessageConsumer consumer); - - // Requires that when |transformation_sequence_in| is applied to |binary_in| - // with initial facts |initial_facts|, the resulting binary is interesting - // according to |interestingness_function|. + // Requires that when |transformation_sequence_in_| is applied to |binary_in_| + // with initial facts |initial_facts_|, the resulting binary is interesting + // according to |interestingness_function_|. + // + // If shrinking succeeded -- possibly terminating early due to reaching the + // shrinker's step limit -- an associated result status is returned together + // with a subsequence of |transformation_sequence_in_| that, when applied + // to |binary_in_| with initial facts |initial_facts_|, produces a binary + // that is also interesting according to |interestingness_function_|; this + // binary is also returned. // - // Produces, via |transformation_sequence_out|, a subsequence of - // |transformation_sequence_in| that, when applied with initial facts - // |initial_facts|, produces a binary (captured via |binary_out|) that is - // also interesting according to |interestingness_function|. - ShrinkerResultStatus Run( - const std::vector<uint32_t>& binary_in, - const protobufs::FactSequence& initial_facts, - const protobufs::TransformationSequence& transformation_sequence_in, - const InterestingnessFunction& interestingness_function, - std::vector<uint32_t>* binary_out, - protobufs::TransformationSequence* transformation_sequence_out) const; + // If shrinking failed for some reason, an appropriate result status is + // returned together with an empty binary and empty transformation sequence. + ShrinkerResult Run(); private: - struct Impl; // Opaque struct for holding internal data. - std::unique_ptr<Impl> impl_; // Unique pointer to internal data. + // Returns the id bound for the given SPIR-V binary, which is assumed to be + // valid. + uint32_t GetIdBound(const std::vector<uint32_t>& binary) const; + + // Target environment. + const spv_target_env target_env_; + + // Message consumer that will be invoked once for each message communicated + // from the library. + MessageConsumer consumer_; + + // The binary to which transformations are to be applied. + const std::vector<uint32_t>& binary_in_; + + // Initial facts known to hold in advance of applying any transformations. + const protobufs::FactSequence& initial_facts_; + + // The series of transformations to be shrunk. + const protobufs::TransformationSequence& transformation_sequence_in_; + + // Function that decides whether a given binary is interesting. + const InterestingnessFunction& interestingness_function_; + + // Step limit to decide when to terminate shrinking early. + const uint32_t step_limit_; + + // Determines whether to check for validity during the replaying of + // transformations. + const bool validate_during_replay_; + + // Options to control validation. + spv_validator_options validator_options_; }; } // namespace fuzz diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 0640f496..7301a894 100644 --- a/source/fuzz/transformation.cpp +++ b/source/fuzz/transformation.cpp @@ -18,6 +18,7 @@ #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_access_chain.h" +#include "source/fuzz/transformation_add_bit_instruction_synonym.h" #include "source/fuzz/transformation_add_constant_boolean.h" #include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_null.h" @@ -31,7 +32,10 @@ #include "source/fuzz/transformation_add_global_variable.h" #include "source/fuzz/transformation_add_image_sample_unused_components.h" #include "source/fuzz/transformation_add_local_variable.h" +#include "source/fuzz/transformation_add_loop_preheader.h" +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" +#include "source/fuzz/transformation_add_opphi_synonym.h" #include "source/fuzz/transformation_add_parameter.h" #include "source/fuzz/transformation_add_relaxed_decoration.h" #include "source/fuzz/transformation_add_spec_constant_op.h" @@ -48,22 +52,37 @@ #include "source/fuzz/transformation_adjust_branch_weights.h" #include "source/fuzz/transformation_composite_construct.h" #include "source/fuzz/transformation_composite_extract.h" +#include "source/fuzz/transformation_composite_insert.h" #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h" +#include "source/fuzz/transformation_duplicate_region_with_selection.h" #include "source/fuzz/transformation_equation_instruction.h" +#include "source/fuzz/transformation_flatten_conditional_branch.h" #include "source/fuzz/transformation_function_call.h" +#include "source/fuzz/transformation_inline_function.h" #include "source/fuzz/transformation_invert_comparison_operator.h" #include "source/fuzz/transformation_load.h" +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" #include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/transformation_move_block_down.h" +#include "source/fuzz/transformation_move_instruction_down.h" +#include "source/fuzz/transformation_mutate_pointer.h" #include "source/fuzz/transformation_outline_function.h" #include "source/fuzz/transformation_permute_function_parameters.h" #include "source/fuzz/transformation_permute_phi_operands.h" +#include "source/fuzz/transformation_propagate_instruction_up.h" #include "source/fuzz/transformation_push_id_through_variable.h" #include "source/fuzz/transformation_record_synonymous_constants.h" +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h" #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h" #include "source/fuzz/transformation_replace_constant_with_uniform.h" +#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h" +#include "source/fuzz/transformation_replace_copy_object_with_store_load.h" #include "source/fuzz/transformation_replace_id_with_synonym.h" +#include "source/fuzz/transformation_replace_irrelevant_id.h" #include "source/fuzz/transformation_replace_linear_algebra_instruction.h" +#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h" +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" +#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h" #include "source/fuzz/transformation_replace_parameter_with_global.h" #include "source/fuzz/transformation_replace_params_with_struct.h" #include "source/fuzz/transformation_set_function_control.h" @@ -88,6 +107,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage( switch (message.transformation_case()) { case protobufs::Transformation::TransformationCase::kAccessChain: return MakeUnique<TransformationAccessChain>(message.access_chain()); + case protobufs::Transformation::TransformationCase:: + kAddBitInstructionSynonym: + return MakeUnique<TransformationAddBitInstructionSynonym>( + message.add_bit_instruction_synonym()); case protobufs::Transformation::TransformationCase::kAddConstantBoolean: return MakeUnique<TransformationAddConstantBoolean>( message.add_constant_boolean()); @@ -124,10 +147,20 @@ std::unique_ptr<Transformation> Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kAddLocalVariable: return MakeUnique<TransformationAddLocalVariable>( message.add_local_variable()); + case protobufs::Transformation::TransformationCase::kAddLoopPreheader: + return MakeUnique<TransformationAddLoopPreheader>( + message.add_loop_preheader()); + case protobufs::Transformation::TransformationCase:: + kAddLoopToCreateIntConstantSynonym: + return MakeUnique<TransformationAddLoopToCreateIntConstantSynonym>( + message.add_loop_to_create_int_constant_synonym()); case protobufs::Transformation::TransformationCase:: kAddNoContractionDecoration: return MakeUnique<TransformationAddNoContractionDecoration>( message.add_no_contraction_decoration()); + case protobufs::Transformation::TransformationCase::kAddOpphiSynonym: + return MakeUnique<TransformationAddOpPhiSynonym>( + message.add_opphi_synonym()); case protobufs::Transformation::TransformationCase::kAddParameter: return MakeUnique<TransformationAddParameter>(message.add_parameter()); case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration: @@ -168,25 +201,48 @@ std::unique_ptr<Transformation> Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kCompositeExtract: return MakeUnique<TransformationCompositeExtract>( message.composite_extract()); + case protobufs::Transformation::TransformationCase::kCompositeInsert: + return MakeUnique<TransformationCompositeInsert>( + message.composite_insert()); case protobufs::Transformation::TransformationCase:: kComputeDataSynonymFactClosure: return MakeUnique<TransformationComputeDataSynonymFactClosure>( message.compute_data_synonym_fact_closure()); + case protobufs::Transformation::TransformationCase:: + kDuplicateRegionWithSelection: + return MakeUnique<TransformationDuplicateRegionWithSelection>( + message.duplicate_region_with_selection()); case protobufs::Transformation::TransformationCase::kEquationInstruction: return MakeUnique<TransformationEquationInstruction>( message.equation_instruction()); + case protobufs::Transformation::TransformationCase:: + kFlattenConditionalBranch: + return MakeUnique<TransformationFlattenConditionalBranch>( + message.flatten_conditional_branch()); case protobufs::Transformation::TransformationCase::kFunctionCall: return MakeUnique<TransformationFunctionCall>(message.function_call()); + case protobufs::Transformation::TransformationCase::kInlineFunction: + return MakeUnique<TransformationInlineFunction>( + message.inline_function()); case protobufs::Transformation::TransformationCase:: kInvertComparisonOperator: return MakeUnique<TransformationInvertComparisonOperator>( message.invert_comparison_operator()); case protobufs::Transformation::TransformationCase::kLoad: return MakeUnique<TransformationLoad>(message.load()); + case protobufs::Transformation::TransformationCase:: + kMakeVectorOperationDynamic: + return MakeUnique<TransformationMakeVectorOperationDynamic>( + message.make_vector_operation_dynamic()); case protobufs::Transformation::TransformationCase::kMergeBlocks: return MakeUnique<TransformationMergeBlocks>(message.merge_blocks()); case protobufs::Transformation::TransformationCase::kMoveBlockDown: return MakeUnique<TransformationMoveBlockDown>(message.move_block_down()); + case protobufs::Transformation::TransformationCase::kMoveInstructionDown: + return MakeUnique<TransformationMoveInstructionDown>( + message.move_instruction_down()); + case protobufs::Transformation::TransformationCase::kMutatePointer: + return MakeUnique<TransformationMutatePointer>(message.mutate_pointer()); case protobufs::Transformation::TransformationCase::kOutlineFunction: return MakeUnique<TransformationOutlineFunction>( message.outline_function()); @@ -197,6 +253,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kPermutePhiOperands: return MakeUnique<TransformationPermutePhiOperands>( message.permute_phi_operands()); + case protobufs::Transformation::TransformationCase::kPropagateInstructionUp: + return MakeUnique<TransformationPropagateInstructionUp>( + message.propagate_instruction_up()); case protobufs::Transformation::TransformationCase::kPushIdThroughVariable: return MakeUnique<TransformationPushIdThroughVariable>( message.push_id_through_variable()); @@ -205,9 +264,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage( return MakeUnique<TransformationRecordSynonymousConstants>( message.record_synonymous_constants()); case protobufs::Transformation::TransformationCase:: - kReplaceParameterWithGlobal: - return MakeUnique<TransformationReplaceParameterWithGlobal>( - message.replace_parameter_with_global()); + kReplaceAddSubMulWithCarryingExtended: + return MakeUnique<TransformationReplaceAddSubMulWithCarryingExtended>( + message.replace_add_sub_mul_with_carrying_extended()); case protobufs::Transformation::TransformationCase:: kReplaceBooleanConstantWithConstantBinary: return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>( @@ -216,17 +275,44 @@ std::unique_ptr<Transformation> Transformation::FromMessage( kReplaceConstantWithUniform: return MakeUnique<TransformationReplaceConstantWithUniform>( message.replace_constant_with_uniform()); + case protobufs::Transformation::TransformationCase:: + kReplaceCopyMemoryWithLoadStore: + return MakeUnique<TransformationReplaceCopyMemoryWithLoadStore>( + message.replace_copy_memory_with_load_store()); + case protobufs::Transformation::TransformationCase:: + kReplaceCopyObjectWithStoreLoad: + return MakeUnique<TransformationReplaceCopyObjectWithStoreLoad>( + message.replace_copy_object_with_store_load()); case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym: return MakeUnique<TransformationReplaceIdWithSynonym>( message.replace_id_with_synonym()); + case protobufs::Transformation::TransformationCase::kReplaceIrrelevantId: + return MakeUnique<TransformationReplaceIrrelevantId>( + message.replace_irrelevant_id()); case protobufs::Transformation::TransformationCase:: kReplaceLinearAlgebraInstruction: return MakeUnique<TransformationReplaceLinearAlgebraInstruction>( message.replace_linear_algebra_instruction()); case protobufs::Transformation::TransformationCase:: + kReplaceLoadStoreWithCopyMemory: + return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>( + message.replace_load_store_with_copy_memory()); + case protobufs::Transformation::TransformationCase:: + kReplaceOpselectWithConditionalBranch: + return MakeUnique<TransformationReplaceOpSelectWithConditionalBranch>( + message.replace_opselect_with_conditional_branch()); + case protobufs::Transformation::TransformationCase:: + kReplaceParameterWithGlobal: + return MakeUnique<TransformationReplaceParameterWithGlobal>( + message.replace_parameter_with_global()); + case protobufs::Transformation::TransformationCase:: kReplaceParamsWithStruct: return MakeUnique<TransformationReplaceParamsWithStruct>( message.replace_params_with_struct()); + case protobufs::Transformation::TransformationCase:: + kReplaceOpphiIdFromDeadPredecessor: + return MakeUnique<TransformationReplaceOpPhiIdFromDeadPredecessor>( + message.replace_opphi_id_from_dead_predecessor()); case protobufs::Transformation::TransformationCase::kSetFunctionControl: return MakeUnique<TransformationSetFunctionControl>( message.set_function_control()); diff --git a/source/fuzz/transformation_access_chain.cpp b/source/fuzz/transformation_access_chain.cpp index f805bab7..33668694 100644 --- a/source/fuzz/transformation_access_chain.cpp +++ b/source/fuzz/transformation_access_chain.cpp @@ -29,7 +29,8 @@ TransformationAccessChain::TransformationAccessChain( TransformationAccessChain::TransformationAccessChain( uint32_t fresh_id, uint32_t pointer_id, const std::vector<uint32_t>& index_id, - const protobufs::InstructionDescriptor& instruction_to_insert_before) { + const protobufs::InstructionDescriptor& instruction_to_insert_before, + const std::vector<std::pair<uint32_t, uint32_t>>& fresh_ids_for_clamping) { message_.set_fresh_id(fresh_id); message_.set_pointer_id(pointer_id); for (auto id : index_id) { @@ -37,12 +38,22 @@ TransformationAccessChain::TransformationAccessChain( } *message_.mutable_instruction_to_insert_before() = instruction_to_insert_before; + for (auto clamping_ids_pair : fresh_ids_for_clamping) { + protobufs::UInt32Pair pair; + pair.set_first(clamping_ids_pair.first); + pair.set_second(clamping_ids_pair.second); + *message_.add_fresh_ids_for_clamping() = pair; + } } bool TransformationAccessChain::IsApplicable( opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { - // The result id must be fresh - if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + // Keep track of the fresh ids used to make sure that they are distinct. + std::set<uint32_t> fresh_ids_used; + + // The result id must be fresh. + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.fresh_id(), ir_context, &fresh_ids_used)) { return false; } // The pointer id must exist and have a type. @@ -50,7 +61,7 @@ bool TransformationAccessChain::IsApplicable( if (!pointer || !pointer->type_id()) { return false; } - // The type must indeed be a pointer + // The type must indeed be a pointer. auto pointer_type = ir_context->get_def_use_mgr()->GetDef(pointer->type_id()); if (pointer_type->opcode() != SpvOpTypePointer) { return false; @@ -96,23 +107,86 @@ bool TransformationAccessChain::IsApplicable( // Start from the base type of the pointer. uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + int id_pairs_used = 0; + // Consider the given index ids in turn. for (auto index_id : message_.index_id()) { - // Try to get the integer value associated with this index is. The first - // component of the result will be false if the id did not correspond to an - // integer. Otherwise, the integer with which the id is associated is the - // second component. - std::pair<bool, uint32_t> maybe_index_value = - GetIndexValue(ir_context, index_id); - if (!maybe_index_value.first) { - // There was no integer: this index is no good. - return false; + // The index value will correspond to the value of the index if the object + // is a struct, otherwise the value 0 will be used. + uint32_t index_value; + + // Check whether the object is a struct. + if (ir_context->get_def_use_mgr()->GetDef(subobject_type_id)->opcode() == + SpvOpTypeStruct) { + // It is a struct: we need to retrieve the integer value. + + bool successful; + std::tie(successful, index_value) = + GetIndexValue(ir_context, index_id, subobject_type_id); + + if (!successful) { + return false; + } + } else { + // It is not a struct: the index will need clamping. + + if (message_.fresh_ids_for_clamping().size() <= id_pairs_used) { + // We don't have enough ids + return false; + } + + // Get two new ids to use and update the amount used. + protobufs::UInt32Pair fresh_ids = + message_.fresh_ids_for_clamping()[id_pairs_used++]; + + // Valid ids need to have been given + if (fresh_ids.first() == 0 || fresh_ids.second() == 0) { + return false; + } + + // Check that the ids are actually fresh and not already used by this + // transformation. + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + fresh_ids.first(), ir_context, &fresh_ids_used) || + !CheckIdIsFreshAndNotUsedByThisTransformation( + fresh_ids.second(), ir_context, &fresh_ids_used)) { + return false; + } + + if (!ValidIndexToComposite(ir_context, index_id, subobject_type_id)) { + return false; + } + + // Perform the clamping using the fresh ids at our disposal. + auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id); + + uint32_t bound = fuzzerutil::GetBoundForCompositeIndex( + *ir_context->get_def_use_mgr()->GetDef(subobject_type_id), + ir_context); + + // The module must have an integer constant of value bound-1 of the same + // type as the index. + if (!fuzzerutil::MaybeGetIntegerConstantFromValueAndType( + ir_context, bound - 1, index_instruction->type_id())) { + return false; + } + + // The module must have the definition of bool type to make a comparison. + if (!fuzzerutil::MaybeGetBoolType(ir_context)) { + return false; + } + + // The index is not necessarily a constant, so we may not know its value. + // We can use index 0 because the components of a non-struct composite + // all have the same type, and index 0 is always in bounds. + index_value = 0; } + // Try to walk down the type using this index. This will yield 0 if the // type is not a composite or the index is out of bounds, and the id of // the next type otherwise. subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( - ir_context, subobject_type_id, maybe_index_value.second); + ir_context, subobject_type_id, index_value); if (!subobject_type_id) { // Either the type was not a composite (so that too many indices were // provided), or the index was out of bounds. @@ -152,25 +226,105 @@ void TransformationAccessChain::Apply( ir_context->get_def_use_mgr()->GetDef(message_.pointer_id())->type_id()); uint32_t subobject_type_id = pointer_type->GetSingleWordInOperand(1); + uint32_t id_pairs_used = 0; + // Go through the index ids in turn. for (auto index_id : message_.index_id()) { - // Add the index id to the operands. - operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}}); - // Get the integer value associated with the index id. - uint32_t index_value = GetIndexValue(ir_context, index_id).second; + uint32_t index_value; + + // Actual id to be used in the instruction: the original id + // or the clamped one. + uint32_t new_index_id; + + // Check whether the object is a struct. + if (ir_context->get_def_use_mgr()->GetDef(subobject_type_id)->opcode() == + SpvOpTypeStruct) { + // It is a struct: we need to retrieve the integer value. + + index_value = + GetIndexValue(ir_context, index_id, subobject_type_id).second; + + new_index_id = index_id; + + } else { + // It is not a struct: the index will need clamping. + + // Get two new ids to use and update the amount used. + protobufs::UInt32Pair fresh_ids = + message_.fresh_ids_for_clamping()[id_pairs_used++]; + + // Perform the clamping using the fresh ids at our disposal. + // The module will not be changed if |add_clamping_instructions| is not + // set. + auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id); + + uint32_t bound = fuzzerutil::GetBoundForCompositeIndex( + *ir_context->get_def_use_mgr()->GetDef(subobject_type_id), + ir_context); + + auto bound_minus_one_id = + fuzzerutil::MaybeGetIntegerConstantFromValueAndType( + ir_context, bound - 1, index_instruction->type_id()); + + assert(bound_minus_one_id && + "A constant of value bound - 1 and the same type as the index " + "must exist as a precondition."); + + uint32_t bool_type_id = fuzzerutil::MaybeGetBoolType(ir_context); + + assert(bool_type_id && + "An OpTypeBool instruction must exist as a precondition."); + + auto int_type_inst = + ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id()); + + // Clamp the integer and add the corresponding instructions in the module + // if |add_clamping_instructions| is set. + auto instruction_to_insert_before = + FindInstruction(message_.instruction_to_insert_before(), ir_context); + + // Compare the index with the bound via an instruction of the form: + // %fresh_ids.first = OpULessThanEqual %bool %int_id %bound_minus_one. + fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.first()); + instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpULessThanEqual, bool_type_id, fresh_ids.first(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}}, + {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}))); + + // Select the index if in-bounds, otherwise one less than the bound: + // %fresh_ids.second = OpSelect %int_type %fresh_ids.first %int_id + // %bound_minus_one + fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.second()); + instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpSelect, int_type_inst->result_id(), + fresh_ids.second(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {fresh_ids.first()}}, + {SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}}, + {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}))); + + new_index_id = fresh_ids.second(); + + index_value = 0; + } + + // Add the correct index id to the operands. + operands.push_back({SPV_OPERAND_TYPE_ID, {new_index_id}}); + // Walk to the next type in the composite object using this index. subobject_type_id = fuzzerutil::WalkOneCompositeTypeIndex( ir_context, subobject_type_id, index_value); } - // The access chain's result type is a pointer to the composite component that - // was reached after following all indices. The storage class is that of the - // original pointer. + // The access chain's result type is a pointer to the composite component + // that was reached after following all indices. The storage class is that + // of the original pointer. uint32_t result_type = fuzzerutil::MaybeGetPointerType( ir_context, subobject_type_id, static_cast<SpvStorageClass>(pointer_type->GetSingleWordInOperand(0))); - // Add the access chain instruction to the module, and update the module's id - // bound. + // Add the access chain instruction to the module, and update the module's + // id bound. fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); FindInstruction(message_.instruction_to_insert_before(), ir_context) ->InsertBefore(MakeUnique<opt::Instruction>( @@ -180,8 +334,8 @@ void TransformationAccessChain::Apply( // Conservatively invalidate all analyses. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); - // If the base pointer's pointee value was irrelevant, the same is true of the - // pointee value of the result of this access chain. + // If the base pointer's pointee value was irrelevant, the same is true of + // the pointee value of the result of this access chain. if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant( message_.pointer_id())) { transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( @@ -196,21 +350,61 @@ protobufs::Transformation TransformationAccessChain::ToMessage() const { } std::pair<bool, uint32_t> TransformationAccessChain::GetIndexValue( - opt::IRContext* ir_context, uint32_t index_id) const { + opt::IRContext* ir_context, uint32_t index_id, + uint32_t object_type_id) const { + if (!ValidIndexToComposite(ir_context, index_id, object_type_id)) { + return {false, 0}; + } auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id); - if (!index_instruction || !spvOpcodeIsConstant(index_instruction->opcode())) { - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3179) We could - // allow non-constant indices when looking up non-structs, using clamping - // to ensure they are in-bounds. + + uint32_t bound = fuzzerutil::GetBoundForCompositeIndex( + *ir_context->get_def_use_mgr()->GetDef(object_type_id), ir_context); + + // The index must be a constant + if (!spvOpcodeIsConstant(index_instruction->opcode())) { + return {false, 0}; + } + + // The index must be in bounds. + uint32_t value = index_instruction->GetSingleWordInOperand(0); + + if (value >= bound) { return {false, 0}; } + + return {true, value}; +} + +bool TransformationAccessChain::ValidIndexToComposite( + opt::IRContext* ir_context, uint32_t index_id, uint32_t object_type_id) { + auto object_type_def = ir_context->get_def_use_mgr()->GetDef(object_type_id); + // The object being indexed must be a composite. + if (!spvOpcodeIsComposite(object_type_def->opcode())) { + return false; + } + + // Get the defining instruction of the index. + auto index_instruction = ir_context->get_def_use_mgr()->GetDef(index_id); + if (!index_instruction) { + return false; + } + + // The index type must be 32-bit integer. auto index_type = ir_context->get_def_use_mgr()->GetDef(index_instruction->type_id()); if (index_type->opcode() != SpvOpTypeInt || index_type->GetSingleWordInOperand(0) != 32) { - return {false, 0}; + return false; + } + + // If the object being traversed is a struct, the id must correspond to an + // in-bound constant. + if (object_type_def->opcode() == SpvOpTypeStruct) { + if (!spvOpcodeIsConstant(index_instruction->opcode())) { + return false; + } } - return {true, index_instruction->GetSingleWordInOperand(0)}; + return true; } } // namespace fuzz diff --git a/source/fuzz/transformation_access_chain.h b/source/fuzz/transformation_access_chain.h index 9306a596..db5b8e67 100644 --- a/source/fuzz/transformation_access_chain.h +++ b/source/fuzz/transformation_access_chain.h @@ -33,20 +33,28 @@ class TransformationAccessChain : public Transformation { TransformationAccessChain( uint32_t fresh_id, uint32_t pointer_id, const std::vector<uint32_t>& index_id, - const protobufs::InstructionDescriptor& instruction_to_insert_before); + const protobufs::InstructionDescriptor& instruction_to_insert_before, + const std::vector<std::pair<uint32_t, uint32_t>>& fresh_ids_for_clamping = + {}); - // - |message_.fresh_id| must be fresh + // - |message_.fresh_id| must be fresh. // - |message_.instruction_to_insert_before| must identify an instruction - // before which it is legitimate to insert an OpAccessChain instruction + // before which it is legitimate to insert an OpAccessChain instruction. // - |message_.pointer_id| must be a result id with pointer type that is // available (according to dominance rules) at the insertion point. - // - The pointer must not be OpConstantNull or OpUndef - // - |message_.index_id| must be a sequence of ids of 32-bit integer constants + // - The pointer must not be OpConstantNull or OpUndef. + // - |message_.index_id| must be a sequence of ids of 32-bit integers // such that it is possible to walk the pointee type of - // |message_.pointer_id| using these indices, remaining in-bounds. + // |message_.pointer_id| using these indices. + // - All indices used to access a struct must be OpConstant. + // - The indices used to index non-struct composites will be clamped to be + // in bound. Enough fresh ids must be given in + // |message_.fresh_id_for_clamping| to perform clamping (2 for + // each index accessing a non-struct). This requires the bool type and + // a constant of value (bound - 1) to be declared in the module. // - If type t is the final type reached by walking these indices, the module // must include an instruction "OpTypePointer SC %t" where SC is the storage - // class associated with |message_.pointer_id| + // class associated with |message_.pointer_id|. bool IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const override; @@ -58,6 +66,9 @@ class TransformationAccessChain : public Transformation { // the indices in |message_.index_id|, and with the same storage class as // |message_.pointer_id|. // + // For each of the indices traversing non-struct composites, two clamping + // instructions are added using ids in |message_.fresh_id_for_clamping|. + // // If the fact manager in |transformation_context| reports that // |message_.pointer_id| has an irrelevant pointee value, then the fact that // |message_.fresh_id| (the result of the access chain) also has an irrelevant @@ -68,11 +79,21 @@ class TransformationAccessChain : public Transformation { protobufs::Transformation ToMessage() const override; private: - // Returns {false, 0} if |index_id| does not correspond to a 32-bit integer - // constant. Otherwise, returns {true, value}, where value is the value of - // the 32-bit integer constant to which |index_id| corresponds. + // Returns {false, 0} in each of the following cases: + // - |index_id| does not correspond to a 32-bit integer constant + // - the object being indexed is not a composite type + // - the constant at |index_id| is out of bounds. + // Otherwise, returns {true, value}, where value is the value of the constant + // at |index_id|. std::pair<bool, uint32_t> GetIndexValue(opt::IRContext* ir_context, - uint32_t index_id) const; + uint32_t index_id, + uint32_t object_type_id) const; + + // Returns true if |index_id| corresponds, in the given context, to a 32-bit + // integer which can be used to index an object of the type specified by + // |object_type_id|. Returns false otherwise. + static bool ValidIndexToComposite(opt::IRContext* ir_context, + uint32_t index_id, uint32_t object_type_id); protobufs::TransformationAccessChain message_; }; diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.cpp b/source/fuzz/transformation_add_bit_instruction_synonym.cpp new file mode 100644 index 00000000..50d03e71 --- /dev/null +++ b/source/fuzz/transformation_add_bit_instruction_synonym.cpp @@ -0,0 +1,228 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_bit_instruction_synonym.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym( + const spvtools::fuzz::protobufs::TransformationAddBitInstructionSynonym& + message) + : message_(message) {} + +TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym( + const uint32_t instruction_result_id, + const std::vector<uint32_t>& fresh_ids) { + message_.set_instruction_result_id(instruction_result_id); + *message_.mutable_fresh_ids() = + google::protobuf::RepeatedField<google::protobuf::uint32>( + fresh_ids.begin(), fresh_ids.end()); +} + +bool TransformationAddBitInstructionSynonym::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557): + // Right now we only support certain operations. When this issue is addressed + // the following conditional can use the function |spvOpcodeIsBit|. + // |instruction| must be defined and must be a supported bit instruction. + if (!instruction || (instruction->opcode() != SpvOpBitwiseOr && + instruction->opcode() != SpvOpBitwiseXor && + instruction->opcode() != SpvOpBitwiseAnd)) { + return false; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3792): + // Right now, only integer operands are supported. + if (ir_context->get_type_mgr()->GetType(instruction->type_id())->AsVector()) { + return false; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3791): + // This condition could be relaxed if the index exists as another integer + // type. + // All bit indexes must be defined as 32-bit unsigned integers. + uint32_t width = ir_context->get_type_mgr() + ->GetType(instruction->type_id()) + ->AsInteger() + ->width(); + for (uint32_t i = 0; i < width; i++) { + if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, + {i}, 32, false, false)) { + return false; + } + } + + // |message_.fresh_ids.size| must have the exact number of fresh ids required + // to apply the transformation. + if (static_cast<uint32_t>(message_.fresh_ids().size()) != + GetRequiredFreshIdCount(ir_context, instruction)) { + return false; + } + + // All ids in |message_.fresh_ids| must be fresh. + for (uint32_t fresh_id : message_.fresh_ids()) { + if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) { + return false; + } + } + + return true; +} + +void TransformationAddBitInstructionSynonym::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + auto bit_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + + switch (bit_instruction->opcode()) { + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + AddBitwiseSynonym(ir_context, transformation_context, bit_instruction); + break; + default: + assert(false && "Should be unreachable."); + break; + } + + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationAddBitInstructionSynonym::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_add_bit_instruction_synonym() = message_; + return result; +} + +uint32_t TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount( + opt::IRContext* ir_context, opt::Instruction* bit_instruction) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557): + // Right now, only certain operations are supported. + switch (bit_instruction->opcode()) { + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + return 4 * ir_context->get_type_mgr() + ->GetType(bit_instruction->type_id()) + ->AsInteger() + ->width() - + 1; + default: + assert(false && "Unsupported bit instruction."); + return 0; + } +} + +void TransformationAddBitInstructionSynonym::AddBitwiseSynonym( + opt::IRContext* ir_context, TransformationContext* transformation_context, + opt::Instruction* bit_instruction) const { + // Fresh id iterator. + auto fresh_id = message_.fresh_ids().begin(); + + // |width| is the bit width of operands (8, 16, 32 or 64). + const uint32_t width = ir_context->get_type_mgr() + ->GetType(bit_instruction->type_id()) + ->AsInteger() + ->width(); + + // |count| is the number of bits to be extracted and inserted at a time. + const uint32_t count = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {1}, 32, false, false); + + // |bitwise_ids| is the collection of OpBiwise* instructions that evaluate a + // pair of extracted bits. Those ids will be used to insert the result bits. + std::vector<uint32_t> bitwise_ids(width); + + for (uint32_t i = 0; i < width; i++) { + // |offset| is the current bit index. + uint32_t offset = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {i}, 32, false, false); + + // |bit_extract_ids| are the two extracted bits from the operands. + std::vector<uint32_t> bit_extract_ids; + + // Extracts the i-th bit from operands. + for (auto operand = bit_instruction->begin() + 2; + operand != bit_instruction->end(); operand++) { + auto bit_extract = + opt::Instruction(ir_context, SpvOpBitFieldUExtract, + bit_instruction->type_id(), *fresh_id++, + {{SPV_OPERAND_TYPE_ID, operand->words}, + {SPV_OPERAND_TYPE_ID, {offset}}, + {SPV_OPERAND_TYPE_ID, {count}}}); + bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_extract)); + fuzzerutil::UpdateModuleIdBound(ir_context, bit_extract.result_id()); + bit_extract_ids.push_back(bit_extract.result_id()); + } + + // Applies |bit_instruction| to the pair of extracted bits. + auto bitwise = + opt::Instruction(ir_context, bit_instruction->opcode(), + bit_instruction->type_id(), *fresh_id++, + {{SPV_OPERAND_TYPE_ID, {bit_extract_ids[0]}}, + {SPV_OPERAND_TYPE_ID, {bit_extract_ids[1]}}}); + bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bitwise)); + fuzzerutil::UpdateModuleIdBound(ir_context, bitwise.result_id()); + bitwise_ids[i] = bitwise.result_id(); + } + + // The first two ids in |bitwise_ids| are used to insert the first two bits of + // the result. + uint32_t offset = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {1}, 32, false, false); + auto bit_insert = opt::Instruction(ir_context, SpvOpBitFieldInsert, + bit_instruction->type_id(), *fresh_id++, + {{SPV_OPERAND_TYPE_ID, {bitwise_ids[0]}}, + {SPV_OPERAND_TYPE_ID, {bitwise_ids[1]}}, + {SPV_OPERAND_TYPE_ID, {offset}}, + {SPV_OPERAND_TYPE_ID, {count}}}); + bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert)); + fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id()); + + // Inserts the remaining bits. + for (uint32_t i = 2; i < width; i++) { + offset = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {i}, 32, false, false); + bit_insert = + opt::Instruction(ir_context, SpvOpBitFieldInsert, + bit_instruction->type_id(), *fresh_id++, + {{SPV_OPERAND_TYPE_ID, {bit_insert.result_id()}}, + {SPV_OPERAND_TYPE_ID, {bitwise_ids[i]}}, + {SPV_OPERAND_TYPE_ID, {offset}}, + {SPV_OPERAND_TYPE_ID, {count}}}); + bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert)); + fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id()); + } + + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Adds the fact that the last |bit_insert| instruction is synonymous of + // |bit_instruction|. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(bit_insert.result_id(), {}), + MakeDataDescriptor(bit_instruction->result_id(), {})); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.h b/source/fuzz/transformation_add_bit_instruction_synonym.h new file mode 100644 index 00000000..1baf959a --- /dev/null +++ b/source/fuzz/transformation_add_bit_instruction_synonym.h @@ -0,0 +1,141 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_SYNONYM_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_SYNONYM_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +// clang-format off +// SPIR-V code to help understand the transformation. +// +// ---------------------------------------------------------------------------------------------------------------- +// | Reference shader | Variant shader | +// ---------------------------------------------------------------------------------------------------------------- +// | OpCapability Shader | OpCapability Shader | +// | OpCapability Int8 | OpCapability Int8 | +// | %1 = OpExtInstImport "GLSL.std.450" | %1 = OpExtInstImport "GLSL.std.450" | +// | OpMemoryModel Logical GLSL450 | OpMemoryModel Logical GLSL450 | +// | OpEntryPoint Vertex %7 "main" | OpEntryPoint Vertex %7 "main" | +// | | | +// | ; Types | ; Types | +// | %2 = OpTypeInt 8 0 | %2 = OpTypeInt 8 0 | +// | %3 = OpTypeVoid | %3 = OpTypeVoid | +// | %4 = OpTypeFunction %3 | %4 = OpTypeFunction %3 | +// | | | +// | ; Constants | ; Constants | +// | %5 = OpConstant %2 0 | %5 = OpConstant %2 0 | +// | %6 = OpConstant %2 1 | %6 = OpConstant %2 1 | +// | | %10 = OpConstant %2 2 | +// | ; main function | %11 = OpConstant %2 3 | +// | %7 = OpFunction %3 None %4 | %12 = OpConstant %2 4 | +// | %8 = OpLabel | %13 = OpConstant %2 5 | +// | %9 = OpBitwiseOr %2 %5 %6 ; bit instruction | %14 = OpConstant %2 6 | +// | OpReturn | %15 = OpConstant %2 7 | +// | OpFunctionEnd | | +// | | ; main function | +// | | %7 = OpFunction %3 None %4 | +// | | %8 = OpLabel | +// | | | +// | | %16 = OpBitFieldUExtract %2 %5 %5 %6 ; extracts bit 0 from %5 | +// | | %17 = OpBitFieldUExtract %2 %6 %5 %6 ; extracts bit 0 from %6 | +// | | %18 = OpBitwiseOr %2 %16 %17 | +// | | | +// | | %19 = OpBitFieldUExtract %2 %5 %6 %6 ; extracts bit 1 from %5 | +// | | %20 = OpBitFieldUExtract %2 %6 %6 %6 ; extracts bit 1 from %6 | +// | | %21 = OpBitwiseOr %2 %19 %20 | +// | | | +// | | %22 = OpBitFieldUExtract %2 %5 %10 %6 ; extracts bit 2 from %5 | +// | | %23 = OpBitFieldUExtract %2 %6 %10 %6 ; extracts bit 2 from %6 | +// | | %24 = OpBitwiseOr %2 %22 %23 | +// | | | +// | | %25 = OpBitFieldUExtract %2 %5 %11 %6 ; extracts bit 3 from %5 | +// | | %26 = OpBitFieldUExtract %2 %6 %11 %6 ; extracts bit 3 from %6 | +// | | %27 = OpBitwiseOr %2 %25 %26 | +// | | | +// | | %28 = OpBitFieldUExtract %2 %5 %12 %6 ; extracts bit 4 from %5 | +// | | %29 = OpBitFieldUExtract %2 %6 %12 %6 ; extracts bit 4 from %6 | +// | | %30 = OpBitwiseOr %2 %28 %29 | +// | | | +// | | %31 = OpBitFieldUExtract %2 %5 %13 %6 ; extracts bit 5 from %5 | +// | | %32 = OpBitFieldUExtract %2 %6 %13 %6 ; extracts bit 5 from %6 | +// | | %33 = OpBitwiseOr %2 %31 %32 | +// | | | +// | | %34 = OpBitFieldUExtract %2 %5 %14 %6 ; extracts bit 6 from %5 | +// | | %35 = OpBitFieldUExtract %2 %6 %14 %6 ; extracts bit 6 from %6 | +// | | %36 = OpBitwiseOr %2 %34 %35 | +// | | | +// | | %37 = OpBitFieldUExtract %2 %5 %15 %6 ; extracts bit 7 from %5 | +// | | %38 = OpBitFieldUExtract %2 %6 %15 %6 ; extracts bit 7 from %6 | +// | | %39 = OpBitwiseOr %2 %37 %38 | +// | | | +// | | %40 = OpBitFieldInsert %2 %18 %21 %6 %6 ; inserts bit 1 | +// | | %41 = OpBitFieldInsert %2 %40 %24 %10 %6 ; inserts bit 2 | +// | | %42 = OpBitFieldInsert %2 %41 %27 %11 %6 ; inserts bit 3 | +// | | %43 = OpBitFieldInsert %2 %42 %30 %12 %6 ; inserts bit 4 | +// | | %44 = OpBitFieldInsert %2 %43 %33 %13 %6 ; inserts bit 5 | +// | | %45 = OpBitFieldInsert %2 %44 %36 %14 %6 ; inserts bit 6 | +// | | %46 = OpBitFieldInsert %2 %45 %39 %15 %6 ; inserts bit 7 | +// | | %9 = OpBitwiseOr %2 %5 %6 ; bit instruction | +// | | OpReturn | +// | | OpFunctionEnd | +// ---------------------------------------------------------------------------------------------------------------- +// +// After the transformation, %9 and %46 will be synonymous. +// clang-format on +class TransformationAddBitInstructionSynonym : public Transformation { + public: + explicit TransformationAddBitInstructionSynonym( + const protobufs::TransformationAddBitInstructionSynonym& message); + + TransformationAddBitInstructionSynonym( + const uint32_t instruction_result_id, + const std::vector<uint32_t>& fresh_ids); + + // - |message_.instruction_result_id| must be a bit instruction. + // - |message_.fresh_ids| must be fresh ids needed to apply the + // transformation. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds a bit instruction synonym. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns the number of fresh ids required to apply the transformation. + static uint32_t GetRequiredFreshIdCount(opt::IRContext* ir_context, + opt::Instruction* bit_instruction); + + private: + protobufs::TransformationAddBitInstructionSynonym message_; + + // Adds an OpBitwise* synonym. + void AddBitwiseSynonym(opt::IRContext* ir_context, + TransformationContext* transformation_context, + opt::Instruction* bitwise_instruction) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_SYNONYM_H_ diff --git a/source/fuzz/transformation_add_constant_boolean.h b/source/fuzz/transformation_add_constant_boolean.h index bc9a2949..d2f9e9ad 100644 --- a/source/fuzz/transformation_add_constant_boolean.h +++ b/source/fuzz/transformation_add_constant_boolean.h @@ -29,7 +29,7 @@ class TransformationAddConstantBoolean : public Transformation { const protobufs::TransformationAddConstantBoolean& message); TransformationAddConstantBoolean(uint32_t fresh_id, bool is_true, - bool is_irrelevant = false); + bool is_irrelevant); // - |message_.fresh_id| must not be used by the module. // - The module must already contain OpTypeBool. diff --git a/source/fuzz/transformation_add_constant_composite.h b/source/fuzz/transformation_add_constant_composite.h index 470bda73..2dddbfb5 100644 --- a/source/fuzz/transformation_add_constant_composite.h +++ b/source/fuzz/transformation_add_constant_composite.h @@ -32,7 +32,7 @@ class TransformationAddConstantComposite : public Transformation { TransformationAddConstantComposite( uint32_t fresh_id, uint32_t type_id, - const std::vector<uint32_t>& constituent_ids, bool is_irrelevant = false); + const std::vector<uint32_t>& constituent_ids, bool is_irrelevant); // - |message_.fresh_id| must be a fresh id // - |message_.type_id| must be the id of a composite type diff --git a/source/fuzz/transformation_add_constant_scalar.cpp b/source/fuzz/transformation_add_constant_scalar.cpp index 98bfbf04..e0b4dfba 100644 --- a/source/fuzz/transformation_add_constant_scalar.cpp +++ b/source/fuzz/transformation_add_constant_scalar.cpp @@ -64,13 +64,12 @@ bool TransformationAddConstantScalar::IsApplicable( void TransformationAddConstantScalar::Apply( opt::IRContext* ir_context, TransformationContext* transformation_context) const { - opt::Instruction::OperandList operand_list; - for (auto word : message_.word()) { - operand_list.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {word}}); - } ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>( ir_context, SpvOpConstant, message_.type_id(), message_.fresh_id(), - operand_list)); + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_LITERAL_INTEGER, + std::vector<uint32_t>(message_.word().begin(), + message_.word().end())}}))); fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); diff --git a/source/fuzz/transformation_add_constant_scalar.h b/source/fuzz/transformation_add_constant_scalar.h index 4155563a..06a77fc7 100644 --- a/source/fuzz/transformation_add_constant_scalar.h +++ b/source/fuzz/transformation_add_constant_scalar.h @@ -32,7 +32,7 @@ class TransformationAddConstantScalar : public Transformation { TransformationAddConstantScalar(uint32_t fresh_id, uint32_t type_id, const std::vector<uint32_t>& words, - bool is_irrelevant = false); + bool is_irrelevant); // - |message_.fresh_id| must not be used by the module // - |message_.type_id| must be the id of a floating-point or integer type diff --git a/source/fuzz/transformation_add_copy_memory.cpp b/source/fuzz/transformation_add_copy_memory.cpp index e9c401d7..4d963641 100644 --- a/source/fuzz/transformation_add_copy_memory.cpp +++ b/source/fuzz/transformation_add_copy_memory.cpp @@ -132,6 +132,10 @@ void TransformationAddCopyMemory::Apply( fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + // Make sure our changes are analyzed + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); + // Even though the copy memory instruction will - at least temporarily - lead // to the destination and source pointers referring to identical values, this // fact is not guaranteed to hold throughout execution of the SPIR-V code @@ -140,10 +144,6 @@ void TransformationAddCopyMemory::Apply( // pointer can be used freely by other fuzzer passes. transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( message_.fresh_id()); - - // Make sure our changes are analyzed - ir_context->InvalidateAnalysesExceptFor( - opt::IRContext::Analysis::kAnalysisNone); } protobufs::Transformation TransformationAddCopyMemory::ToMessage() const { diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp index fe4931d3..9b50ea79 100644 --- a/source/fuzz/transformation_add_dead_block.cpp +++ b/source/fuzz/transformation_add_dead_block.cpp @@ -42,7 +42,7 @@ bool TransformationAddDeadBlock::IsApplicable( // First, we check that a constant with the same value as // |message_.condition_value| is present. if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context, - message_.condition_value())) { + message_.condition_value(), false)) { // The required constant is not present, so the transformation cannot be // applied. return false; @@ -78,6 +78,27 @@ bool TransformationAddDeadBlock::IsApplicable( return false; } + // |existing_block| must be reachable. + opt::DominatorAnalysis* dominator_analysis = + ir_context->GetDominatorAnalysis(existing_block->GetParent()); + if (!dominator_analysis->IsReachable(existing_block->id())) { + return false; + } + + assert(existing_block->id() != successor_block_id && + "|existing_block| must be different from |successor_block_id|"); + + // Even though we know |successor_block_id| is not a merge block, it might + // still have multiple predecessors because divergent control flow is allowed + // to converge early (before the merge block). In this case, when we create + // the selection construct, its header |existing_block| will not dominate the + // merge block |successor_block_id|, which is invalid. Thus, |existing_block| + // must dominate |successor_block_id|. + if (!dominator_analysis->Dominates(existing_block->id(), + successor_block_id)) { + return false; + } + return true; } @@ -94,7 +115,7 @@ void TransformationAddDeadBlock::Apply( // Get the id of the boolean value that will be used as the branch condition. auto bool_id = fuzzerutil::MaybeGetBoolConstant( - ir_context, *transformation_context, message_.condition_value()); + ir_context, *transformation_context, message_.condition_value(), false); // Make a new block that unconditionally branches to the original successor // block. diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp index 11b2f1fb..284174ad 100644 --- a/source/fuzz/transformation_add_dead_break.cpp +++ b/source/fuzz/transformation_add_dead_break.cpp @@ -113,7 +113,8 @@ bool TransformationAddDeadBreak::IsApplicable( // First, we check that a constant with the same value as // |message_.break_condition_value| is present. if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context, - message_.break_condition_value())) { + message_.break_condition_value(), + false)) { // The required constant is not present, so the transformation cannot be // applied. return false; @@ -206,7 +207,7 @@ void TransformationAddDeadBreak::ApplyImpl( ir_context, ir_context->cfg()->block(message_.from_block()), ir_context->cfg()->block(message_.to_block()), fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context, - message_.break_condition_value()), + message_.break_condition_value(), false), message_.phi_id()); } diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp index 8200b99a..b5b7ae3f 100644 --- a/source/fuzz/transformation_add_dead_continue.cpp +++ b/source/fuzz/transformation_add_dead_continue.cpp @@ -39,7 +39,8 @@ bool TransformationAddDeadContinue::IsApplicable( // First, we check that a constant with the same value as // |message_.continue_condition_value| is present. if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context, - message_.continue_condition_value())) { + message_.continue_condition_value(), + false)) { // The required constant is not present, so the transformation cannot be // applied. return false; @@ -152,7 +153,8 @@ void TransformationAddDeadContinue::ApplyImpl( fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis( ir_context, bb_from, ir_context->cfg()->block(continue_block), fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context, - message_.continue_condition_value()), + message_.continue_condition_value(), + false), message_.phi_id()); } diff --git a/source/fuzz/transformation_add_function.cpp b/source/fuzz/transformation_add_function.cpp index 90276ed0..afe8e09b 100644 --- a/source/fuzz/transformation_add_function.cpp +++ b/source/fuzz/transformation_add_function.cpp @@ -168,6 +168,29 @@ void TransformationAddFunction::Apply( (void)(success); // Keep release builds happy (otherwise they may complain // that |success| is not used). + if (message_.is_livesafe()) { + // Make the function livesafe, which also should succeed. + success = TryToMakeFunctionLivesafe(ir_context, *transformation_context); + assert(success && "It should be possible to make the function livesafe."); + (void)(success); // Keep release builds happy. + + // Inform the fact manager that the function is livesafe. + assert(message_.instruction(0).opcode() == SpvOpFunction && + "The first instruction of an 'add function' transformation must be " + "OpFunction."); + transformation_context->GetFactManager()->AddFactFunctionIsLivesafe( + message_.instruction(0).result_id()); + } else { + // Inform the fact manager that all blocks in the function are dead. + for (auto& inst : message_.instruction()) { + if (inst.opcode() == SpvOpLabel) { + transformation_context->GetFactManager()->AddFactBlockIsDead( + inst.result_id()); + } + } + } + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + // Record the fact that all pointer parameters and variables declared in the // function should be regarded as having irrelevant values. This allows other // passes to store arbitrarily to such variables, and to pass them freely as @@ -191,29 +214,6 @@ void TransformationAddFunction::Apply( break; } } - - if (message_.is_livesafe()) { - // Make the function livesafe, which also should succeed. - success = TryToMakeFunctionLivesafe(ir_context, *transformation_context); - assert(success && "It should be possible to make the function livesafe."); - (void)(success); // Keep release builds happy. - - // Inform the fact manager that the function is livesafe. - assert(message_.instruction(0).opcode() == SpvOpFunction && - "The first instruction of an 'add function' transformation must be " - "OpFunction."); - transformation_context->GetFactManager()->AddFactFunctionIsLivesafe( - message_.instruction(0).result_id()); - } else { - // Inform the fact manager that all blocks in the function are dead. - for (auto& inst : message_.instruction()) { - if (inst.opcode() == SpvOpLabel) { - transformation_context->GetFactManager()->AddFactBlockIsDead( - inst.result_id()); - } - } - } - ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); } protobufs::Transformation TransformationAddFunction::ToMessage() const { @@ -363,6 +363,22 @@ bool TransformationAddFunction::TryToMakeFunctionLivesafe( return true; } +uint32_t TransformationAddFunction::GetBackEdgeBlockId( + opt::IRContext* ir_context, uint32_t loop_header_block_id) { + const auto* loop_header_block = + ir_context->cfg()->block(loop_header_block_id); + assert(loop_header_block && "|loop_header_block_id| is invalid"); + + for (auto pred : ir_context->cfg()->preds(loop_header_block_id)) { + if (ir_context->GetDominatorAnalysis(loop_header_block->GetParent()) + ->Dominates(loop_header_block_id, pred)) { + return pred; + } + } + + return 0; +} + bool TransformationAddFunction::TryToAddLoopLimiters( opt::IRContext* ir_context, opt::Function* added_function) const { // Collect up all the loop headers so that we can subsequently add loop @@ -474,21 +490,28 @@ bool TransformationAddFunction::TryToAddLoopLimiters( for (auto loop_header : loop_headers) { // Look for the loop's back-edge block. This is a predecessor of the loop // header that is dominated by the loop header. - uint32_t back_edge_block_id = 0; - for (auto pred : ir_context->cfg()->preds(loop_header->id())) { - if (ir_context->GetDominatorAnalysis(added_function) - ->Dominates(loop_header->id(), pred)) { - back_edge_block_id = pred; - break; - } - } + const auto back_edge_block_id = + GetBackEdgeBlockId(ir_context, loop_header->id()); if (!back_edge_block_id) { // The loop's back-edge block must be unreachable. This means that the // loop cannot iterate, so there is no need to make it lifesafe; we can // move on from this loop. continue; } - auto back_edge_block = ir_context->cfg()->block(back_edge_block_id); + + // If the loop's merge block is unreachable, then there are no constraints + // on where the merge block appears in relation to the blocks of the loop. + // This means we need to be careful when adding a branch from the back-edge + // block to the merge block: the branch might make the loop merge reachable, + // and it might then be dominated by the loop header and possibly by other + // blocks in the loop. Since a block needs to appear before those blocks it + // strictly dominates, this could make the module invalid. To avoid this + // problem we bail out in the case where the loop header does not dominate + // the loop merge. + if (!ir_context->GetDominatorAnalysis(added_function) + ->Dominates(loop_header->id(), loop_header->MergeBlockId())) { + return false; + } // Go through the sequence of loop limiter infos and find the one // corresponding to this loop. @@ -560,6 +583,7 @@ bool TransformationAddFunction::TryToAddLoopLimiters( // %t4 = OpLogicalOr %bool %c %t3 // OpBranchConditional %t4 %loop_merge %loop_header + auto back_edge_block = ir_context->cfg()->block(back_edge_block_id); auto back_edge_block_terminator = back_edge_block->terminator(); bool compare_using_greater_than_equal; if (back_edge_block_terminator->opcode() == SpvOpBranch) { @@ -675,16 +699,10 @@ bool TransformationAddFunction::TryToAddLoopLimiters( } // Add the new edge, by changing OpBranch to OpBranchConditional. - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3162): This - // could be a problem if the merge block was originally unreachable: it - // might now be dominated by other blocks that it appears earlier than in - // the module. back_edge_block_terminator->SetOpcode(SpvOpBranchConditional); back_edge_block_terminator->SetInOperands(opt::Instruction::OperandList( {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}}, - {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()} - - }, + {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}}, {SPV_OPERAND_TYPE_ID, {loop_header->id()}}})); } @@ -794,8 +812,8 @@ bool TransformationAddFunction::TryToClampAccessChainIndices( // Get the bound for the composite being indexed into; e.g. the number of // columns of matrix or the size of an array. - uint32_t bound = - GetBoundForCompositeIndex(ir_context, *should_be_composite_type); + uint32_t bound = fuzzerutil::GetBoundForCompositeIndex( + *should_be_composite_type, ir_context); // Get the instruction associated with the index and figure out its integer // type. @@ -873,28 +891,6 @@ bool TransformationAddFunction::TryToClampAccessChainIndices( return true; } -uint32_t TransformationAddFunction::GetBoundForCompositeIndex( - opt::IRContext* ir_context, const opt::Instruction& composite_type_inst) { - switch (composite_type_inst.opcode()) { - case SpvOpTypeArray: - return fuzzerutil::GetArraySize(composite_type_inst, ir_context); - case SpvOpTypeMatrix: - case SpvOpTypeVector: - return composite_type_inst.GetSingleWordInOperand(1); - case SpvOpTypeStruct: { - return fuzzerutil::GetNumberOfStructMembers(composite_type_inst); - } - case SpvOpTypeRuntimeArray: - assert(false && - "GetBoundForCompositeIndex should not be invoked with an " - "OpTypeRuntimeArray, which does not have a static bound."); - return 0; - default: - assert(false && "Unknown composite type."); - return 0; - } -} - opt::Instruction* TransformationAddFunction::FollowCompositeIndex( opt::IRContext* ir_context, const opt::Instruction& composite_type_inst, uint32_t index_id) { diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h index 5af197b6..4ebf171c 100644 --- a/source/fuzz/transformation_add_function.h +++ b/source/fuzz/transformation_add_function.h @@ -58,13 +58,6 @@ class TransformationAddFunction : public Transformation { protobufs::Transformation ToMessage() const override; - // Helper method that returns the bound for indexing into a composite of type - // |composite_type_inst|, i.e. the number of fields of a struct, the size of - // an array, the number of components of a vector, or the number of columns of - // a matrix. - static uint32_t GetBoundForCompositeIndex( - opt::IRContext* ir_context, const opt::Instruction& composite_type_inst); - // Helper method that, given composite type |composite_type_inst|, returns the // type of the sub-object at index |index_id|, which is required to be in- // bounds. @@ -72,6 +65,12 @@ class TransformationAddFunction : public Transformation { opt::IRContext* ir_context, const opt::Instruction& composite_type_inst, uint32_t index_id); + // Returns id of the back-edge block, given the corresponding + // |loop_header_block_id|. |loop_header_block_id| must be the id of a loop + // header block. Returns 0 if the loop has no back-edge block. + static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context, + uint32_t loop_header_block_id); + private: // Attempts to create a function from the series of instructions in // |message_.instruction| and add it to |ir_context|. diff --git a/source/fuzz/transformation_add_global_variable.cpp b/source/fuzz/transformation_add_global_variable.cpp index 303c4d97..0ec4a7b3 100644 --- a/source/fuzz/transformation_add_global_variable.cpp +++ b/source/fuzz/transformation_add_global_variable.cpp @@ -98,15 +98,15 @@ void TransformationAddGlobalVariable::Apply( static_cast<SpvStorageClass>(message_.storage_class()), message_.initializer_id()); - if (message_.value_is_irrelevant()) { - transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( - message_.fresh_id()); - } - // We have added an instruction to the module, so need to be careful about the // validity of existing analyses. ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); + + if (message_.value_is_irrelevant()) { + transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( + message_.fresh_id()); + } } protobufs::Transformation TransformationAddGlobalVariable::ToMessage() const { diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp index a6b31b49..284b9f6e 100644 --- a/source/fuzz/transformation_add_local_variable.cpp +++ b/source/fuzz/transformation_add_local_variable.cpp @@ -74,11 +74,12 @@ void TransformationAddLocalVariable::Apply( message_.type_id(), message_.function_id(), message_.initializer_id()); + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + if (message_.value_is_irrelevant()) { transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( message_.fresh_id()); } - ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); } protobufs::Transformation TransformationAddLocalVariable::ToMessage() const { diff --git a/source/fuzz/transformation_add_loop_preheader.cpp b/source/fuzz/transformation_add_loop_preheader.cpp new file mode 100644 index 00000000..2f3468a9 --- /dev/null +++ b/source/fuzz/transformation_add_loop_preheader.cpp @@ -0,0 +1,225 @@ +// Copyright (c) 2020 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 "transformation_add_loop_preheader.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/instruction.h" + +namespace spvtools { +namespace fuzz { +TransformationAddLoopPreheader::TransformationAddLoopPreheader( + const protobufs::TransformationAddLoopPreheader& message) + : message_(message) {} + +TransformationAddLoopPreheader::TransformationAddLoopPreheader( + uint32_t loop_header_block, uint32_t fresh_id, + std::vector<uint32_t> phi_id) { + message_.set_loop_header_block(loop_header_block); + message_.set_fresh_id(fresh_id); + for (auto id : phi_id) { + message_.add_phi_id(id); + } +} + +bool TransformationAddLoopPreheader::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& /* unused */) const { + // |message_.loop_header_block()| must be the id of a loop header block. + opt::BasicBlock* loop_header_block = + fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block()); + if (!loop_header_block || !loop_header_block->IsLoopHeader()) { + return false; + } + + // The id for the preheader must actually be fresh. + std::set<uint32_t> used_ids; + if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.fresh_id(), + ir_context, &used_ids)) { + return false; + } + + size_t num_predecessors = + ir_context->cfg()->preds(message_.loop_header_block()).size(); + + // The block must have at least 2 predecessors (the back-edge block and + // another predecessor outside of the loop) + if (num_predecessors < 2) { + return false; + } + + // If the block only has one predecessor outside of the loop (and thus 2 in + // total), then no additional fresh ids are necessary. + if (num_predecessors == 2) { + return true; + } + + // Count the number of OpPhi instructions. + int32_t num_phi_insts = 0; + loop_header_block->ForEachPhiInst( + [&num_phi_insts](opt::Instruction* /* unused */) { num_phi_insts++; }); + + // There must be enough fresh ids for the OpPhi instructions. + if (num_phi_insts > message_.phi_id_size()) { + return false; + } + + // Check that the needed ids are fresh and distinct. + for (int32_t i = 0; i < num_phi_insts; i++) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.phi_id(i), + ir_context, &used_ids)) { + return false; + } + } + + return true; +} + +void TransformationAddLoopPreheader::Apply( + opt::IRContext* ir_context, + TransformationContext* /* transformation_context */) const { + // Find the loop header. + opt::BasicBlock* loop_header = + fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block()); + + auto dominator_analysis = + ir_context->GetDominatorAnalysis(loop_header->GetParent()); + + uint32_t back_edge_block_id = 0; + + // Update the branching instructions of the out-of-loop predecessors of the + // header. Set |back_edge_block_id| to be the id of the back-edge block. + ir_context->get_def_use_mgr()->ForEachUse( + loop_header->id(), + [this, &ir_context, &dominator_analysis, &loop_header, + &back_edge_block_id](opt::Instruction* use_inst, uint32_t use_index) { + if (dominator_analysis->Dominates(loop_header->GetLabelInst(), + use_inst)) { + // If |use_inst| is a branch instruction dominated by the header, the + // block containing it is the back-edge block. + if (use_inst->IsBranch()) { + assert(back_edge_block_id == 0 && + "There should only be one back-edge block"); + back_edge_block_id = ir_context->get_instr_block(use_inst)->id(); + } + // References to the header inside the loop should not be updated + return; + } + + // If |use_inst| is not a branch or merge instruction, it should not be + // changed. + if (!use_inst->IsBranch() && + use_inst->opcode() != SpvOpSelectionMerge && + use_inst->opcode() != SpvOpLoopMerge) { + return; + } + + // Update the reference. + use_inst->SetOperand(use_index, {message_.fresh_id()}); + }); + + assert(back_edge_block_id && "The back-edge block should have been found"); + + // Make a new block for the preheader. + std::unique_ptr<opt::BasicBlock> preheader = MakeUnique<opt::BasicBlock>( + std::unique_ptr<opt::Instruction>(new opt::Instruction( + ir_context, SpvOpLabel, 0, message_.fresh_id(), {}))); + + uint32_t phi_ids_used = 0; + + // Update the OpPhi instructions and, if there is more than one out-of-loop + // predecessor, add necessary OpPhi instructions so the preheader. + loop_header->ForEachPhiInst([this, &ir_context, &preheader, + &back_edge_block_id, + &phi_ids_used](opt::Instruction* phi_inst) { + // The loop header must have at least 2 incoming edges (the back edge, and + // at least one from outside the loop). + assert(phi_inst->NumInOperands() >= 4); + + if (phi_inst->NumInOperands() == 4) { + // There is just one out-of-loop predecessor, so no additional + // instructions in the preheader are necessary. The reference to the + // original out-of-loop predecessor needs to be updated so that it refers + // to the preheader. + uint32_t index_of_out_of_loop_pred_id = + phi_inst->GetInOperand(1).words[0] == back_edge_block_id ? 3 : 1; + phi_inst->SetInOperand(index_of_out_of_loop_pred_id, {preheader->id()}); + } else { + // There is more than one out-of-loop predecessor, so an OpPhi instruction + // needs to be added to the preheader, and its value will depend on all + // the current out-of-loop predecessors of the header. + + // Get the operand list and the value corresponding to the back-edge + // block. + std::vector<opt::Operand> preheader_in_operands; + uint32_t back_edge_val = 0; + + for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) { + // Only add operands if they don't refer to the back-edge block. + if (phi_inst->GetInOperand(i + 1).words[0] == back_edge_block_id) { + back_edge_val = phi_inst->GetInOperand(i).words[0]; + } else { + preheader_in_operands.push_back(std::move(phi_inst->GetInOperand(i))); + preheader_in_operands.push_back( + std::move(phi_inst->GetInOperand(i + 1))); + } + } + + // Add the new instruction to the preheader. + uint32_t fresh_phi_id = message_.phi_id(phi_ids_used++); + + // Update id bound. + fuzzerutil::UpdateModuleIdBound(ir_context, fresh_phi_id); + + preheader->AddInstruction(std::unique_ptr<opt::Instruction>( + new opt::Instruction(ir_context, SpvOpPhi, phi_inst->type_id(), + fresh_phi_id, preheader_in_operands))); + + // Update the OpPhi instruction in the header so that it refers to the + // back edge block and the preheader as the predecessors, and it uses the + // newly-defined OpPhi in the preheader for the corresponding value. + phi_inst->SetInOperands( + {{SPV_OPERAND_TYPE_RESULT_ID, {fresh_phi_id}}, + {SPV_OPERAND_TYPE_RESULT_ID, {preheader->id()}}, + {SPV_OPERAND_TYPE_RESULT_ID, {back_edge_val}}, + {SPV_OPERAND_TYPE_RESULT_ID, {back_edge_block_id}}}); + } + }); + + // Update id bound. + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // Add an unconditional branch from the preheader to the header. + preheader->AddInstruction(std::unique_ptr<opt::Instruction>( + new opt::Instruction(ir_context, SpvOpBranch, 0, 0, + std::initializer_list<opt::Operand>{opt::Operand( + spv_operand_type_t::SPV_OPERAND_TYPE_RESULT_ID, + {loop_header->id()})}))); + + // Insert the preheader in the module. + loop_header->GetParent()->InsertBasicBlockBefore(std::move(preheader), + loop_header); + + // Invalidate analyses because the structure of the program changed. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationAddLoopPreheader::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_loop_preheader() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_loop_preheader.h b/source/fuzz/transformation_add_loop_preheader.h new file mode 100644 index 00000000..2143e3f7 --- /dev/null +++ b/source/fuzz/transformation_add_loop_preheader.h @@ -0,0 +1,57 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H +#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddLoopPreheader : public Transformation { + public: + explicit TransformationAddLoopPreheader( + const protobufs::TransformationAddLoopPreheader& message); + + TransformationAddLoopPreheader(uint32_t loop_header_block, uint32_t fresh_id, + std::vector<uint32_t> phi_id); + + // - |message_.loop_header_block| must be the id of a loop header block in + // the given module. + // - |message_.fresh_id| must be an available id. + // - |message_.phi_ids| must be a list of available ids. + // It can be empty if the loop header only has one predecessor outside of + // the loop. Otherwise, it must contain at least as many ids as OpPhi + // instructions in the loop header block. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds a preheader block as the unique out-of-loop predecessor of the given + // loop header block. All of the existing out-of-loop predecessors of the + // header are changed so that they branch to the preheader instead. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddLoopPreheader message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp new file mode 100644 index 00000000..175f5e66 --- /dev/null +++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp @@ -0,0 +1,427 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +namespace { +uint32_t kMaxNumOfIterations = 32; +} + +TransformationAddLoopToCreateIntConstantSynonym:: + TransformationAddLoopToCreateIntConstantSynonym( + const protobufs::TransformationAddLoopToCreateIntConstantSynonym& + message) + : message_(message) {} + +TransformationAddLoopToCreateIntConstantSynonym:: + TransformationAddLoopToCreateIntConstantSynonym( + uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id, + uint32_t num_iterations_id, uint32_t block_after_loop_id, + uint32_t syn_id, uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id, + uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id, + uint32_t additional_block_id) { + message_.set_constant_id(constant_id); + message_.set_initial_val_id(initial_val_id); + message_.set_step_val_id(step_val_id); + message_.set_num_iterations_id(num_iterations_id); + message_.set_block_after_loop_id(block_after_loop_id); + message_.set_syn_id(syn_id); + message_.set_loop_id(loop_id); + message_.set_ctr_id(ctr_id); + message_.set_temp_id(temp_id); + message_.set_eventual_syn_id(eventual_syn_id); + message_.set_incremented_ctr_id(incremented_ctr_id); + message_.set_cond_id(cond_id); + message_.set_additional_block_id(additional_block_id); +} + +bool TransformationAddLoopToCreateIntConstantSynonym::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // Check that |message_.constant_id|, |message_.initial_val_id| and + // |message_.step_val_id| are existing constants. + + auto constant = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.constant_id()); + auto initial_val = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.initial_val_id()); + auto step_val = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.step_val_id()); + + if (!constant || !initial_val || !step_val) { + return false; + } + + // Check that the type of |constant| is integer scalar or vector with integer + // components. + if (!constant->AsIntConstant() && + (!constant->AsVectorConstant() || + !constant->type()->AsVector()->element_type()->AsInteger())) { + return false; + } + + // Check that the component bit width of |constant| is <= 64. + // Consider the width of the constant if it is an integer, of a single + // component if it is a vector. + uint32_t bit_width = + constant->AsIntConstant() + ? constant->type()->AsInteger()->width() + : constant->type()->AsVector()->element_type()->AsInteger()->width(); + if (bit_width > 64) { + return false; + } + + auto constant_def = + ir_context->get_def_use_mgr()->GetDef(message_.constant_id()); + auto initial_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id()); + auto step_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.step_val_id()); + + // Check that |constant|, |initial_val| and |step_val| have the same type, + // with possibly different signedness. + if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(), + initial_val_def->type_id()) || + !fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(), + step_val_def->type_id())) { + return false; + } + + // |message_.num_iterations_id| is an integer constant with bit width 32. + auto num_iterations = ir_context->get_constant_mgr()->FindDeclaredConstant( + message_.num_iterations_id()); + + if (!num_iterations || !num_iterations->AsIntConstant() || + num_iterations->type()->AsInteger()->width() != 32) { + return false; + } + + // Check that the number of iterations is > 0 and <= 32. + uint32_t num_iterations_value = + num_iterations->AsIntConstant()->GetU32BitValue(); + + if (num_iterations_value == 0 || num_iterations_value > kMaxNumOfIterations) { + return false; + } + + // Check that the module contains 32-bit signed integer scalar constants of + // value 0 and 1. + if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, + {0}, 32, true, false)) { + return false; + } + + if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, + {1}, 32, true, false)) { + return false; + } + + // Check that the module contains the Bool type. + if (!fuzzerutil::MaybeGetBoolType(ir_context)) { + return false; + } + + // Check that the equation C = I - S * N is satisfied. + + // Collect the components in vectors (if the constants are scalars, these + // vectors will contain the constants themselves). + std::vector<const opt::analysis::Constant*> c_components; + std::vector<const opt::analysis::Constant*> i_components; + std::vector<const opt::analysis::Constant*> s_components; + if (constant->AsIntConstant()) { + c_components.emplace_back(constant); + i_components.emplace_back(initial_val); + s_components.emplace_back(step_val); + } else { + // It is a vector: get all the components. + c_components = constant->AsVectorConstant()->GetComponents(); + i_components = initial_val->AsVectorConstant()->GetComponents(); + s_components = step_val->AsVectorConstant()->GetComponents(); + } + + // Check the value of the components satisfy the equation. + for (uint32_t i = 0; i < c_components.size(); i++) { + // Use 64-bits integers to be able to handle constants of any width <= 64. + uint64_t c_value = c_components[i]->AsIntConstant()->GetZeroExtendedValue(); + uint64_t i_value = i_components[i]->AsIntConstant()->GetZeroExtendedValue(); + uint64_t s_value = s_components[i]->AsIntConstant()->GetZeroExtendedValue(); + + uint64_t result = i_value - s_value * num_iterations_value; + + // Use bit shifts to ignore the first bits in excess (if there are any). By + // shifting left, we discard the first |64 - bit_width| bits. By shifting + // right, we move the bits back to their correct position. + result = (result << (64 - bit_width)) >> (64 - bit_width); + + if (c_value != result) { + return false; + } + } + + // Check that |message_.block_after_loop_id| is the label of a block. + auto block = + fuzzerutil::MaybeFindBlock(ir_context, message_.block_after_loop_id()); + + // Check that the block exists and has a single predecessor. + if (!block || ir_context->cfg()->preds(block->id()).size() != 1) { + return false; + } + + // Check that the block is not a merge block. + if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) { + return false; + } + + // Check that the block is not a continue block. + if (ir_context->GetStructuredCFGAnalysis()->IsContinueBlock(block->id())) { + return false; + } + + // Check that the block is not a loop header. + if (block->IsLoopHeader()) { + return false; + } + + // Check all the fresh ids. + std::set<uint32_t> fresh_ids_used; + for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(), + message_.temp_id(), message_.eventual_syn_id(), + message_.incremented_ctr_id(), message_.cond_id()}) { + if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context, + &fresh_ids_used)) { + return false; + } + } + + // Check the additional block id if it is non-zero. + return !message_.additional_block_id() || + CheckIdIsFreshAndNotUsedByThisTransformation( + message_.additional_block_id(), ir_context, &fresh_ids_used); +} + +void TransformationAddLoopToCreateIntConstantSynonym::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // Find 32-bit signed integer constants 0 and 1. + uint32_t const_0_id = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {0}, 32, true, false); + auto const_0_def = ir_context->get_def_use_mgr()->GetDef(const_0_id); + uint32_t const_1_id = fuzzerutil::MaybeGetIntegerConstant( + ir_context, *transformation_context, {1}, 32, true, false); + + // Retrieve the instruction defining the initial value constant. + auto initial_val_def = + ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id()); + + // Retrieve the block before which we want to insert the loop. + auto block_after_loop = + ir_context->get_instr_block(message_.block_after_loop_id()); + + // Find the predecessor of the block. + uint32_t pred_id = + ir_context->cfg()->preds(message_.block_after_loop_id())[0]; + + // Get the id for the last block in the new loop. It will be + // |message_.additional_block_id| if this is non_zero, |message_.loop_id| + // otherwise. + uint32_t last_loop_block_id = message_.additional_block_id() + ? message_.additional_block_id() + : message_.loop_id(); + + // Create the loop header block. + std::unique_ptr<opt::BasicBlock> loop_block = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, message_.loop_id(), + opt::Instruction::OperandList{})); + + // Add OpPhi instructions to retrieve the current value of the counter and of + // the temporary variable that will be decreased at each operation. + loop_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpPhi, const_0_def->type_id(), message_.ctr_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {const_0_id}}, + {SPV_OPERAND_TYPE_ID, {pred_id}}, + {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + loop_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpPhi, initial_val_def->type_id(), message_.temp_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.initial_val_id()}}, + {SPV_OPERAND_TYPE_ID, {pred_id}}, + {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + // Collect the other instructions in a list. These will be added to an + // additional block if |message_.additional_block_id| is defined, to the loop + // header otherwise. + std::vector<std::unique_ptr<opt::Instruction>> other_instructions; + + // Add an instruction to subtract the step value from the temporary value. + // The value of this id will converge to the constant in the last iteration. + other_instructions.push_back(MakeUnique<opt::Instruction>( + ir_context, SpvOpISub, initial_val_def->type_id(), + message_.eventual_syn_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.temp_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.step_val_id()}}})); + + // Add an instruction to increment the counter. + other_instructions.push_back(MakeUnique<opt::Instruction>( + ir_context, SpvOpIAdd, const_0_def->type_id(), + message_.incremented_ctr_id(), + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {message_.ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {const_1_id}}})); + + // Add an instruction to decide whether the condition holds. + other_instructions.push_back(MakeUnique<opt::Instruction>( + ir_context, SpvOpSLessThan, fuzzerutil::MaybeGetBoolType(ir_context), + message_.cond_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.num_iterations_id()}}})); + + // Define the OpLoopMerge instruction for the loop header. The merge block is + // the existing block, the continue block is the last block in the loop + // (either the loop itself or the additional block). + std::unique_ptr<opt::Instruction> merge_inst = MakeUnique<opt::Instruction>( + ir_context, SpvOpLoopMerge, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}); + + // Define a conditional branch instruction, branching to the loop header if + // the condition holds, and to the existing block otherwise. This instruction + // will be added to the last block in the loop. + std::unique_ptr<opt::Instruction> conditional_branch = + MakeUnique<opt::Instruction>( + ir_context, SpvOpBranchConditional, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.cond_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.loop_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}}); + + if (message_.additional_block_id()) { + // If an id for the additional block is specified, create an additional + // block, containing the instructions in the list and a branching + // instruction. + + std::unique_ptr<opt::BasicBlock> additional_block = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, message_.additional_block_id(), + opt::Instruction::OperandList{})); + + for (auto& instruction : other_instructions) { + additional_block->AddInstruction(std::move(instruction)); + } + + additional_block->AddInstruction(std::move(conditional_branch)); + + // Add the merge instruction to the header. + loop_block->AddInstruction(std::move(merge_inst)); + + // Add an unconditional branch from the header to the additional block. + loop_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.additional_block_id()}}})); + + // Insert the two loop blocks before the existing block. + block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block), + block_after_loop); + block_after_loop->GetParent()->InsertBasicBlockBefore( + std::move(additional_block), block_after_loop); + } else { + // If no id for an additional block is specified, the loop will only be made + // up of one block, so we need to add all the instructions to it. + + for (auto& instruction : other_instructions) { + loop_block->AddInstruction(std::move(instruction)); + } + + // Add the merge and conditional branch instructions. + loop_block->AddInstruction(std::move(merge_inst)); + loop_block->AddInstruction(std::move(conditional_branch)); + + // Insert the header before the existing block. + block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block), + block_after_loop); + } + + // Update the branching instructions leading to this block. + ir_context->get_def_use_mgr()->ForEachUse( + message_.block_after_loop_id(), + [this](opt::Instruction* instruction, uint32_t operand_index) { + assert(instruction->opcode() != SpvOpLoopMerge && + instruction->opcode() != SpvOpSelectionMerge && + instruction->opcode() != SpvOpSwitch && + "The block should not be referenced by OpLoopMerge, " + "OpSelectionMerge or OpSwitch instructions, by construction."); + // Replace all uses of the label inside branch instructions. + if (instruction->opcode() == SpvOpBranch || + instruction->opcode() == SpvOpBranchConditional) { + instruction->SetOperand(operand_index, {message_.loop_id()}); + } + }); + + // Update all the OpPhi instructions in the block after the loop: its + // predecessor is now the last block in the loop. + block_after_loop->ForEachPhiInst( + [last_loop_block_id](opt::Instruction* phi_inst) { + // Since the block only had one predecessor, the id of the predecessor + // is input operand 1. + phi_inst->SetInOperand(1, {last_loop_block_id}); + }); + + // Add a new OpPhi instruction at the beginning of the block after the loop, + // defining the synonym of the constant. The type id will be the same as + // |message_.initial_value_id|, since this is the value that is decremented in + // the loop. + block_after_loop->begin()->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpPhi, initial_val_def->type_id(), message_.syn_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}}, + {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}})); + + // Update the module id bound with all the fresh ids used. + for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(), + message_.temp_id(), message_.eventual_syn_id(), + message_.incremented_ctr_id(), message_.cond_id(), + message_.cond_id(), message_.additional_block_id()}) { + fuzzerutil::UpdateModuleIdBound(ir_context, id); + } + + // Since we changed the structure of the module, we need to invalidate all the + // analyses. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Record that |message_.syn_id| is synonymous with |message_.constant_id|. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.syn_id(), {}), + MakeDataDescriptor(message_.constant_id(), {})); +} + +protobufs::Transformation +TransformationAddLoopToCreateIntConstantSynonym::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_loop_to_create_int_constant_synonym() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h new file mode 100644 index 00000000..29140298 --- /dev/null +++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { +class TransformationAddLoopToCreateIntConstantSynonym : public Transformation { + public: + explicit TransformationAddLoopToCreateIntConstantSynonym( + const protobufs::TransformationAddLoopToCreateIntConstantSynonym& + message); + + TransformationAddLoopToCreateIntConstantSynonym( + uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id, + uint32_t num_iterations_id, uint32_t block_after_loop_id, uint32_t syn_id, + uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id, + uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id, + uint32_t additional_block_id); + + // - |message_.constant_id|, |message_.initial_value_id|, + // |message_.step_val_id| are integer constants (scalar or vectors) with the + // same type (with possibly different signedness, but same bit width, which + // must be <= 64). Let their value be C, I, S respectively. + // - |message_.num_iterations_id| is a 32-bit integer scalar constant, with + // value N > 0 and N <= 32. + // - The module contains 32-bit signed integer scalar constants of values 0 + // and 1. + // - The module contains the boolean type. + // - C = I - S * N + // - |message_.block_after_loop_id| is the label of a block which has a single + // predecessor and which is not a merge block, a continue block or a loop + // header. + // - |message_.additional_block_id| is either 0 or a valid fresh id, distinct + // from the other fresh ids. + // - All of the other parameters are valid fresh ids. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds a loop to the module, defining a synonym of an integer (scalar or + // vector) constant. This id is marked as synonym with the original constant. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddLoopToCreateIntConstantSynonym message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_ diff --git a/source/fuzz/transformation_add_opphi_synonym.cpp b/source/fuzz/transformation_add_opphi_synonym.cpp new file mode 100644 index 00000000..d3afc15b --- /dev/null +++ b/source/fuzz/transformation_add_opphi_synonym.cpp @@ -0,0 +1,197 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_opphi_synonym.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym( + const protobufs::TransformationAddOpPhiSynonym& message) + : message_(message) {} + +TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym( + uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids, + uint32_t fresh_id) { + message_.set_block_id(block_id); + *message_.mutable_pred_to_id() = + fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids); + message_.set_fresh_id(fresh_id); +} + +bool TransformationAddOpPhiSynonym::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // Check that |message_.block_id| is a block label id. + auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id()); + if (!block) { + return false; + } + + // Check that |message_.fresh_id| is actually fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + + // Check that |message_.pred_to_id| contains a mapping for all of the block's + // predecessors. + std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id()); + + // There must be at least one predecessor. + if (predecessors.empty()) { + return false; + } + + std::map<uint32_t, uint32_t> preds_to_ids = + fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id()); + + // There must not be repeated key values in |message_.pred_to_id|. + if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) { + return false; + } + + // Check that each predecessor has a corresponding mapping and all of the + // corresponding ids exist. + for (uint32_t pred : predecessors) { + if (preds_to_ids.count(pred) == 0) { + return false; + } + + // Check that the id exists in the module. + if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) { + return false; + } + } + + // Get the first id and its type (which should be the same as all the other + // ones) and check that the transformation supports this type. + uint32_t first_id = preds_to_ids[predecessors[0]]; + uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id(); + if (!CheckTypeIsAllowed(ir_context, type_id)) { + return false; + } + + // Check that the ids corresponding to predecessors are all synonymous, have + // the same type and are available to use at the end of the predecessor. + for (uint32_t pred : predecessors) { + auto id = preds_to_ids[pred]; + + // Check that the id has the same type as the other ones. + if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) { + return false; + } + + // Check that the id is synonymous with the others by checking that it is + // synonymous with the first one (or it is the same id). + if (id != first_id && + !transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) { + return false; + } + + // Check that the id is available at the end of the corresponding + // predecessor block. + + auto pred_block = ir_context->get_instr_block(pred); + + // We should always be able to find the predecessor block, since it is in + // the predecessors list of |block|. + assert(pred_block && "Could not find one of the predecessor blocks."); + + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, pred_block->terminator(), id)) { + return false; + } + } + + return true; +} + +void TransformationAddOpPhiSynonym::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // Get the type id from one of the ids. + uint32_t first_id = message_.pred_to_id(0).second(); + uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id(); + + // Define the operand list. + opt::Instruction::OperandList operand_list; + + // For each predecessor, add the corresponding operands. + for (auto& pair : message_.pred_to_id()) { + operand_list.emplace_back( + opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}}); + operand_list.emplace_back( + opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}}); + } + + // Add a new OpPhi instructions at the beginning of the block. + ir_context->get_instr_block(message_.block_id()) + ->begin() + .InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id, + message_.fresh_id(), + std::move(operand_list))); + + // Update the module id bound. + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // Invalidate all analyses, since we added an instruction to the module. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); + + // Record the fact that the new id is synonym with the other ones by declaring + // that it is a synonym of the first one. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.fresh_id(), {}), + MakeDataDescriptor(first_id, {})); +} + +protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_opphi_synonym() = message_; + return result; +} + +bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed( + opt::IRContext* ir_context, uint32_t type_id) { + auto type = ir_context->get_type_mgr()->GetType(type_id); + if (!type) { + return false; + } + + // We allow the following types: Bool, Integer, Float, Vector, Matrix, Array, + // Struct. + if (type->AsBool() || type->AsInteger() || type->AsFloat() || + type->AsVector() || type->AsMatrix() || type->AsArray() || + type->AsStruct()) { + return true; + } + + // We allow pointer types if the VariablePointers capability is enabled and + // the pointer has the correct storage class (Workgroup or StorageBuffer). + if (type->AsPointer()) { + auto storage_class = type->AsPointer()->storage_class(); + return ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointers) && + (storage_class == SpvStorageClassWorkgroup || + storage_class == SpvStorageClassStorageBuffer); + } + + // We do not allow other types. + return false; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_add_opphi_synonym.h b/source/fuzz/transformation_add_opphi_synonym.h new file mode 100644 index 00000000..dc0e6d9b --- /dev/null +++ b/source/fuzz/transformation_add_opphi_synonym.h @@ -0,0 +1,72 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { +class TransformationAddOpPhiSynonym : public Transformation { + public: + explicit TransformationAddOpPhiSynonym( + const protobufs::TransformationAddOpPhiSynonym& message); + + TransformationAddOpPhiSynonym( + uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids, + uint32_t fresh_id); + + // - |message_.block_id| is the label of a block with at least one + // predecessor. + // - |message_.pred_to_id| contains a mapping from each of the predecessors of + // the block to an id that is available at the end of the predecessor. + // - All the ids corresponding to a predecessor in |message_.pred_to_id|: + // - have been recorded as synonymous and all have the same type. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a + // predecessor is a dead block, any id of the right type could be used, + // even if it is not synonym with the others. + // - have one of the following types: Bool, Integer, Float, Vector, Matrix, + // Array, Struct. Pointer types are also allowed if the VariablePointers + // capability is enabled and the storage class is Workgroup or + // StorageBuffer. + // - |message_.fresh_id| is a fresh id. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Given a block with n predecessors, with n >= 1, and n corresponding + // synonymous ids of the same type, each available to use at the end of the + // corresponding predecessor, adds an OpPhi instruction at the beginning of + // the block of the form: + // %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n + // This instruction is then marked as synonymous with the ids. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + // Returns true if |type_id| is the id of a type in the module, which is one + // of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct. + // Pointer types are also allowed if the VariablePointers capability is + // enabled and the storage class is Workgroup or StorageBuffer. + static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id); + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddOpPhiSynonym message_; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_ diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp index cc32362e..0079148d 100644 --- a/source/fuzz/transformation_add_parameter.cpp +++ b/source/fuzz/transformation_add_parameter.cpp @@ -14,8 +14,6 @@ #include "source/fuzz/transformation_add_parameter.h" -#include <source/spirv_constant.h> - #include "source/fuzz/fuzzer_util.h" namespace spvtools { @@ -26,17 +24,20 @@ TransformationAddParameter::TransformationAddParameter( : message_(message) {} TransformationAddParameter::TransformationAddParameter( - uint32_t function_id, uint32_t parameter_fresh_id, uint32_t initializer_id, + uint32_t function_id, uint32_t parameter_fresh_id, + uint32_t parameter_type_id, std::map<uint32_t, uint32_t> call_parameter_ids, uint32_t function_type_fresh_id) { message_.set_function_id(function_id); message_.set_parameter_fresh_id(parameter_fresh_id); - message_.set_initializer_id(initializer_id); + message_.set_parameter_type_id(parameter_type_id); + *message_.mutable_call_parameter_ids() = + fuzzerutil::MapToRepeatedUInt32Pair(call_parameter_ids); message_.set_function_type_fresh_id(function_type_fresh_id); } bool TransformationAddParameter::IsApplicable( opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { - // Check that function exists + // Check that function exists. const auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id()); if (!function || @@ -44,22 +45,53 @@ bool TransformationAddParameter::IsApplicable( return false; } - // Check that |initializer_id| is valid. - const auto* initializer_inst = - ir_context->get_def_use_mgr()->GetDef(message_.initializer_id()); - - if (!initializer_inst) { + // The type must be supported. + uint32_t new_parameter_type_id = message_.parameter_type_id(); + auto new_parameter_type = + ir_context->get_type_mgr()->GetType(new_parameter_type_id); + if (!new_parameter_type) { return false; } - - // Check that initializer's type is valid. - const auto* initializer_type = - ir_context->get_type_mgr()->GetType(initializer_inst->type_id()); - - if (!initializer_type || !IsParameterTypeSupported(*initializer_type)) { + if (!IsParameterTypeSupported(*new_parameter_type)) { return false; } + // Iterate over all callers. + std::map<uint32_t, uint32_t> call_parameter_ids_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids()); + for (auto* instr : + fuzzerutil::GetCallers(ir_context, message_.function_id())) { + uint32_t caller_id = instr->result_id(); + + // If there is no entry for this caller, return false. + if (call_parameter_ids_map.find(caller_id) == + call_parameter_ids_map.end()) { + return false; + } + uint32_t value_id = call_parameter_ids_map[caller_id]; + + auto value_instr = ir_context->get_def_use_mgr()->GetDef(value_id); + if (!value_instr) { + return false; + } + // If the id of the value of the map is not available before the caller, + // return false. + if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr, + value_id)) { + return false; + } + + // The type of the value must be defined. + uint32_t value_type_id = fuzzerutil::GetTypeId(ir_context, value_id); + if (!value_type_id) { + return false; + } + + // Type of every value of the map must be the same for all callers. + if (new_parameter_type_id != value_type_id) { + return false; + } + } return fuzzerutil::IsFreshId(ir_context, message_.parameter_fresh_id()) && fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id()) && message_.parameter_fresh_id() != message_.function_type_fresh_id(); @@ -68,72 +100,72 @@ bool TransformationAddParameter::IsApplicable( void TransformationAddParameter::Apply( opt::IRContext* ir_context, TransformationContext* transformation_context) const { - // Find the function that will be transformed + // Find the function that will be transformed. auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id()); assert(function && "Can't find the function"); - auto parameter_type_id = - fuzzerutil::GetTypeId(ir_context, message_.initializer_id()); - assert(parameter_type_id != 0 && "Initializer has invalid type"); + std::map<uint32_t, uint32_t> call_parameter_ids_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids()); + + uint32_t new_parameter_type_id = message_.parameter_type_id(); + auto new_parameter_type = + ir_context->get_type_mgr()->GetType(new_parameter_type_id); + assert(new_parameter_type && "New parameter has invalid type."); // Add new parameters to the function. function->AddParameter(MakeUnique<opt::Instruction>( - ir_context, SpvOpFunctionParameter, parameter_type_id, + ir_context, SpvOpFunctionParameter, new_parameter_type_id, message_.parameter_fresh_id(), opt::Instruction::OperandList())); fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id()); - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403): - // Add an PointeeValueIsIrrelevant fact if the parameter is a pointer. - - // Mark new parameter as irrelevant so that we can replace its use with some - // other id. - transformation_context->GetFactManager()->AddFactIdIsIrrelevant( - message_.parameter_fresh_id()); - // Fix all OpFunctionCall instructions. - ir_context->get_def_use_mgr()->ForEachUser( - &function->DefInst(), [this](opt::Instruction* call) { - if (call->opcode() != SpvOpFunctionCall || - call->GetSingleWordInOperand(0) != message_.function_id()) { - return; - } - - call->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}); - }); - - auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); - assert(old_function_type && "Function must have a valid type"); + for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) { + inst->AddOperand( + {SPV_OPERAND_TYPE_ID, {call_parameter_ids_map[inst->result_id()]}}); + } - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) { - // Adjust existing function type if it is used only by this function. - old_function_type->AddOperand({SPV_OPERAND_TYPE_ID, {parameter_type_id}}); + // Update function's type. + { + // We use a separate scope here since |old_function_type| might become a + // dangling pointer after the call to the fuzzerutil::UpdateFunctionType. - // We must make sure that all dependencies of |old_function_type| are - // evaluated before |old_function_type| (i.e. the domination rules are not - // broken). Thus, we move |old_function_type| to the end of the list of all - // types in the module. - old_function_type->RemoveFromList(); - ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type)); - } else { - // Otherwise, either create a new type or use an existing one. - std::vector<uint32_t> type_ids; - type_ids.reserve(old_function_type->NumInOperands() + 1); + const auto* old_function_type = + fuzzerutil::GetFunctionType(ir_context, function); + assert(old_function_type && "Function must have a valid type"); - for (uint32_t i = 0, n = old_function_type->NumInOperands(); i < n; ++i) { - type_ids.push_back(old_function_type->GetSingleWordInOperand(i)); + std::vector<uint32_t> parameter_type_ids; + for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { + parameter_type_ids.push_back( + old_function_type->GetSingleWordInOperand(i)); } - type_ids.push_back(parameter_type_id); + parameter_type_ids.push_back(new_parameter_type_id); - function->DefInst().SetInOperand( - 1, {fuzzerutil::FindOrCreateFunctionType( - ir_context, message_.function_type_fresh_id(), type_ids)}); + fuzzerutil::UpdateFunctionType( + ir_context, function->result_id(), message_.function_type_fresh_id(), + old_function_type->GetSingleWordInOperand(0), parameter_type_ids); } + auto new_parameter_kind = new_parameter_type->kind(); + // Make sure our changes are analyzed. ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); + + // If the |new_parameter_type_id| is not a pointer type, mark id as + // irrelevant so that we can replace its use with some other id. If the + // |new_parameter_type_id| is a pointer type, we cannot mark it with + // IdIsIrrelevant, because this pointer might be replaced by a pointer from + // original shader. This would change the semantics of the module. In the case + // of a pointer type we mark it with PointeeValueIsIrrelevant. + if (new_parameter_kind != opt::analysis::Type::kPointer) { + transformation_context->GetFactManager()->AddFactIdIsIrrelevant( + message_.parameter_fresh_id()); + } else { + transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( + message_.parameter_fresh_id()); + } } protobufs::Transformation TransformationAddParameter::ToMessage() const { @@ -150,16 +182,30 @@ bool TransformationAddParameter::IsParameterTypeSupported( case opt::analysis::Type::kBool: case opt::analysis::Type::kInteger: case opt::analysis::Type::kFloat: - case opt::analysis::Type::kArray: case opt::analysis::Type::kMatrix: case opt::analysis::Type::kVector: return true; + case opt::analysis::Type::kArray: + return IsParameterTypeSupported(*type.AsArray()->element_type()); case opt::analysis::Type::kStruct: return std::all_of(type.AsStruct()->element_types().begin(), type.AsStruct()->element_types().end(), [](const opt::analysis::Type* element_type) { return IsParameterTypeSupported(*element_type); }); + case opt::analysis::Type::kPointer: { + auto storage_class = type.AsPointer()->storage_class(); + switch (storage_class) { + case SpvStorageClassPrivate: + case SpvStorageClassFunction: + case SpvStorageClassWorkgroup: { + auto pointee_type = type.AsPointer()->pointee_type(); + return IsParameterTypeSupported(*pointee_type); + } + default: + return false; + } + } default: return false; } diff --git a/source/fuzz/transformation_add_parameter.h b/source/fuzz/transformation_add_parameter.h index e6b90192..361c01a8 100644 --- a/source/fuzz/transformation_add_parameter.h +++ b/source/fuzz/transformation_add_parameter.h @@ -29,14 +29,19 @@ class TransformationAddParameter : public Transformation { const protobufs::TransformationAddParameter& message); TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id, - uint32_t initializer_id, + uint32_t parameter_type_id, + std::map<uint32_t, uint32_t> call_parameter_ids, uint32_t function_type_fresh_id); // - |function_id| must be a valid result id of some non-entry-point function // in the module. - // - |initializer_id| must be a valid result id of some instruction in the - // module. Instruction's type must be supported by this transformation - // as specified by IsParameterTypeSupported function. + // - |parameter_type_id| is a type id of the new parameter. The type must be + // supported by this transformation as specified by IsParameterTypeSupported + // function. + // - |call_parameter_id| must map from every id of an OpFunctionCall + // instruction of this function to the id that will be passed as the new + // parameter at that call site. There could be no callers, therefore this + // map can be empty. // - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are // not equal. bool IsApplicable( @@ -46,8 +51,8 @@ class TransformationAddParameter : public Transformation { // - Creates a new OpFunctionParameter instruction with result id // |parameter_fresh_id| for the function with |function_id|. // - Adjusts function's type to include a new parameter. - // - Adds |initializer_id| as a new operand to every OpFunctionCall - // instruction that calls the function. + // - Adds an argument to every caller of the function to account for the added + // parameter. The argument is the value in |call_parameter_id| map. void Apply(opt::IRContext* ir_context, TransformationContext* transformation_context) const override; diff --git a/source/fuzz/transformation_add_relaxed_decoration.h b/source/fuzz/transformation_add_relaxed_decoration.h index 30c1abfa..1615461c 100644 --- a/source/fuzz/transformation_add_relaxed_decoration.h +++ b/source/fuzz/transformation_add_relaxed_decoration.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H -#define SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_ #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" @@ -59,4 +59,4 @@ class TransformationAddRelaxedDecoration : public Transformation { } // namespace fuzz } // namespace spvtools -#endif // SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_ diff --git a/source/fuzz/transformation_add_spec_constant_op.cpp b/source/fuzz/transformation_add_spec_constant_op.cpp index d6a7083f..b93725b9 100644 --- a/source/fuzz/transformation_add_spec_constant_op.cpp +++ b/source/fuzz/transformation_add_spec_constant_op.cpp @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/transformation_add_spec_constant_op.h" + #include <utility> #include "source/fuzz/fuzzer_util.h" -#include "source/fuzz/transformation_add_spec_constant_op.h" namespace spvtools { namespace fuzz { diff --git a/source/fuzz/transformation_add_synonym.cpp b/source/fuzz/transformation_add_synonym.cpp index 90e40540..bd9df132 100644 --- a/source/fuzz/transformation_add_synonym.cpp +++ b/source/fuzz/transformation_add_synonym.cpp @@ -68,6 +68,17 @@ bool TransformationAddSynonym::IsApplicable( return false; } + const auto* insert_before_inst_block = + ir_context->get_instr_block(insert_before_inst); + assert(insert_before_inst_block && + "|insert_before_inst| must be in some block"); + + if (transformation_context.GetFactManager()->BlockIsDead( + insert_before_inst_block->id())) { + // We don't create synonyms in dead blocks. + return false; + } + // Check that we can insert |message._synonymous_instruction| before // |message_.insert_before| instruction. We use OpIAdd to represent some // instruction that can produce a synonym. @@ -115,7 +126,7 @@ void TransformationAddSynonym::Apply( // Mark two ids as synonymous. transformation_context->GetFactManager()->AddFactDataSynonym( MakeDataDescriptor(message_.result_id(), {}), - MakeDataDescriptor(message_.synonym_fresh_id(), {}), ir_context); + MakeDataDescriptor(message_.synonym_fresh_id(), {})); } protobufs::Transformation TransformationAddSynonym::ToMessage() const { @@ -258,7 +269,7 @@ uint32_t TransformationAddSynonym::MaybeGetConstantId( case protobufs::TransformationAddSynonym::SUB_ZERO: case protobufs::TransformationAddSynonym::LOGICAL_OR: return fuzzerutil::MaybeGetZeroConstant( - ir_context, transformation_context, synonym_type_id); + ir_context, transformation_context, synonym_type_id, false); case protobufs::TransformationAddSynonym::MUL_ONE: case protobufs::TransformationAddSynonym::LOGICAL_AND: { auto synonym_type = ir_context->get_type_mgr()->GetType(synonym_type_id); @@ -272,12 +283,12 @@ uint32_t TransformationAddSynonym::MaybeGetConstantId( auto one_word = vector->element_type()->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u; if (auto scalar_one_id = fuzzerutil::MaybeGetScalarConstant( - ir_context, transformation_context, {one_word}, - element_type_id)) { + ir_context, transformation_context, {one_word}, element_type_id, + false)) { return fuzzerutil::MaybeGetCompositeConstant( ir_context, transformation_context, std::vector<uint32_t>(vector->element_count(), scalar_one_id), - synonym_type_id); + synonym_type_id, false); } return 0; @@ -285,7 +296,7 @@ uint32_t TransformationAddSynonym::MaybeGetConstantId( return fuzzerutil::MaybeGetScalarConstant( ir_context, transformation_context, {synonym_type->AsFloat() ? fuzzerutil::FloatToWord(1) : 1u}, - synonym_type_id); + synonym_type_id, false); } } default: diff --git a/source/fuzz/transformation_add_type_float.cpp b/source/fuzz/transformation_add_type_float.cpp index c0c434bb..9f43c3ed 100644 --- a/source/fuzz/transformation_add_type_float.cpp +++ b/source/fuzz/transformation_add_type_float.cpp @@ -36,6 +36,28 @@ bool TransformationAddTypeFloat::IsApplicable( return false; } + // Checks float type width capabilities. + switch (message_.width()) { + case 16: + // The Float16 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat16)) { + return false; + } + break; + case 32: + // No capabilities needed. + break; + case 64: + // The Float64 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat64)) { + return false; + } + break; + default: + assert(false && "Unexpected float type width"); + return false; + } + // Applicable if there is no float type with this width already declared in // the module. return fuzzerutil::MaybeGetFloatType(ir_context, message_.width()) == 0; diff --git a/source/fuzz/transformation_add_type_int.cpp b/source/fuzz/transformation_add_type_int.cpp index 20759fc8..e39a23df 100644 --- a/source/fuzz/transformation_add_type_int.cpp +++ b/source/fuzz/transformation_add_type_int.cpp @@ -38,6 +38,34 @@ bool TransformationAddTypeInt::IsApplicable( return false; } + // Checks integer type width capabilities. + switch (message_.width()) { + case 8: + // The Int8 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt8)) { + return false; + } + break; + case 16: + // The Int16 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16)) { + return false; + } + break; + case 32: + // No capabilities needed. + break; + case 64: + // The Int64 capability must be present. + if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64)) { + return false; + } + break; + default: + assert(false && "Unexpected integer type width"); + return false; + } + // Applicable if there is no int type with this width and signedness already // declared in the module. return fuzzerutil::MaybeGetIntegerType(ir_context, message_.width(), diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp index a7345a17..e8adefdd 100644 --- a/source/fuzz/transformation_add_type_struct.cpp +++ b/source/fuzz/transformation_add_type_struct.cpp @@ -44,6 +44,14 @@ bool TransformationAddTypeStruct::IsApplicable( // function type; both are illegal. return false; } + + // From the spec for the BuiltIn decoration: + // - When applied to a structure-type member, that structure type cannot + // be contained as a member of another structure type. + if (type->AsStruct() && + fuzzerutil::MembersHaveBuiltInDecoration(ir_context, member_type)) { + return false; + } } return true; } diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h index 86a532d2..c9d0cfdb 100644 --- a/source/fuzz/transformation_add_type_struct.h +++ b/source/fuzz/transformation_add_type_struct.h @@ -35,6 +35,9 @@ class TransformationAddTypeStruct : public Transformation { // - |message_.fresh_id| must be a fresh id // - |message_.member_type_id| must be a sequence of non-function type ids + // - |message_.member_type_id| may not contain a result id of an OpTypeStruct + // instruction with BuiltIn members (i.e. members of the struct are + // decorated via OpMemberDecorate with BuiltIn decoration). bool IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const override; diff --git a/source/fuzz/transformation_composite_construct.cpp b/source/fuzz/transformation_composite_construct.cpp index 9cef7ffc..8489824b 100644 --- a/source/fuzz/transformation_composite_construct.cpp +++ b/source/fuzz/transformation_composite_construct.cpp @@ -144,11 +144,6 @@ void TransformationCompositeConstruct::Apply( ir_context->get_type_mgr()->GetType(message_.composite_type_id()); uint32_t index = 0; for (auto component : message_.component()) { - if (transformation_context->GetFactManager()->IdIsIrrelevant(component)) { - // Irrelevant ids do not participate in DataSynonym facts. - continue; - } - auto component_type = ir_context->get_type_mgr()->GetType( ir_context->get_def_use_mgr()->GetDef(component)->type_id()); if (composite_type->AsVector() && component_type->AsVector()) { @@ -163,17 +158,23 @@ void TransformationCompositeConstruct::Apply( for (uint32_t subvector_index = 0; subvector_index < component_type->AsVector()->element_count(); subvector_index++) { - transformation_context->GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(component, {subvector_index}), - MakeDataDescriptor(message_.fresh_id(), {index}), ir_context); + if (!transformation_context->GetFactManager()->IdIsIrrelevant( + component)) { + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(component, {subvector_index}), + MakeDataDescriptor(message_.fresh_id(), {index})); + } index++; } } else { // The other cases are simple: the component is made directly synonymous // with the element of the composite being constructed. - transformation_context->GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(component, {}), - MakeDataDescriptor(message_.fresh_id(), {index}), ir_context); + if (!transformation_context->GetFactManager()->IdIsIrrelevant( + component)) { + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(component, {}), + MakeDataDescriptor(message_.fresh_id(), {index})); + } index++; } } diff --git a/source/fuzz/transformation_composite_extract.cpp b/source/fuzz/transformation_composite_extract.cpp index 9f4d5546..ed9ab001 100644 --- a/source/fuzz/transformation_composite_extract.cpp +++ b/source/fuzz/transformation_composite_extract.cpp @@ -125,8 +125,7 @@ void TransformationCompositeExtract::Apply( protobufs::DataDescriptor data_descriptor_for_result_id = MakeDataDescriptor(message_.fresh_id(), {}); transformation_context->GetFactManager()->AddFactDataSynonym( - data_descriptor_for_extracted_element, data_descriptor_for_result_id, - ir_context); + data_descriptor_for_extracted_element, data_descriptor_for_result_id); } } diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp new file mode 100644 index 00000000..f7d1ac56 --- /dev/null +++ b/source/fuzz/transformation_composite_insert.cpp @@ -0,0 +1,224 @@ +// Copyright (c) 2020 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 "transformation_composite_insert.h" + +#include "source/fuzz/fuzzer_pass_add_composite_inserts.h" +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationCompositeInsert::TransformationCompositeInsert( + const spvtools::fuzz::protobufs::TransformationCompositeInsert& message) + : message_(message) {} + +TransformationCompositeInsert::TransformationCompositeInsert( + const protobufs::InstructionDescriptor& instruction_to_insert_before, + uint32_t fresh_id, uint32_t composite_id, uint32_t object_id, + const std::vector<uint32_t>& index) { + *message_.mutable_instruction_to_insert_before() = + instruction_to_insert_before; + message_.set_fresh_id(fresh_id); + message_.set_composite_id(composite_id); + message_.set_object_id(object_id); + for (auto an_index : index) { + message_.add_index(an_index); + } +} + +bool TransformationCompositeInsert::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.fresh_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + + // |message_.composite_id| must refer to an existing composite value. + auto composite = + ir_context->get_def_use_mgr()->GetDef(message_.composite_id()); + + if (!IsCompositeInstructionSupported(ir_context, composite)) { + return false; + } + + // The indices in |message_.index| must be suitable for indexing into + // |composite->type_id()|. + auto component_to_be_replaced_type_id = fuzzerutil::WalkCompositeTypeIndices( + ir_context, composite->type_id(), message_.index()); + if (component_to_be_replaced_type_id == 0) { + return false; + } + + // The instruction having the id of |message_.object_id| must be defined. + auto object_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.object_id()); + if (object_instruction == nullptr || object_instruction->type_id() == 0) { + return false; + } + + // We ignore pointers for now. + auto object_instruction_type = + ir_context->get_type_mgr()->GetType(object_instruction->type_id()); + if (object_instruction_type->AsPointer() != nullptr) { + return false; + } + + // The type id of the object having |message_.object_id| and the type id of + // the component of the composite at index |message_.index| must be the same. + if (component_to_be_replaced_type_id != object_instruction->type_id()) { + return false; + } + + // |message_.instruction_to_insert_before| must be a defined instruction. + auto instruction_to_insert_before = + FindInstruction(message_.instruction_to_insert_before(), ir_context); + if (instruction_to_insert_before == nullptr) { + return false; + } + + // |message_.composite_id| and |message_.object_id| must be available before + // the |message_.instruction_to_insert_before|. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, instruction_to_insert_before, message_.composite_id())) { + return false; + } + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, instruction_to_insert_before, message_.object_id())) { + return false; + } + + // It must be possible to insert an OpCompositeInsert before this + // instruction. + return fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpCompositeInsert, instruction_to_insert_before); +} + +void TransformationCompositeInsert::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + // |message_.struct_fresh_id| must be fresh. + assert(fuzzerutil::IsFreshId(ir_context, message_.fresh_id()) && + "|message_.fresh_id| must be fresh"); + + std::vector<uint32_t> index = + fuzzerutil::RepeatedFieldToVector(message_.index()); + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.object_id()}}); + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}}); + for (auto i : index) { + in_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}); + } + auto composite_type_id = + fuzzerutil::GetTypeId(ir_context, message_.composite_id()); + + FindInstruction(message_.instruction_to_insert_before(), ir_context) + ->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeInsert, composite_type_id, + message_.fresh_id(), std::move(in_operands))); + + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // We have modified the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Add facts about synonyms. Every element which hasn't been changed in + // the copy is synonymous to the corresponding element in the original + // composite which has id |message_.composite_id|. For every index that is a + // prefix of |index|, the components different from the one that + // contains the inserted object are synonymous with corresponding + // elements in the original composite. + + // If |composite_id| is irrelevant then don't add any synonyms. + if (transformation_context->GetFactManager()->IdIsIrrelevant( + message_.composite_id())) { + return; + } + uint32_t current_node_type_id = composite_type_id; + std::vector<uint32_t> current_index; + + for (uint32_t current_level = 0; current_level < index.size(); + current_level++) { + auto current_node_type_inst = + ir_context->get_def_use_mgr()->GetDef(current_node_type_id); + uint32_t index_to_skip = index[current_level]; + uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex( + *current_node_type_inst, ir_context); + + // Update the current_node_type_id. + current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex( + ir_context, current_node_type_id, index_to_skip); + + for (uint32_t i = 0; i < num_of_components; i++) { + if (i == index_to_skip) { + continue; + } + current_index.push_back(i); + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3659): + // Google C++ guide restricts the use of r-value references. + // https://google.github.io/styleguide/cppguide.html#Rvalue_references + // Consider changing the signature of MakeDataDescriptor() + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.fresh_id(), + std::vector<uint32_t>(current_index)), + MakeDataDescriptor(message_.composite_id(), + std::vector<uint32_t>(current_index))); + current_index.pop_back(); + } + // Store the prefix of the |index|. + current_index.push_back(index[current_level]); + } + // The element which has been changed is synonymous to the found object + // itself. Add this fact only if |object_id| is not irrelevant. + if (!transformation_context->GetFactManager()->IdIsIrrelevant( + message_.object_id())) { + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.object_id(), {}), + MakeDataDescriptor(message_.fresh_id(), std::vector<uint32_t>(index))); + } +} + +protobufs::Transformation TransformationCompositeInsert::ToMessage() const { + protobufs::Transformation result; + *result.mutable_composite_insert() = message_; + return result; +} + +bool TransformationCompositeInsert::IsCompositeInstructionSupported( + opt::IRContext* ir_context, opt::Instruction* instruction) { + if (instruction == nullptr) { + return false; + } + if (instruction->result_id() == 0 || instruction->type_id() == 0) { + return false; + } + auto composite_type = + ir_context->get_type_mgr()->GetType(instruction->type_id()); + if (!fuzzerutil::IsCompositeType(composite_type)) { + return false; + } + + // Empty composites are not supported. + auto instruction_type_inst = + ir_context->get_def_use_mgr()->GetDef(instruction->type_id()); + if (fuzzerutil::GetBoundForCompositeIndex(*instruction_type_inst, + ir_context) == 0) { + return false; + } + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_composite_insert.h b/source/fuzz/transformation_composite_insert.h new file mode 100644 index 00000000..c1320fce --- /dev/null +++ b/source/fuzz/transformation_composite_insert.h @@ -0,0 +1,72 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_INSERT_H_ +#define SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_INSERT_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationCompositeInsert : public Transformation { + public: + explicit TransformationCompositeInsert( + const protobufs::TransformationCompositeInsert& message); + + TransformationCompositeInsert( + const protobufs::InstructionDescriptor& instruction_to_insert_before, + uint32_t fresh_id, uint32_t composite_id, uint32_t object_id, + const std::vector<uint32_t>& index); + + // - |message_.fresh_id| must be fresh. + // - |message_.composite_id| must refer to an existing composite value. + // - |message_.index| must refer to a correct index in the composite. + // - The type id of the object and the type id of the component of the + // composite at index |message_.index| must be the same. + // - |message_.instruction_to_insert_before| must refer to a defined + // instruction. + // - It must be possible to insert OpCompositeInsert before + // |instruction_to_insert_before|. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Adds an instruction OpCompositeInsert before + // |instruction_to_insert_before|, which creates a new composite from + // |composite_id| by inserting |object_id| at the specified |index|. + // Synonyms are created between those components which are identical in the + // original and the modified composite and between the inserted object and its + // copy in the modified composite. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks if |instruction| is a instruction of a composite type supported by + // this transformation. + static bool IsCompositeInstructionSupported(opt::IRContext* ir_context, + opt::Instruction* instruction); + + private: + protobufs::TransformationCompositeInsert message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_INSERT_H_ diff --git a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp index ff3ba3c6..7e666d20 100644 --- a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp +++ b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp @@ -35,10 +35,10 @@ bool TransformationComputeDataSynonymFactClosure::IsApplicable( } void TransformationComputeDataSynonymFactClosure::Apply( - opt::IRContext* ir_context, + opt::IRContext* /*unused*/, TransformationContext* transformation_context) const { transformation_context->GetFactManager()->ComputeClosureOfFacts( - ir_context, message_.maximum_equivalence_class_size()); + message_.maximum_equivalence_class_size()); } protobufs::Transformation diff --git a/source/fuzz/transformation_context.cpp b/source/fuzz/transformation_context.cpp index 6c2dfdff..bd0e4064 100644 --- a/source/fuzz/transformation_context.cpp +++ b/source/fuzz/transformation_context.cpp @@ -14,12 +14,43 @@ #include "source/fuzz/transformation_context.h" +#include <cassert> + +#include "source/util/make_unique.h" + namespace spvtools { namespace fuzz { +namespace { + +// An overflow id source that should never be used: its methods assert false. +// This is the right id source for use during fuzzing, when overflow ids should +// never be required. +class NullOverflowIdSource : public OverflowIdSource { + bool HasOverflowIds() const override { + assert(false && "Bad attempt to query whether overflow ids are available."); + return false; + } + + uint32_t GetNextOverflowId() override { + assert(false && "Bad attempt to request an overflow id."); + return 0; + } +}; + +} // namespace TransformationContext::TransformationContext( FactManager* fact_manager, spv_validator_options validator_options) - : fact_manager_(fact_manager), validator_options_(validator_options) {} + : fact_manager_(fact_manager), + validator_options_(validator_options), + overflow_id_source_(MakeUnique<NullOverflowIdSource>()) {} + +TransformationContext::TransformationContext( + FactManager* fact_manager, spv_validator_options validator_options, + std::unique_ptr<OverflowIdSource> overflow_id_source) + : fact_manager_(fact_manager), + validator_options_(validator_options), + overflow_id_source_(std::move(overflow_id_source)) {} TransformationContext::~TransformationContext() = default; diff --git a/source/fuzz/transformation_context.h b/source/fuzz/transformation_context.h index 37e15a22..c76a7bef 100644 --- a/source/fuzz/transformation_context.h +++ b/source/fuzz/transformation_context.h @@ -15,7 +15,10 @@ #ifndef SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_ #define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_ -#include "source/fuzz/fact_manager.h" +#include <memory> + +#include "source/fuzz/fact_manager/fact_manager.h" +#include "source/fuzz/overflow_id_source.h" #include "spirv-tools/libspirv.hpp" namespace spvtools { @@ -26,16 +29,29 @@ namespace fuzz { class TransformationContext { public: // Constructs a transformation context with a given fact manager and validator - // options. + // options. Overflow ids are not available from a transformation context + // constructed in this way. TransformationContext(FactManager* fact_manager, spv_validator_options validator_options); + // Constructs a transformation context with a given fact manager, validator + // options and overflow id source. + TransformationContext(FactManager* fact_manager, + spv_validator_options validator_options, + std::unique_ptr<OverflowIdSource> overflow_id_source); + ~TransformationContext(); FactManager* GetFactManager() { return fact_manager_; } const FactManager* GetFactManager() const { return fact_manager_; } + OverflowIdSource* GetOverflowIdSource() { return overflow_id_source_.get(); } + + const OverflowIdSource* GetOverflowIdSource() const { + return overflow_id_source_.get(); + } + spv_validator_options GetValidatorOptions() const { return validator_options_; } @@ -48,6 +64,8 @@ class TransformationContext { // Options to control validation when deciding whether transformations can be // applied. spv_validator_options validator_options_; + + std::unique_ptr<OverflowIdSource> overflow_id_source_; }; } // namespace fuzz diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp new file mode 100644 index 00000000..8d099ae1 --- /dev/null +++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp @@ -0,0 +1,593 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_duplicate_region_with_selection.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationDuplicateRegionWithSelection:: + TransformationDuplicateRegionWithSelection( + const spvtools::fuzz::protobufs:: + TransformationDuplicateRegionWithSelection& message) + : message_(message) {} + +TransformationDuplicateRegionWithSelection:: + TransformationDuplicateRegionWithSelection( + uint32_t new_entry_fresh_id, uint32_t condition_id, + uint32_t merge_label_fresh_id, uint32_t entry_block_id, + uint32_t exit_block_id, + const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label, + const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id, + const std::map<uint32_t, uint32_t>& original_id_to_phi_id) { + message_.set_new_entry_fresh_id(new_entry_fresh_id); + message_.set_condition_id(condition_id); + message_.set_merge_label_fresh_id(merge_label_fresh_id); + message_.set_entry_block_id(entry_block_id); + message_.set_exit_block_id(exit_block_id); + *message_.mutable_original_label_to_duplicate_label() = + fuzzerutil::MapToRepeatedUInt32Pair(original_label_to_duplicate_label); + *message_.mutable_original_id_to_duplicate_id() = + fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_duplicate_id); + *message_.mutable_original_id_to_phi_id() = + fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_phi_id); +} + +bool TransformationDuplicateRegionWithSelection::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // Instruction with the id |condition_id| must exist and must be of a bool + // type. + auto bool_instr = + ir_context->get_def_use_mgr()->GetDef(message_.condition_id()); + if (bool_instr == nullptr || !bool_instr->type_id()) { + return false; + } + if (!ir_context->get_type_mgr()->GetType(bool_instr->type_id())->AsBool()) { + return false; + } + + // The |new_entry_fresh_id| must be fresh and distinct. + std::set<uint32_t> ids_used_by_this_transformation; + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_entry_fresh_id(), ir_context, + &ids_used_by_this_transformation)) { + return false; + } + + // The |merge_label_fresh_id| must be fresh and distinct. + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.merge_label_fresh_id(), ir_context, + &ids_used_by_this_transformation)) { + return false; + } + + // The entry and exit block ids must refer to blocks. + for (auto block_id : {message_.entry_block_id(), message_.exit_block_id()}) { + auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id); + if (!block_label || block_label->opcode() != SpvOpLabel) { + return false; + } + } + auto entry_block = ir_context->cfg()->block(message_.entry_block_id()); + auto exit_block = ir_context->cfg()->block(message_.exit_block_id()); + + // The |entry_block| and the |exit_block| must be in the same function. + if (entry_block->GetParent() != exit_block->GetParent()) { + return false; + } + + // The |entry_block| must dominate the |exit_block|. + auto dominator_analysis = + ir_context->GetDominatorAnalysis(entry_block->GetParent()); + if (!dominator_analysis->Dominates(entry_block, exit_block)) { + return false; + } + + // The |exit_block| must post-dominate the |entry_block|. + auto postdominator_analysis = + ir_context->GetPostDominatorAnalysis(entry_block->GetParent()); + if (!postdominator_analysis->Dominates(exit_block, entry_block)) { + return false; + } + + auto enclosing_function = entry_block->GetParent(); + + // |entry_block| cannot be the first block of the |enclosing_function|. + if (&*enclosing_function->begin() == entry_block) { + return false; + } + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): + // The following code has been copied from TransformationOutlineFunction. + // Consider refactoring to avoid duplication. + auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block); + + // Check whether |region_set| really is a single-entry single-exit region, and + // also check whether structured control flow constructs and their merge + // and continue constructs are either wholly in or wholly out of the region - + // e.g. avoid the situation where the region contains the head of a loop but + // not the loop's continue construct. + // + // This is achieved by going through every block in the |enclosing_function| + for (auto& block : *enclosing_function) { + if (&block == exit_block) { + // It is not OK for the exit block to head a loop construct or a + // conditional construct. + if (block.GetMergeInst()) { + return false; + } + continue; + } + if (region_set.count(&block) != 0) { + // The block is in the region and is not the region's exit block. Let's + // see whether all of the block's successors are in the region. If they + // are not, the region is not single-entry single-exit. + bool all_successors_in_region = true; + block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context, + ®ion_set](uint32_t successor) -> bool { + if (region_set.count(ir_context->cfg()->block(successor)) == 0) { + all_successors_in_region = false; + return false; + } + return true; + }); + if (!all_successors_in_region) { + return false; + } + } + + if (auto merge = block.GetMergeInst()) { + // The block is a loop or selection header. The header and its + // associated merge block must be both in the region or both be + // outside the region. + auto merge_block = + ir_context->cfg()->block(merge->GetSingleWordOperand(0)); + if (region_set.count(&block) != region_set.count(merge_block)) { + return false; + } + } + + if (auto loop_merge = block.GetLoopMergeInst()) { + // The continue target of a loop must be within the region if and only if + // the header of the loop is. + auto continue_target = + ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1)); + // The continue target is a single-entry, single-exit region. Therefore, + // if the continue target is the exit block, the region might not contain + // the loop header. However, we would like to exclude this situation, + // since it would be impossible for the modified exit block to branch to + // the new selection merge block. In this scenario the exit block is + // required to branch to the loop header. + if (region_set.count(&block) != region_set.count(continue_target)) { + return false; + } + } + } + + // Get the maps from the protobuf. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3786): + // Consider additionally providing overflow ids to make this + // transformation more applicable when shrinking. + std::map<uint32_t, uint32_t> original_label_to_duplicate_label = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_label_to_duplicate_label()); + + std::map<uint32_t, uint32_t> original_id_to_duplicate_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_id_to_duplicate_id()); + + std::map<uint32_t, uint32_t> original_id_to_phi_id = + fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id()); + + for (auto block : region_set) { + auto label = + ir_context->get_def_use_mgr()->GetDef(block->id())->result_id(); + // The label of every block in the region must be present in the map + // |original_label_to_duplicate_label|. + if (original_label_to_duplicate_label.count(label) == 0) { + return false; + } + auto duplicate_label = original_label_to_duplicate_label[label]; + // Each id assigned to labels in the region must be distinct and fresh. + if (!duplicate_label || + !CheckIdIsFreshAndNotUsedByThisTransformation( + duplicate_label, ir_context, &ids_used_by_this_transformation)) { + return false; + } + for (auto instr : *block) { + if (!instr.HasResultId()) { + continue; + } + // Every instruction with a result id in the region must be present in the + // map |original_id_to_duplicate_id|. + if (original_id_to_duplicate_id.count(instr.result_id()) == 0) { + return false; + } + auto duplicate_id = original_id_to_duplicate_id[instr.result_id()]; + // Id assigned to this result id in the region must be distinct and fresh. + if (!duplicate_id || + !CheckIdIsFreshAndNotUsedByThisTransformation( + duplicate_id, ir_context, &ids_used_by_this_transformation)) { + return false; + } + if (&instr == &*exit_block->tail() || + fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, &*exit_block->tail(), instr.result_id())) { + // Every instruction with a result id available at the end of the region + // must be present in the map |original_id_to_phi_id|. + if (original_id_to_phi_id.count(instr.result_id()) == 0) { + return false; + } + // Using pointers with OpPhi requires capability VariablePointers. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3787): + // Consider not adding OpPhi instructions for the pointers which are + // unused after the region, so that the transformation could be + // still applicable. + if (ir_context->get_type_mgr()->GetType(instr.type_id())->AsPointer() && + !ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointers)) { + return false; + } + auto phi_id = original_id_to_phi_id[instr.result_id()]; + // Id assigned to this result id in the region must be distinct and + // fresh. + if (!phi_id || + !CheckIdIsFreshAndNotUsedByThisTransformation( + phi_id, ir_context, &ids_used_by_this_transformation)) { + return false; + } + } + } + } + return true; +} + +void TransformationDuplicateRegionWithSelection::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_entry_fresh_id()); + fuzzerutil::UpdateModuleIdBound(ir_context, message_.merge_label_fresh_id()); + + // Create the new entry block containing the main conditional instruction. Set + // its parent to the parent of the original entry block, since it is located + // in the same function. + std::unique_ptr<opt::BasicBlock> new_entry_block = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, message_.new_entry_fresh_id(), + opt::Instruction::OperandList())); + auto entry_block = ir_context->cfg()->block(message_.entry_block_id()); + auto enclosing_function = entry_block->GetParent(); + auto exit_block = ir_context->cfg()->block(message_.exit_block_id()); + + // Get the blocks contained in the region. + std::set<opt::BasicBlock*> region_blocks = + GetRegionBlocks(ir_context, entry_block, exit_block); + + // Construct the merge block. + std::unique_ptr<opt::BasicBlock> merge_block = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(opt::Instruction( + ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(), + opt::Instruction::OperandList()))); + + // Get the maps from the protobuf. + std::map<uint32_t, uint32_t> original_label_to_duplicate_label = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_label_to_duplicate_label()); + + std::map<uint32_t, uint32_t> original_id_to_duplicate_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_id_to_duplicate_id()); + + std::map<uint32_t, uint32_t> original_id_to_phi_id = + fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id()); + + // Before adding duplicate blocks, we need to update the OpPhi instructions in + // the successors of the |exit_block|. We know that the execution of the + // transformed region will end in |merge_block|. Hence, we need to change all + // occurrences of the label id of the |exit_block| to the label id of the + // |merge_block|. + exit_block->ForEachSuccessorLabel([this, ir_context](uint32_t label_id) { + auto block = ir_context->cfg()->block(label_id); + for (auto& instr : *block) { + if (instr.opcode() == SpvOpPhi) { + instr.ForEachId([this](uint32_t* id) { + if (*id == message_.exit_block_id()) { + *id = message_.merge_label_fresh_id(); + } + }); + } + } + }); + + // Get vector of predecessors id of |entry_block|. Remove any duplicate + // values. + auto entry_block_preds = ir_context->cfg()->preds(entry_block->id()); + std::sort(entry_block_preds.begin(), entry_block_preds.end()); + entry_block_preds.erase( + unique(entry_block_preds.begin(), entry_block_preds.end()), + entry_block_preds.end()); + // We know that |entry_block| has only one predecessor, since the region is + // single-entry, single-exit and its constructs and their merge blocks must be + // either wholly within or wholly outside of the region. + assert(entry_block_preds.size() == 1 && + "The entry of the region to be duplicated can have only one " + "predecessor."); + uint32_t entry_block_pred_id = + ir_context->get_instr_block(entry_block_preds[0])->id(); + // Update all the OpPhi instructions in the |entry_block|. Change every + // occurrence of |entry_block_pred_id| to the id of |new_entry|, because we + // will insert |new_entry| before |entry_block|. + for (auto& instr : *entry_block) { + if (instr.opcode() == SpvOpPhi) { + instr.ForEachId([this, entry_block_pred_id](uint32_t* id) { + if (*id == entry_block_pred_id) { + *id = message_.new_entry_fresh_id(); + } + }); + } + } + + // Duplication of blocks will invalidate iterators. Store all the blocks from + // the enclosing function. + std::vector<opt::BasicBlock*> blocks; + for (auto& block : *enclosing_function) { + blocks.push_back(&block); + } + + opt::BasicBlock* previous_block = nullptr; + opt::BasicBlock* duplicated_exit_block = nullptr; + // Iterate over all blocks of the function to duplicate blocks of the original + // region and their instructions. + for (auto& block : blocks) { + // The block must be contained in the region. + if (region_blocks.count(block) == 0) { + continue; + } + + fuzzerutil::UpdateModuleIdBound( + ir_context, original_label_to_duplicate_label[block->id()]); + + std::unique_ptr<opt::BasicBlock> duplicated_block = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, + original_label_to_duplicate_label[block->id()], + opt::Instruction::OperandList())); + + for (auto& instr : *block) { + // Case where an instruction is the terminator of the exit block is + // handled separately. + if (block == exit_block && instr.IsBlockTerminator()) { + switch (instr.opcode()) { + case SpvOpBranch: + case SpvOpReturn: + case SpvOpReturnValue: + case SpvOpUnreachable: + case SpvOpKill: + continue; + default: + assert(false && + "Unexpected terminator for |exit_block| of the region."); + } + } + // Duplicate the instruction. + auto cloned_instr = instr.Clone(ir_context); + duplicated_block->AddInstruction( + std::unique_ptr<opt::Instruction>(cloned_instr)); + + fuzzerutil::UpdateModuleIdBound( + ir_context, original_id_to_duplicate_id[instr.result_id()]); + + // If an id from the original region was used in this instruction, + // replace it with the value from |original_id_to_duplicate_id|. + // If a label from the original region was used in this instruction, + // replace it with the value from |original_label_to_duplicate_label|. + cloned_instr->ForEachId( + [original_id_to_duplicate_id, + original_label_to_duplicate_label](uint32_t* op) { + if (original_id_to_duplicate_id.count(*op) != 0) { + *op = original_id_to_duplicate_id.at(*op); + } + if (original_label_to_duplicate_label.count(*op) != 0) { + *op = original_label_to_duplicate_label.at(*op); + } + }); + } + + // If the block is the first duplicated block, insert it after the exit + // block of the original region. Otherwise, insert it after the preceding + // one. + auto duplicated_block_ptr = duplicated_block.get(); + if (previous_block) { + enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block), + previous_block); + } else { + enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block), + exit_block); + } + previous_block = duplicated_block_ptr; + if (block == exit_block) { + // After execution of the loop, this variable stores a pointer to the last + // duplicated block. + duplicated_exit_block = duplicated_block_ptr; + } + } + + for (auto& block : region_blocks) { + for (auto& instr : *block) { + if (instr.result_id() != 0 && + (&instr == &*exit_block->tail() || + fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, &*exit_block->tail(), instr.result_id()))) { + // Add the OpPhi instruction for every result id that is + // available at the end of the region (the last instruction + // of the |exit_block|) + merge_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpPhi, instr.type_id(), + original_id_to_phi_id[instr.result_id()], + opt::Instruction::OperandList({ + {SPV_OPERAND_TYPE_ID, {instr.result_id()}}, + {SPV_OPERAND_TYPE_ID, {exit_block->id()}}, + {SPV_OPERAND_TYPE_ID, + {original_id_to_duplicate_id[instr.result_id()]}}, + {SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}}, + }))); + + fuzzerutil::UpdateModuleIdBound( + ir_context, original_id_to_phi_id[instr.result_id()]); + + // If the instruction has been remapped by an OpPhi, look + // for all its uses outside of the region and outside of the + // merge block (to not overwrite just added instructions in + // the merge block) and replace the original instruction id + // with the id of the corresponding OpPhi instruction. + ir_context->get_def_use_mgr()->ForEachUse( + &instr, + [ir_context, &instr, region_blocks, original_id_to_phi_id, + &merge_block](opt::Instruction* user, uint32_t operand_index) { + auto user_block = ir_context->get_instr_block(user); + if ((region_blocks.find(user_block) != region_blocks.end()) || + user_block == merge_block.get()) { + return; + } + user->SetOperand(operand_index, + {original_id_to_phi_id.at(instr.result_id())}); + }); + } + } + } + + // Construct a conditional instruction in the |new_entry_block|. + // If the condition is true, the execution proceeds in the + // |entry_block| of the original region. If the condition is + // false, the execution proceeds in the first block of the + // duplicated region. + new_entry_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpSelectionMerge, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}}))); + + new_entry_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranchConditional, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.condition_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.entry_block_id()}}, + {SPV_OPERAND_TYPE_ID, + {original_label_to_duplicate_label[message_.entry_block_id()]}}}))); + + // Move the terminator of |exit_block| to the end of + // |merge_block|. + auto exit_block_terminator = exit_block->terminator(); + auto cloned_instr = exit_block_terminator->Clone(ir_context); + merge_block->AddInstruction(std::unique_ptr<opt::Instruction>(cloned_instr)); + ir_context->KillInst(exit_block_terminator); + + // Add OpBranch instruction to the merge block at the end of + // |exit_block| and at the end of |duplicated_exit_block|, so that + // the execution proceeds in the |merge_block|. + opt::Instruction merge_branch_instr = opt::Instruction( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}})); + exit_block->AddInstruction(MakeUnique<opt::Instruction>(merge_branch_instr)); + duplicated_exit_block->AddInstruction( + std::unique_ptr<opt::Instruction>(merge_branch_instr.Clone(ir_context))); + + // Execution needs to start in the |new_entry_block|. Change all + // the uses of |entry_block_label_instr| outside of the original + // region to |message_.new_entry_fresh_id|. + auto entry_block_label_instr = + ir_context->get_def_use_mgr()->GetDef(message_.entry_block_id()); + ir_context->get_def_use_mgr()->ForEachUse( + entry_block_label_instr, + [this, ir_context, region_blocks](opt::Instruction* user, + uint32_t operand_index) { + auto user_block = ir_context->get_instr_block(user); + if ((region_blocks.count(user_block) != 0)) { + return; + } + switch (user->opcode()) { + case SpvOpSwitch: + case SpvOpBranch: + case SpvOpBranchConditional: + case SpvOpLoopMerge: + case SpvOpSelectionMerge: { + user->SetOperand(operand_index, {message_.new_entry_fresh_id()}); + } break; + case SpvOpName: + break; + default: + assert(false && + "The label id cannot be used by instructions " + "other than " + "OpSwitch, OpBranch, OpBranchConditional, " + "OpLoopMerge, " + "OpSelectionMerge"); + } + }); + + // Insert the merge block after the |duplicated_exit_block| (the + // last duplicated block). + enclosing_function->InsertBasicBlockAfter(std::move(merge_block), + duplicated_exit_block); + + // Insert the |new_entry_block| before the entry block of the + // original region. + enclosing_function->InsertBasicBlockBefore(std::move(new_entry_block), + entry_block); + + // Since we have changed the module, most of the analysis are now + // invalid. We can invalidate analyses now after all of the blocks + // have been registered. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785): +// The following method has been copied from +// TransformationOutlineFunction. Consider refactoring to avoid +// duplication. +std::set<opt::BasicBlock*> +TransformationDuplicateRegionWithSelection::GetRegionBlocks( + opt::IRContext* ir_context, opt::BasicBlock* entry_block, + opt::BasicBlock* exit_block) { + auto enclosing_function = entry_block->GetParent(); + auto dominator_analysis = + ir_context->GetDominatorAnalysis(enclosing_function); + auto postdominator_analysis = + ir_context->GetPostDominatorAnalysis(enclosing_function); + + // A block belongs to a region between the entry block and the exit + // block if and only if it is dominated by the entry block and + // post-dominated by the exit block. + std::set<opt::BasicBlock*> result; + for (auto& block : *enclosing_function) { + if (dominator_analysis->Dominates(entry_block, &block) && + postdominator_analysis->Dominates(exit_block, &block)) { + result.insert(&block); + } + } + return result; +} + +protobufs::Transformation +TransformationDuplicateRegionWithSelection::ToMessage() const { + protobufs::Transformation result; + *result.mutable_duplicate_region_with_selection() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_duplicate_region_with_selection.h b/source/fuzz/transformation_duplicate_region_with_selection.h new file mode 100644 index 00000000..a1a2a893 --- /dev/null +++ b/source/fuzz/transformation_duplicate_region_with_selection.h @@ -0,0 +1,78 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationDuplicateRegionWithSelection : public Transformation { + public: + explicit TransformationDuplicateRegionWithSelection( + const protobufs::TransformationDuplicateRegionWithSelection& message); + + explicit TransformationDuplicateRegionWithSelection( + uint32_t new_entry_fresh_id, uint32_t condition_id, + uint32_t merge_label_fresh_id, uint32_t entry_block_id, + uint32_t exit_block_id, + const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label, + const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id, + const std::map<uint32_t, uint32_t>& original_id_to_phi_id); + + // - |new_entry_fresh_id|, |merge_label_fresh_id| must be fresh and distinct. + // - |condition_id| must refer to a valid instruction of boolean type. + // - |entry_block_id| and |exit_block_id| must refer to valid blocks and they + // must form a single-entry, single-exit region. Its constructs and their + // merge blocks must be either wholly within or wholly outside of the + // region. + // - |original_label_to_duplicate_label| must contain at least a key for every + // block in the original region. + // - |original_id_to_duplicate_id| must contain at least a key for every + // result id in the original region. + // - |original_id_to_phi_id| must contain at least a key for every result id + // available at the end of the original region. + // - In each of these three maps, each value must be a distinct, fresh id. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // A transformation that inserts a conditional statement with a boolean + // expression of arbitrary value and duplicates a given single-entry, + // single-exit region, so that it is present in each conditional branch and + // will be executed regardless of which branch will be taken. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + // Returns the set of blocks dominated by |entry_block| and post-dominated + // by |exit_block|. + static std::set<opt::BasicBlock*> GetRegionBlocks( + opt::IRContext* ir_context, opt::BasicBlock* entry_block, + opt::BasicBlock* exit_block); + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationDuplicateRegionWithSelection message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_ diff --git a/source/fuzz/transformation_equation_instruction.cpp b/source/fuzz/transformation_equation_instruction.cpp index e27cd297..cf2e9b1e 100644 --- a/source/fuzz/transformation_equation_instruction.cpp +++ b/source/fuzz/transformation_equation_instruction.cpp @@ -93,8 +93,7 @@ void TransformationEquationInstruction::Apply( ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); transformation_context->GetFactManager()->AddFactIdEquation( - message_.fresh_id(), static_cast<SpvOp>(message_.opcode()), rhs_id, - ir_context); + message_.fresh_id(), static_cast<SpvOp>(message_.opcode()), rhs_id); } protobufs::Transformation TransformationEquationInstruction::ToMessage() const { diff --git a/source/fuzz/transformation_flatten_conditional_branch.cpp b/source/fuzz/transformation_flatten_conditional_branch.cpp new file mode 100644 index 00000000..09e93e75 --- /dev/null +++ b/source/fuzz/transformation_flatten_conditional_branch.cpp @@ -0,0 +1,703 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_flatten_conditional_branch.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch( + const protobufs::TransformationFlattenConditionalBranch& message) + : message_(message) {} + +TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch( + uint32_t header_block_id, bool true_branch_first, + const std::vector<protobufs::SideEffectWrapperInfo>& + side_effect_wrappers_info) { + message_.set_header_block_id(header_block_id); + message_.set_true_branch_first(true_branch_first); + for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) { + *message_.add_side_effect_wrapper_info() = side_effect_wrapper_info; + } +} + +bool TransformationFlattenConditionalBranch::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + uint32_t header_block_id = message_.header_block_id(); + auto header_block = fuzzerutil::MaybeFindBlock(ir_context, header_block_id); + + // The block must have been found and it must be a selection header. + if (!header_block || !header_block->GetMergeInst() || + header_block->GetMergeInst()->opcode() != SpvOpSelectionMerge) { + return false; + } + + // The header block must end with an OpBranchConditional instruction. + if (header_block->terminator()->opcode() != SpvOpBranchConditional) { + return false; + } + + // Use a set to keep track of the instructions that require fresh ids. + std::set<opt::Instruction*> instructions_that_need_ids; + + // Check that, if there are enough ids, the conditional can be flattened and, + // if so, add all the problematic instructions that need to be enclosed inside + // conditionals to |instructions_that_need_ids|. + if (!GetProblematicInstructionsIfConditionalCanBeFlattened( + ir_context, header_block, &instructions_that_need_ids)) { + return false; + } + + // Get the mapping from instructions to the fresh ids needed to enclose them + // inside conditionals. + auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context); + + { + std::set<uint32_t> used_fresh_ids; + + // Check the ids in the map. + for (const auto& inst_to_info : insts_to_wrapper_info) { + // Check the fresh ids needed for all of the instructions that need to be + // enclosed inside a conditional. + for (uint32_t id : {inst_to_info.second.merge_block_id(), + inst_to_info.second.execute_block_id()}) { + if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation( + id, ir_context, &used_fresh_ids)) { + return false; + } + } + + // Check the other ids needed, if the instruction needs a placeholder. + if (InstructionNeedsPlaceholder(ir_context, *inst_to_info.first)) { + // Check the fresh ids. + for (uint32_t id : {inst_to_info.second.actual_result_id(), + inst_to_info.second.alternative_block_id(), + inst_to_info.second.placeholder_result_id()}) { + if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation( + id, ir_context, &used_fresh_ids)) { + return false; + } + } + + // Check that the placeholder value id exists, has the right type and is + // available to use at this point. + auto value_def = ir_context->get_def_use_mgr()->GetDef( + inst_to_info.second.value_to_copy_id()); + if (!value_def || + value_def->type_id() != inst_to_info.first->type_id() || + !fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, inst_to_info.first, + inst_to_info.second.value_to_copy_id())) { + return false; + } + } + } + } + + // If some instructions that require ids are not in the map, the + // transformation needs overflow ids to be applicable. + for (auto instruction : instructions_that_need_ids) { + if (insts_to_wrapper_info.count(instruction) == 0 && + !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { + return false; + } + } + + // All checks were passed. + return true; +} + +void TransformationFlattenConditionalBranch::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + uint32_t header_block_id = message_.header_block_id(); + auto header_block = ir_context->cfg()->block(header_block_id); + + // Find the first block where flow converges (it is not necessarily the merge + // block) by walking the true branch until reaching a block that + // post-dominates the header. + // This is necessary because a potential common set of blocks at the end of + // the construct should not be duplicated. + uint32_t convergence_block_id = + header_block->terminator()->GetSingleWordInOperand(1); + auto postdominator_analysis = + ir_context->GetPostDominatorAnalysis(header_block->GetParent()); + while (!postdominator_analysis->Dominates(convergence_block_id, + header_block_id)) { + auto current_block = ir_context->get_instr_block(convergence_block_id); + // If the transformation is applicable, the terminator is OpBranch. + convergence_block_id = + current_block->terminator()->GetSingleWordInOperand(0); + } + + // Get the mapping from instructions to fresh ids. + auto insts_to_info = GetInstructionsToWrapperInfo(ir_context); + + auto branch_instruction = header_block->terminator(); + + // Get a reference to the last block in the first branch that will be laid out + // (this depends on |message_.true_branch_first|). The last block is the block + // in the branch just before flow converges (it might not exist). + opt::BasicBlock* last_block_first_branch = nullptr; + + // branch = 1 corresponds to the true branch, branch = 2 corresponds to the + // false branch. If the true branch is to be laid out first, we need to visit + // the false branch first, because each branch is moved to right after the + // header while it is visited. + std::vector<uint32_t> branches = {2, 1}; + if (!message_.true_branch_first()) { + // Similarly, we need to visit the true branch first, if we want it to be + // laid out after the false branch. + branches = {1, 2}; + } + + // Keep track of blocks and ids for which we should later add dead block and + // irrelevant id facts. We wait until we have finished applying the + // transformation before adding these facts, so that the fact manager has + // access to the fully up-to-date module. + std::vector<uint32_t> dead_blocks; + std::vector<uint32_t> irrelevant_ids; + + // Adjust the conditional branches by enclosing problematic instructions + // within conditionals and get references to the last block in each branch. + for (uint32_t branch : branches) { + auto current_block = header_block; + // Get the id of the first block in this branch. + uint32_t next_block_id = branch_instruction->GetSingleWordInOperand(branch); + + // Consider all blocks in the branch until the convergence block is reached. + while (next_block_id != convergence_block_id) { + // Move the next block to right after the current one. + current_block->GetParent()->MoveBasicBlockToAfter(next_block_id, + current_block); + + // Move forward in the branch. + current_block = ir_context->cfg()->block(next_block_id); + + // Find all the instructions in the current block which need to be + // enclosed inside conditionals. + std::vector<opt::Instruction*> problematic_instructions; + + current_block->ForEachInst( + [&problematic_instructions](opt::Instruction* instruction) { + if (instruction->opcode() != SpvOpLabel && + instruction->opcode() != SpvOpBranch && + !fuzzerutil::InstructionHasNoSideEffects(*instruction)) { + problematic_instructions.push_back(instruction); + } + }); + + uint32_t condition_id = + header_block->terminator()->GetSingleWordInOperand(0); + + // Enclose all of the problematic instructions in conditionals, with the + // same condition as the selection construct being flattened. + for (auto instruction : problematic_instructions) { + // Get the info needed by this instruction to wrap it inside a + // conditional. + protobufs::SideEffectWrapperInfo wrapper_info; + + if (insts_to_info.count(instruction) != 0) { + // Get the fresh ids from the map, if present. + wrapper_info = insts_to_info[instruction]; + } else { + // If we could not get it from the map, use overflow ids. We don't + // need to set |wrapper_info.instruction|, as it will not be used. + wrapper_info.set_merge_block_id( + transformation_context->GetOverflowIdSource() + ->GetNextOverflowId()); + wrapper_info.set_execute_block_id( + transformation_context->GetOverflowIdSource() + ->GetNextOverflowId()); + + if (InstructionNeedsPlaceholder(ir_context, *instruction)) { + // Ge the fresh ids from the overflow ids. + wrapper_info.set_actual_result_id( + transformation_context->GetOverflowIdSource() + ->GetNextOverflowId()); + wrapper_info.set_alternative_block_id( + transformation_context->GetOverflowIdSource() + ->GetNextOverflowId()); + wrapper_info.set_placeholder_result_id( + transformation_context->GetOverflowIdSource() + ->GetNextOverflowId()); + + // Try to find a zero constant. It does not matter whether it is + // relevant or irrelevant. + for (bool is_irrelevant : {true, false}) { + wrapper_info.set_value_to_copy_id( + fuzzerutil::MaybeGetZeroConstant( + ir_context, *transformation_context, + instruction->type_id(), is_irrelevant)); + if (wrapper_info.value_to_copy_id()) { + break; + } + } + } + } + + // Enclose the instruction in a conditional and get the merge block + // generated by this operation (this is where all the following + // instructions will be). + current_block = EncloseInstructionInConditional( + ir_context, *transformation_context, current_block, instruction, + wrapper_info, condition_id, branch == 1, &dead_blocks, + &irrelevant_ids); + } + + next_block_id = current_block->terminator()->GetSingleWordInOperand(0); + + // If the next block is the convergence block and this the branch that + // will be laid out right after the header, record this as the last block + // in the first branch. + if (next_block_id == convergence_block_id && branch == branches[1]) { + last_block_first_branch = current_block; + } + } + } + + // Get the condition operand and the ids of the starting blocks of the first + // and last branches to be laid out. The first branch is the true branch iff + // |message_.true_branch_first| is true. + auto condition_operand = branch_instruction->GetInOperand(0); + uint32_t first_block_first_branch_id = + branch_instruction->GetSingleWordInOperand(branches[1]); + uint32_t first_block_last_branch_id = + branch_instruction->GetSingleWordInOperand(branches[0]); + + // The current header should unconditionally branch to the starting block in + // the first branch to be laid out, if such a branch exists (i.e. the header + // does not branch directly to the convergence block), and to the starting + // block in the last branch to be laid out otherwise. + uint32_t after_header = first_block_first_branch_id != convergence_block_id + ? first_block_first_branch_id + : first_block_last_branch_id; + + // Kill the merge instruction and the branch instruction in the current + // header. + auto merge_inst = header_block->GetMergeInst(); + ir_context->KillInst(branch_instruction); + ir_context->KillInst(merge_inst); + + // Add a new, unconditional, branch instruction from the current header to + // |after_header|. + header_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {after_header}}})); + + // If the first branch to be laid out exists, change the branch instruction so + // that the last block in such branch unconditionally branches to the first + // block in the other branch (or the convergence block if there is no other + // branch) and change the OpPhi instructions in the last branch accordingly + // (the predecessor changed). + if (last_block_first_branch) { + last_block_first_branch->terminator()->SetInOperand( + 0, {first_block_last_branch_id}); + + // Change the OpPhi instructions of the last branch (if there is another + // branch) so that the predecessor is now the last block of the first + // branch. The block must have a single predecessor, so the operand + // specifying the predecessor is always in the same position. + if (first_block_last_branch_id != convergence_block_id) { + ir_context->get_instr_block(first_block_last_branch_id) + ->ForEachPhiInst( + [&last_block_first_branch](opt::Instruction* phi_inst) { + // The operand specifying the predecessor is the second input + // operand. + phi_inst->SetInOperand(1, {last_block_first_branch->id()}); + }); + } + } + + // If the OpBranchConditional instruction in the header branches to the same + // block for both values of the condition, this is the convergence block (the + // flow does not actually diverge) and the OpPhi instructions in it are still + // valid, so we do not need to make any changes. + if (first_block_first_branch_id != first_block_last_branch_id) { + // Replace all of the current OpPhi instructions in the convergence block + // with OpSelect. + + ir_context->get_instr_block(convergence_block_id) + ->ForEachPhiInst([&condition_operand](opt::Instruction* phi_inst) { + phi_inst->SetOpcode(SpvOpSelect); + std::vector<opt::Operand> operands; + operands.emplace_back(condition_operand); + // Only consider the operands referring to the instructions ids, as + // the block labels are not necessary anymore. + for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) { + operands.emplace_back(phi_inst->GetInOperand(i)); + } + + phi_inst->SetInOperands(std::move(operands)); + }); + } + + // Invalidate all analyses + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Now that we have finished adding blocks and ids to the module and + // invalidated existing analyses, update the fact manager regarding dead + // blocks and irrelevant ids. + for (auto dead_block : dead_blocks) { + transformation_context->GetFactManager()->AddFactBlockIsDead(dead_block); + } + for (auto irrelevant_id : irrelevant_ids) { + transformation_context->GetFactManager()->AddFactIdIsIrrelevant( + irrelevant_id); + } +} + +protobufs::Transformation TransformationFlattenConditionalBranch::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_flatten_conditional_branch() = message_; + return result; +} + +bool TransformationFlattenConditionalBranch:: + GetProblematicInstructionsIfConditionalCanBeFlattened( + opt::IRContext* ir_context, opt::BasicBlock* header, + std::set<opt::Instruction*>* instructions_that_need_ids) { + uint32_t merge_block_id = header->MergeBlockIdIfAny(); + assert(merge_block_id && + header->GetMergeInst()->opcode() == SpvOpSelectionMerge && + header->terminator()->opcode() == SpvOpBranchConditional && + "|header| must be the header of a conditional."); + + auto enclosing_function = header->GetParent(); + auto dominator_analysis = + ir_context->GetDominatorAnalysis(enclosing_function); + auto postdominator_analysis = + ir_context->GetPostDominatorAnalysis(enclosing_function); + + // Check that the header and the merge block describe a single-entry, + // single-exit region. + if (!dominator_analysis->Dominates(header->id(), merge_block_id) || + !postdominator_analysis->Dominates(merge_block_id, header->id())) { + return false; + } + + // Traverse the CFG starting from the header and check that, for all the + // blocks that can be reached by the header before the flow converges: + // - they don't contain merge, barrier or OpSampledImage instructions + // - they branch unconditionally to another block + // Add any side-effecting instruction, requiring fresh ids, to + // |instructions_that_need_ids| + std::list<uint32_t> to_check; + header->ForEachSuccessorLabel( + [&to_check](uint32_t label) { to_check.push_back(label); }); + + while (!to_check.empty()) { + uint32_t block_id = to_check.front(); + to_check.pop_front(); + + // If the block post-dominates the header, this is where flow converges, and + // we don't need to check this branch any further, because the + // transformation will only change the part of the graph where flow is + // divergent. + if (postdominator_analysis->Dominates(block_id, header->id())) { + continue; + } + + auto block = ir_context->cfg()->block(block_id); + + // The block must not have a merge instruction, because inner constructs are + // not allowed. + if (block->GetMergeInst()) { + return false; + } + + // The terminator instruction for the block must be OpBranch. + if (block->terminator()->opcode() != SpvOpBranch) { + return false; + } + + // Check all of the instructions in the block. + bool all_instructions_compatible = + block->WhileEachInst([ir_context, instructions_that_need_ids]( + opt::Instruction* instruction) { + // We can ignore OpLabel instructions. + if (instruction->opcode() == SpvOpLabel) { + return true; + } + + // If the instruction is a branch, it must be an unconditional branch. + if (instruction->IsBranch()) { + return instruction->opcode() == SpvOpBranch; + } + + // We cannot go ahead if we encounter an instruction that cannot be + // handled. + if (!InstructionCanBeHandled(ir_context, *instruction)) { + return false; + } + + // If the instruction has side effects, add it to the + // |instructions_that_need_ids| set. + if (!fuzzerutil::InstructionHasNoSideEffects(*instruction)) { + instructions_that_need_ids->emplace(instruction); + } + + return true; + }); + + if (!all_instructions_compatible) { + return false; + } + + // Add the successor of this block to the list of blocks that need to be + // checked. + to_check.push_back(block->terminator()->GetSingleWordInOperand(0)); + } + + // All the blocks are compatible with the transformation and this is indeed a + // single-entry, single-exit region. + return true; +} + +bool TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder( + opt::IRContext* ir_context, const opt::Instruction& instruction) { + assert(!fuzzerutil::InstructionHasNoSideEffects(instruction) && + InstructionCanBeHandled(ir_context, instruction) && + "The instruction must have side effects and it must be possible to " + "enclose it inside a conditional."); + + if (instruction.HasResultId()) { + // We need a placeholder iff the type is not Void. + auto type = ir_context->get_type_mgr()->GetType(instruction.type_id()); + return type && !type->AsVoid(); + } + + return false; +} + +std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo> +TransformationFlattenConditionalBranch::GetInstructionsToWrapperInfo( + opt::IRContext* ir_context) const { + std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo> + instructions_to_ids; + for (const auto& wrapper_info : message_.side_effect_wrapper_info()) { + auto instruction = FindInstruction(wrapper_info.instruction(), ir_context); + if (instruction) { + instructions_to_ids.emplace(instruction, wrapper_info); + } + } + + return instructions_to_ids; +} + +opt::BasicBlock* +TransformationFlattenConditionalBranch::EncloseInstructionInConditional( + opt::IRContext* ir_context, + const TransformationContext& transformation_context, opt::BasicBlock* block, + opt::Instruction* instruction, + const protobufs::SideEffectWrapperInfo& wrapper_info, uint32_t condition_id, + bool exec_if_cond_true, std::vector<uint32_t>* dead_blocks, + std::vector<uint32_t>* irrelevant_ids) const { + // Get the next instruction (it will be useful for splitting). + auto next_instruction = instruction->NextNode(); + + // Update the module id bound. + for (uint32_t id : + {wrapper_info.merge_block_id(), wrapper_info.execute_block_id()}) { + fuzzerutil::UpdateModuleIdBound(ir_context, id); + } + + // Create the block where the instruction is executed by splitting the + // original block. + auto execute_block = block->SplitBasicBlock( + ir_context, wrapper_info.execute_block_id(), + fuzzerutil::GetIteratorForInstruction(block, instruction)); + + // Create the merge block for the conditional that we are about to create by + // splitting execute_block (this will leave |instruction| as the only + // instruction in |execute_block|). + auto merge_block = execute_block->SplitBasicBlock( + ir_context, wrapper_info.merge_block_id(), + fuzzerutil::GetIteratorForInstruction(execute_block, next_instruction)); + + // Propagate the fact that the block is dead to the newly-created blocks. + if (transformation_context.GetFactManager()->BlockIsDead(block->id())) { + dead_blocks->emplace_back(execute_block->id()); + dead_blocks->emplace_back(merge_block->id()); + } + + // Initially, consider the merge block as the alternative block to branch to + // if the instruction should not be executed. + auto alternative_block = merge_block; + + // Add an unconditional branch from |execute_block| to |merge_block|. + execute_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {merge_block->id()}}})); + + // If the instruction requires a placeholder, it means that it has a result id + // and its result needs to be able to be used later on, and we need to: + // - add an additional block |ids.alternative_block_id| where a placeholder + // result id (using fresh id |ids.placeholder_result_id|) is obtained either + // by using OpCopyObject and copying |ids.value_to_copy_id| or, if such id + // was not given and a suitable constant was not found, by using OpUndef. + // - mark |ids.placeholder_result_id| as irrelevant + // - change the result id of the instruction to a fresh id + // (|ids.actual_result_id|). + // - add an OpPhi instruction, which will have the original result id of the + // instruction, in the merge block. + if (InstructionNeedsPlaceholder(ir_context, *instruction)) { + // Update the module id bound with the additional ids. + for (uint32_t id : + {wrapper_info.actual_result_id(), wrapper_info.alternative_block_id(), + wrapper_info.placeholder_result_id()}) { + fuzzerutil::UpdateModuleIdBound(ir_context, id); + } + + // Create a new block using |fresh_ids.alternative_block_id| for its label. + auto alternative_block_temp = + MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, wrapper_info.alternative_block_id(), + opt::Instruction::OperandList{})); + + // Keep the original result id of the instruction in a variable. + uint32_t original_result_id = instruction->result_id(); + + // Set the result id of the instruction to be |ids.actual_result_id|. + instruction->SetResultId(wrapper_info.actual_result_id()); + + // Add a placeholder instruction, with the same type as the original + // instruction and id |ids.placeholder_result_id|, to the new block. + if (wrapper_info.value_to_copy_id()) { + // If there is an available id to copy from, the placeholder instruction + // will be %placeholder_result_id = OpCopyObject %type %value_to_copy_id + alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpCopyObject, instruction->type_id(), + wrapper_info.placeholder_result_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {wrapper_info.value_to_copy_id()}}})); + } else { + // If there is no such id, use an OpUndef instruction. + alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpUndef, instruction->type_id(), + wrapper_info.placeholder_result_id(), + opt::Instruction::OperandList{})); + } + + // Mark |ids.placeholder_result_id| as irrelevant. + irrelevant_ids->emplace_back(wrapper_info.placeholder_result_id()); + + // Add an unconditional branch from the new block to the merge block. + alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {merge_block->id()}}})); + + // Insert the block before the merge block. + alternative_block = block->GetParent()->InsertBasicBlockBefore( + std::move(alternative_block_temp), merge_block); + + // Using the original instruction result id, add an OpPhi instruction to the + // merge block, which will either take the value of the result of the + // instruction or the placeholder value defined in the alternative block. + merge_block->begin().InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpPhi, instruction->type_id(), original_result_id, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {instruction->result_id()}}, + {SPV_OPERAND_TYPE_ID, {execute_block->id()}}, + {SPV_OPERAND_TYPE_ID, {wrapper_info.placeholder_result_id()}}, + {SPV_OPERAND_TYPE_ID, {alternative_block->id()}}})); + + // Propagate the fact that the block is dead to the new block. + if (transformation_context.GetFactManager()->BlockIsDead(block->id())) { + dead_blocks->emplace_back(alternative_block->id()); + } + } + + // Depending on whether the instruction should be executed in the if branch or + // in the else branch, get the corresponding ids. + auto if_block_id = (exec_if_cond_true ? execute_block : alternative_block) + ->GetLabel() + ->result_id(); + auto else_block_id = (exec_if_cond_true ? alternative_block : execute_block) + ->GetLabel() + ->result_id(); + + // Add an OpSelectionMerge instruction to the block. + block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpSelectionMerge, 0, 0, + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {merge_block->id()}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}})); + + // Add an OpBranchConditional, to the block, using |condition_id| as the + // condition and branching to |if_block_id| if the condition is true and to + // |else_block_id| if the condition is false. + block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranchConditional, 0, 0, + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {condition_id}}, + {SPV_OPERAND_TYPE_ID, {if_block_id}}, + {SPV_OPERAND_TYPE_ID, {else_block_id}}})); + + return merge_block; +} + +bool TransformationFlattenConditionalBranch::InstructionCanBeHandled( + opt::IRContext* ir_context, const opt::Instruction& instruction) { + // We can handle all instructions with no side effects. + if (fuzzerutil::InstructionHasNoSideEffects(instruction)) { + return true; + } + + // We cannot handle barrier instructions, while we should be able to handle + // all other instructions by enclosing them inside a conditional. + if (instruction.opcode() == SpvOpControlBarrier || + instruction.opcode() == SpvOpMemoryBarrier || + instruction.opcode() == SpvOpNamedBarrierInitialize || + instruction.opcode() == SpvOpMemoryNamedBarrier || + instruction.opcode() == SpvOpTypeNamedBarrier) { + return false; + } + + // We cannot handle OpSampledImage instructions, as they need to be in the + // same block as their use. + if (instruction.opcode() == SpvOpSampledImage) { + return false; + } + + // We cannot handle instructions with an id which return a void type, if the + // result id is used in the module (e.g. a function call to a function that + // returns nothing). + if (instruction.HasResultId()) { + auto type = ir_context->get_type_mgr()->GetType(instruction.type_id()); + assert(type && "The type should be found in the module"); + + if (type->AsVoid() && + !ir_context->get_def_use_mgr()->WhileEachUse( + instruction.result_id(), + [](opt::Instruction* use_inst, uint32_t use_index) { + // Return false if the id is used as an input operand. + return use_index < + use_inst->NumOperands() - use_inst->NumInOperands(); + })) { + return false; + } + } + + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_flatten_conditional_branch.h b/source/fuzz/transformation_flatten_conditional_branch.h new file mode 100644 index 00000000..4ec471e5 --- /dev/null +++ b/source/fuzz/transformation_flatten_conditional_branch.h @@ -0,0 +1,120 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H +#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationFlattenConditionalBranch : public Transformation { + public: + explicit TransformationFlattenConditionalBranch( + const protobufs::TransformationFlattenConditionalBranch& message); + + TransformationFlattenConditionalBranch( + uint32_t header_block_id, bool true_branch_first, + const std::vector<protobufs::SideEffectWrapperInfo>& + side_effect_wrappers_info); + + // - |message_.header_block_id| must be the label id of a reachable selection + // header, which ends with an OpBranchConditional instruction. + // - The header block and the merge block must describe a single-entry, + // single-exit region. + // - The region must not contain barrier or OpSampledImage instructions. + // - The region must not contain selection or loop constructs. + // - For each instruction that requires additional fresh ids, then: + // - if the instruction is mapped to the required ids for enclosing it by + // |message_.side_effect_wrapper_info|, these must be valid (the + // fresh ids must be non-zero, fresh and distinct); + // - if there is no such mapping, the transformation context must have + // overflow ids available. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Flattens the selection construct with header |message_.header_block_id|, + // changing any OpPhi in the block where the flow converges to OpSelect and + // enclosing any instruction with side effects in conditionals so that + // they are only executed when they should. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if the conditional headed by |header| can be flattened, + // according to the conditions of the IsApplicable method, assuming that + // enough fresh ids would be provided. In this case, it fills the + // |instructions_that_need_ids| set with all the instructions that would + // require fresh ids. + // Returns false otherwise. + // Assumes that |header| is the header of a conditional, so its last two + // instructions are OpSelectionMerge and OpBranchConditional. + static bool GetProblematicInstructionsIfConditionalCanBeFlattened( + opt::IRContext* ir_context, opt::BasicBlock* header, + std::set<opt::Instruction*>* instructions_that_need_ids); + + // Returns true iff the given instruction needs a placeholder to be enclosed + // inside a conditional. So, it returns: + // - true if the instruction has a non-void result id, + // - false if the instruction does not have a result id or has a void result + // id. + // Assumes that the instruction has side effects, requiring it to be enclosed + // inside a conditional, and that it can be enclosed inside a conditional + // keeping the module valid. Assumes that, if the instruction has a void + // result type, its result id is not used in the module. + static bool InstructionNeedsPlaceholder(opt::IRContext* ir_context, + const opt::Instruction& instruction); + + private: + // Returns an unordered_map mapping instructions to the info required to + // enclose them inside a conditional. It maps the instructions to the + // corresponding entry in |message_.side_effect_wrapper_info|. + std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo> + GetInstructionsToWrapperInfo(opt::IRContext* ir_context) const; + + // Splits the given block, adding a new selection construct so that the given + // instruction is only executed if the boolean value of |condition_id| matches + // the value of |exec_if_cond_true|. + // Assumes that all parameters are consistent. + // 2 fresh ids are required if the instruction does not have a result id (the + // first two ids in |wrapper_info| must be valid fresh ids), 5 otherwise. + // Returns the merge block created. + // + // |dead_blocks| and |irrelevant_ids| are used to record the ids of blocks + // and instructions for which dead block and irrelevant id facts should + // ultimately be created. + opt::BasicBlock* EncloseInstructionInConditional( + opt::IRContext* ir_context, + const TransformationContext& transformation_context, + opt::BasicBlock* block, opt::Instruction* instruction, + const protobufs::SideEffectWrapperInfo& wrapper_info, + uint32_t condition_id, bool exec_if_cond_true, + std::vector<uint32_t>* dead_blocks, + std::vector<uint32_t>* irrelevant_ids) const; + + // Returns true if the given instruction either has no side effects or it can + // be handled by being enclosed in a conditional. + static bool InstructionCanBeHandled(opt::IRContext* ir_context, + const opt::Instruction& instruction); + + protobufs::TransformationFlattenConditionalBranch message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H diff --git a/source/fuzz/transformation_inline_function.cpp b/source/fuzz/transformation_inline_function.cpp new file mode 100644 index 00000000..dfa66f89 --- /dev/null +++ b/source/fuzz/transformation_inline_function.cpp @@ -0,0 +1,296 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_inline_function.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationInlineFunction::TransformationInlineFunction( + const spvtools::fuzz::protobufs::TransformationInlineFunction& message) + : message_(message) {} + +TransformationInlineFunction::TransformationInlineFunction( + uint32_t function_call_id, + const std::map<uint32_t, uint32_t>& result_id_map) { + message_.set_function_call_id(function_call_id); + *message_.mutable_result_id_map() = + fuzzerutil::MapToRepeatedUInt32Pair(result_id_map); +} + +bool TransformationInlineFunction::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // The values in the |message_.result_id_map| must be all fresh and all + // distinct. + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + std::set<uint32_t> ids_used_by_this_transformation; + for (auto& pair : result_id_map) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second, ir_context, &ids_used_by_this_transformation)) { + return false; + } + } + + // |function_call_instruction| must be suitable for inlining. + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + if (!IsSuitableForInlining(ir_context, function_call_instruction)) { + return false; + } + + // |function_call_instruction| must be the penultimate instruction in its + // block and its block termination instruction must be an OpBranch. This + // avoids the case where the penultimate instruction is an OpLoopMerge, which + // would make the back-edge block not branch to the loop header. + auto* function_call_instruction_block = + ir_context->get_instr_block(function_call_instruction); + if (function_call_instruction != + &*--function_call_instruction_block->tail() || + function_call_instruction_block->terminator()->opcode() != SpvOpBranch) { + return false; + } + + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + for (auto& block : *called_function) { + // Since the entry block label will not be inlined, only the remaining + // labels must have a corresponding value in the map. + if (&block != &*called_function->entry() && + !result_id_map.count(block.GetLabel()->result_id())) { + return false; + } + + // |result_id_map| must have an entry for every result id in the called + // function. + for (auto& instruction : block) { + // If |instruction| has result id, then it must have a mapped id in + // |result_id_map|. + if (instruction.HasResultId() && + !result_id_map.count(instruction.result_id())) { + return false; + } + } + } + + // |result_id_map| must not contain an entry for any parameter of the function + // that is being inlined. + bool found_entry_for_parameter = false; + called_function->ForEachParam( + [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) { + if (result_id_map.count(param->result_id())) { + found_entry_for_parameter = true; + } + }); + return !found_entry_for_parameter; +} + +void TransformationInlineFunction::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* caller_function = + ir_context->get_instr_block(function_call_instruction)->GetParent(); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + auto* successor_block = ir_context->cfg()->block( + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0)); + + // Inline the |called_function| entry block. + for (auto& entry_block_instruction : *called_function->entry()) { + opt::Instruction* inlined_instruction = nullptr; + + if (entry_block_instruction.opcode() == SpvOpVariable) { + // All OpVariable instructions in a function must be in the first block + // in the function. + inlined_instruction = caller_function->begin()->begin()->InsertBefore( + MakeUnique<opt::Instruction>(entry_block_instruction)); + } else { + inlined_instruction = function_call_instruction->InsertBefore( + MakeUnique<opt::Instruction>(entry_block_instruction)); + } + + AdaptInlinedInstruction(ir_context, inlined_instruction); + } + + // Inline the |called_function| non-entry blocks. + for (auto& block : *called_function) { + if (&block == &*called_function->entry()) { + continue; + } + + auto* cloned_block = block.Clone(ir_context); + cloned_block = caller_function->InsertBasicBlockBefore( + std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block); + cloned_block->SetParent(caller_function); + cloned_block->GetLabel()->SetResultId( + result_id_map.at(cloned_block->GetLabel()->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + cloned_block->GetLabel()->result_id()); + + for (auto& inlined_instruction : *cloned_block) { + AdaptInlinedInstruction(ir_context, &inlined_instruction); + } + } + + // Removes the function call instruction and its block termination instruction + // from |caller_function|. + ir_context->KillInst( + ir_context->get_instr_block(function_call_instruction)->terminator()); + ir_context->KillInst(function_call_instruction); + + // Since the SPIR-V module has changed, no analyses must be validated. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationInlineFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_inline_function() = message_; + return result; +} + +bool TransformationInlineFunction::IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction) { + // |function_call_instruction| must be defined and must be an OpFunctionCall + // instruction. + if (!function_call_instruction || + function_call_instruction->opcode() != SpvOpFunctionCall) { + return false; + } + + // If |function_call_instruction| return type is void, then + // |function_call_instruction| must not have uses. + if (ir_context->get_type_mgr() + ->GetType(function_call_instruction->type_id()) + ->AsVoid() && + ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) { + return false; + } + + // |called_function| must not have an early return. + auto called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + if (called_function->HasEarlyReturn()) { + return false; + } + + // |called_function| must not use OpKill or OpUnreachable. + if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) { + return false; + } + + return true; +} + +void TransformationInlineFunction::AdaptInlinedInstruction( + opt::IRContext* ir_context, + opt::Instruction* instruction_to_be_inlined) const { + auto* function_call_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.function_call_id()); + auto* called_function = fuzzerutil::FindFunction( + ir_context, function_call_instruction->GetSingleWordInOperand(0)); + const auto result_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map()); + + const auto* function_call_block = + ir_context->get_instr_block(function_call_instruction); + assert(function_call_block && "OpFunctionCall must belong to some block"); + + // Replaces the operand ids with their mapped result ids. + instruction_to_be_inlined->ForEachInId( + [called_function, function_call_instruction, &result_id_map, + function_call_block](uint32_t* id) { + // We are not inlining the entry block of the |called_function|. + // + // We must check this condition first since we can't use the fresh id + // from |result_id_map| even if it has one. This is because that fresh + // id will never be added to the module since entry blocks are not + // inlined. + if (*id == called_function->entry()->id()) { + *id = function_call_block->id(); + return; + } + + // If |id| is mapped, then set it to its mapped value. + if (result_id_map.count(*id)) { + *id = result_id_map.at(*id); + return; + } + + uint32_t parameter_index = 0; + called_function->ForEachParam( + [id, function_call_instruction, + ¶meter_index](opt::Instruction* parameter_instruction) { + // If the id is a function parameter, then set it to the + // parameter value passed in the function call instruction. + if (*id == parameter_instruction->result_id()) { + // We do + 1 because the first in-operand for OpFunctionCall is + // the function id that is being called. + *id = function_call_instruction->GetSingleWordInOperand( + parameter_index + 1); + } + parameter_index++; + }); + }); + + // If |instruction_to_be_inlined| has result id, then set it to its mapped + // value. + if (instruction_to_be_inlined->HasResultId()) { + assert(result_id_map.count(instruction_to_be_inlined->result_id()) && + "Result id must be mapped to a fresh id."); + instruction_to_be_inlined->SetResultId( + result_id_map.at(instruction_to_be_inlined->result_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, + instruction_to_be_inlined->result_id()); + } + + // The return instruction will be changed into an OpBranch to the basic + // block that follows the block containing the function call. + if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) { + uint32_t successor_block_id = + ir_context->get_instr_block(function_call_instruction) + ->terminator() + ->GetSingleWordInOperand(0); + switch (instruction_to_be_inlined->opcode()) { + case SpvOpReturn: + instruction_to_be_inlined->AddOperand( + {SPV_OPERAND_TYPE_ID, {successor_block_id}}); + break; + case SpvOpReturnValue: { + instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCopyObject, function_call_instruction->type_id(), + function_call_instruction->result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {instruction_to_be_inlined->GetSingleWordOperand(0)}}}))); + instruction_to_be_inlined->SetInOperand(0, {successor_block_id}); + break; + } + default: + break; + } + instruction_to_be_inlined->SetOpcode(SpvOpBranch); + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_inline_function.h b/source/fuzz/transformation_inline_function.h new file mode 100644 index 00000000..29a9ea6f --- /dev/null +++ b/source/fuzz/transformation_inline_function.h @@ -0,0 +1,75 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationInlineFunction : public Transformation { + public: + explicit TransformationInlineFunction( + const protobufs::TransformationInlineFunction& message); + + TransformationInlineFunction( + uint32_t function_call_id, + const std::map<uint32_t, uint32_t>& result_id_map); + + // - |message_.result_id_map| must map the instructions of the called function + // to fresh ids. + // - |message_.function_call_id| must be an OpFunctionCall instruction. + // It must not have an early return and must not use OpUnreachable or + // OpKill. This is to guard against making the module invalid when the + // caller is inside a continue construct. + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735): + // Allow functions that use OpKill or OpUnreachable to be inlined if the + // function call is not part of a continue construct. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpFunctionCall instruction, identified by + // |message_.function_call_id|, with a copy of the function's body. + // |message_.result_id_map| is used to provide fresh ids for duplicate + // instructions. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if |function_call_instruction| is defined, is an + // OpFunctionCall instruction, has no uses if its return type is void, has no + // early returns and has no uses of OpKill or OpUnreachable. + static bool IsSuitableForInlining( + opt::IRContext* ir_context, opt::Instruction* function_call_instruction); + + private: + protobufs::TransformationInlineFunction message_; + + // Inline |instruction_to_be_inlined| by setting its ids to the corresponding + // ids in |result_id_map|. + void AdaptInlinedInstruction(opt::IRContext* ir_context, + opt::Instruction* instruction) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_ diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.cpp b/source/fuzz/transformation_make_vector_operation_dynamic.cpp new file mode 100644 index 00000000..c960676c --- /dev/null +++ b/source/fuzz/transformation_make_vector_operation_dynamic.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationMakeVectorOperationDynamic:: + TransformationMakeVectorOperationDynamic( + const spvtools::fuzz::protobufs:: + TransformationMakeVectorOperationDynamic& message) + : message_(message) {} + +TransformationMakeVectorOperationDynamic:: + TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id, + uint32_t constant_index_id) { + message_.set_instruction_result_id(instruction_result_id); + message_.set_constant_index_id(constant_index_id); +} + +bool TransformationMakeVectorOperationDynamic::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |instruction| must be a vector operation. + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + if (!IsVectorOperation(ir_context, instruction)) { + return false; + } + + // |constant_index_instruction| must be defined as an integer instruction. + auto constant_index_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.constant_index_id()); + if (!constant_index_instruction || !constant_index_instruction->type_id() || + !ir_context->get_type_mgr() + ->GetType(constant_index_instruction->type_id()) + ->AsInteger()) { + return false; + } + + return true; +} + +void TransformationMakeVectorOperationDynamic::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); + + // The OpVectorInsertDynamic instruction has the vector and component operands + // in reverse order in relation to the OpCompositeInsert corresponding + // operands. + if (instruction->opcode() == SpvOpCompositeInsert) { + std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1)); + } + + // Sets the literal operand to the equivalent constant. + instruction->SetInOperand( + instruction->opcode() == SpvOpCompositeExtract ? 1 : 2, + {message_.constant_index_id()}); + + // Sets the |instruction| opcode to the corresponding vector dynamic opcode. + instruction->SetOpcode(instruction->opcode() == SpvOpCompositeExtract + ? SpvOpVectorExtractDynamic + : SpvOpVectorInsertDynamic); +} + +protobufs::Transformation TransformationMakeVectorOperationDynamic::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_make_vector_operation_dynamic() = message_; + return result; +} + +bool TransformationMakeVectorOperationDynamic::IsVectorOperation( + opt::IRContext* ir_context, opt::Instruction* instruction) { + // |instruction| must be defined and must be an OpCompositeExtract/Insert + // instruction. + if (!instruction || (instruction->opcode() != SpvOpCompositeExtract && + instruction->opcode() != SpvOpCompositeInsert)) { + return false; + } + + // The composite must be a vector. + auto composite_instruction = + ir_context->get_def_use_mgr()->GetDef(instruction->GetSingleWordInOperand( + instruction->opcode() == SpvOpCompositeExtract ? 0 : 1)); + if (!ir_context->get_type_mgr() + ->GetType(composite_instruction->type_id()) + ->AsVector()) { + return false; + } + + return true; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.h b/source/fuzz/transformation_make_vector_operation_dynamic.h new file mode 100644 index 00000000..e7d17494 --- /dev/null +++ b/source/fuzz/transformation_make_vector_operation_dynamic.h @@ -0,0 +1,63 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMakeVectorOperationDynamic : public Transformation { + public: + explicit TransformationMakeVectorOperationDynamic( + const protobufs::TransformationMakeVectorOperationDynamic& message); + + TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id, + uint32_t constant_index_id); + + // - |message_.instruction_result_id| must be the result id of an + // OpCompositeExtract/Insert instruction such that the composite operand is a + // vector. + // - |message_.constant_index_id| must be the result id of an integer + // instruction such that its value equals the indexing literal of the + // OpCompositeExtract/Insert instruction. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpCompositeExtract and OpCompositeInsert instructions with the + // OpVectorExtractDynamic and OpVectorInsertDynamic instructions. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks |instruction| is defined, is an OpCompositeExtract/Insert + // instruction and the composite operand is a vector. + static bool IsVectorOperation(opt::IRContext* ir_context, + opt::Instruction* instruction); + + private: + protobufs::TransformationMakeVectorOperationDynamic message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_ diff --git a/source/fuzz/transformation_move_instruction_down.cpp b/source/fuzz/transformation_move_instruction_down.cpp new file mode 100644 index 00000000..b83dc078 --- /dev/null +++ b/source/fuzz/transformation_move_instruction_down.cpp @@ -0,0 +1,729 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_move_instruction_down.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "spirv/unified1/GLSL.std.450.h" + +namespace spvtools { +namespace fuzz { +namespace { + +const char* const kExtensionSetName = "GLSL.std.450"; + +std::string GetExtensionSet(opt::IRContext* ir_context, + const opt::Instruction& op_ext_inst) { + assert(op_ext_inst.opcode() == SpvOpExtInst && "Wrong opcode"); + + const auto* ext_inst_import = ir_context->get_def_use_mgr()->GetDef( + op_ext_inst.GetSingleWordInOperand(0)); + assert(ext_inst_import && "Extension set is not imported"); + + return ext_inst_import->GetInOperand(0).AsString(); +} + +} // namespace + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message) + : message_(message) {} + +TransformationMoveInstructionDown::TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction) { + *message_.mutable_instruction() = instruction; +} + +bool TransformationMoveInstructionDown::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // |instruction| must be valid. + auto* inst = FindInstruction(message_.instruction(), ir_context); + if (!inst) { + return false; + } + + // Instruction's opcode must be supported by this transformation. + if (!IsInstructionSupported(ir_context, *inst)) { + return false; + } + + auto* inst_block = ir_context->get_instr_block(inst); + assert(inst_block && + "Global instructions and function parameters are not supported"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst); + assert(inst_it != inst_block->end() && + "Can't get an iterator for the instruction"); + + // |instruction| can't be the last instruction in the block. + auto successor_it = ++inst_it; + if (successor_it == inst_block->end()) { + return false; + } + + // We don't risk swapping a memory instruction with an unsupported one. + if (!IsSimpleInstruction(ir_context, *inst) && + !IsInstructionSupported(ir_context, *successor_it)) { + return false; + } + + // It must be safe to swap the instructions without changing the semantics of + // the module. + if (IsInstructionSupported(ir_context, *successor_it) && + !CanSafelySwapInstructions(ir_context, *inst, *successor_it, + *transformation_context.GetFactManager())) { + return false; + } + + // Check that we can insert |instruction| after |inst_it|. + auto successors_successor_it = ++inst_it; + if (successors_successor_it == inst_block->end() || + !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(), + successors_successor_it)) { + return false; + } + + // Check that |instruction|'s successor doesn't depend on the |instruction|. + if (inst->result_id()) { + for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) { + const auto& operand = successor_it->GetInOperand(i); + if (spvIsInIdType(operand.type) && + operand.words[0] == inst->result_id()) { + return false; + } + } + } + + return true; +} + +void TransformationMoveInstructionDown::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* inst = FindInstruction(message_.instruction(), ir_context); + assert(inst && + "The instruction should've been validated in the IsApplicable"); + + auto inst_it = fuzzerutil::GetIteratorForInstruction( + ir_context->get_instr_block(inst), inst); + + // Move the instruction down in the block. + inst->InsertAfter(&*++inst_it); + + ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const { + protobufs::Transformation result; + *result.mutable_move_instruction_down() = message_; + return result; +} + +bool TransformationMoveInstructionDown::IsInstructionSupported( + opt::IRContext* ir_context, const opt::Instruction& inst) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): + // Add support for more instructions here. + return IsSimpleInstruction(ir_context, inst) || + IsMemoryInstruction(ir_context, inst) || IsBarrierInstruction(inst); +} + +bool TransformationMoveInstructionDown::IsSimpleInstruction( + opt::IRContext* ir_context, const opt::Instruction& inst) { + switch (inst.opcode()) { + case SpvOpNop: + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + // OpAccessChain and OpInBoundsAccessChain are considered simple + // instructions since they result in a pointer to the object in memory, + // not the object itself. + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + return true; + case SpvOpExtInst: { + const auto* ext_inst_import = + ir_context->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0)); + + if (ext_inst_import->GetInOperand(0).AsString() != kExtensionSetName) { + return false; + } + + switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { + case GLSLstd450Round: + case GLSLstd450RoundEven: + case GLSLstd450Trunc: + case GLSLstd450FAbs: + case GLSLstd450SAbs: + case GLSLstd450FSign: + case GLSLstd450SSign: + case GLSLstd450Floor: + case GLSLstd450Ceil: + case GLSLstd450Fract: + case GLSLstd450Radians: + case GLSLstd450Degrees: + case GLSLstd450Sin: + case GLSLstd450Cos: + case GLSLstd450Tan: + case GLSLstd450Asin: + case GLSLstd450Acos: + case GLSLstd450Atan: + case GLSLstd450Sinh: + case GLSLstd450Cosh: + case GLSLstd450Tanh: + case GLSLstd450Asinh: + case GLSLstd450Acosh: + case GLSLstd450Atanh: + case GLSLstd450Atan2: + case GLSLstd450Pow: + case GLSLstd450Exp: + case GLSLstd450Log: + case GLSLstd450Exp2: + case GLSLstd450Log2: + case GLSLstd450Sqrt: + case GLSLstd450InverseSqrt: + case GLSLstd450Determinant: + case GLSLstd450MatrixInverse: + case GLSLstd450ModfStruct: + case GLSLstd450FMin: + case GLSLstd450UMin: + case GLSLstd450SMin: + case GLSLstd450FMax: + case GLSLstd450UMax: + case GLSLstd450SMax: + case GLSLstd450FClamp: + case GLSLstd450UClamp: + case GLSLstd450SClamp: + case GLSLstd450FMix: + case GLSLstd450IMix: + case GLSLstd450Step: + case GLSLstd450SmoothStep: + case GLSLstd450Fma: + case GLSLstd450FrexpStruct: + case GLSLstd450Ldexp: + case GLSLstd450PackSnorm4x8: + case GLSLstd450PackUnorm4x8: + case GLSLstd450PackSnorm2x16: + case GLSLstd450PackUnorm2x16: + case GLSLstd450PackHalf2x16: + case GLSLstd450PackDouble2x32: + case GLSLstd450UnpackSnorm2x16: + case GLSLstd450UnpackUnorm2x16: + case GLSLstd450UnpackHalf2x16: + case GLSLstd450UnpackSnorm4x8: + case GLSLstd450UnpackUnorm4x8: + case GLSLstd450UnpackDouble2x32: + case GLSLstd450Length: + case GLSLstd450Distance: + case GLSLstd450Cross: + case GLSLstd450Normalize: + case GLSLstd450FaceForward: + case GLSLstd450Reflect: + case GLSLstd450Refract: + case GLSLstd450FindILsb: + case GLSLstd450FindSMsb: + case GLSLstd450FindUMsb: + case GLSLstd450NMin: + case GLSLstd450NMax: + case GLSLstd450NClamp: + return true; + default: + return false; + } + } + default: + return false; + } +} + +bool TransformationMoveInstructionDown::IsMemoryReadInstruction( + opt::IRContext* ir_context, const opt::Instruction& inst) { + switch (inst.opcode()) { + // Some simple instructions. + case SpvOpLoad: + case SpvOpCopyMemory: + // Image instructions. + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageFetch: + case SpvOpImageGather: + case SpvOpImageDrefGather: + case SpvOpImageRead: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleExplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: + case SpvOpImageSparseFetch: + case SpvOpImageSparseGather: + case SpvOpImageSparseDrefGather: + case SpvOpImageSparseRead: + // Atomic instructions. + case SpvOpAtomicLoad: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + return true; + // Extensions. + case SpvOpExtInst: { + if (GetExtensionSet(ir_context, inst) != kExtensionSetName) { + return false; + } + + switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtOffset: + case GLSLstd450InterpolateAtSample: + return true; + default: + return false; + } + } + default: + return false; + } +} + +uint32_t TransformationMoveInstructionDown::GetMemoryReadTarget( + opt::IRContext* ir_context, const opt::Instruction& inst) { + (void)ir_context; // |ir_context| is only used in assertions. + assert(IsMemoryReadInstruction(ir_context, inst) && + "|inst| is not a memory read instruction"); + + switch (inst.opcode()) { + // Simple instructions. + case SpvOpLoad: + // Image instructions. + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageFetch: + case SpvOpImageGather: + case SpvOpImageDrefGather: + case SpvOpImageRead: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleExplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: + case SpvOpImageSparseFetch: + case SpvOpImageSparseGather: + case SpvOpImageSparseDrefGather: + case SpvOpImageSparseRead: + // Atomic instructions. + case SpvOpAtomicLoad: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + return inst.GetSingleWordInOperand(0); + case SpvOpCopyMemory: + return inst.GetSingleWordInOperand(1); + case SpvOpExtInst: { + assert(GetExtensionSet(ir_context, inst) == kExtensionSetName && + "Extension set is not supported"); + + switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtOffset: + case GLSLstd450InterpolateAtSample: + return inst.GetSingleWordInOperand(2); + default: + // This assertion will fail if not all memory read extension + // instructions are handled in the switch. + assert(false && "Not all memory opcodes are handled"); + return 0; + } + } + default: + // This assertion will fail if not all memory read opcodes are handled in + // the switch. + assert(false && "Not all memory opcodes are handled"); + return 0; + } +} + +bool TransformationMoveInstructionDown::IsMemoryWriteInstruction( + opt::IRContext* ir_context, const opt::Instruction& inst) { + switch (inst.opcode()) { + // Simple Instructions. + case SpvOpStore: + case SpvOpCopyMemory: + // Image instructions. + case SpvOpImageWrite: + // Atomic instructions. + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + case SpvOpAtomicFlagClear: + return true; + // Extensions. + case SpvOpExtInst: { + if (GetExtensionSet(ir_context, inst) != kExtensionSetName) { + return false; + } + + auto extension = static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1)); + return extension == GLSLstd450Modf || extension == GLSLstd450Frexp; + } + default: + return false; + } +} + +uint32_t TransformationMoveInstructionDown::GetMemoryWriteTarget( + opt::IRContext* ir_context, const opt::Instruction& inst) { + (void)ir_context; // |ir_context| is only used in assertions. + assert(IsMemoryWriteInstruction(ir_context, inst) && + "|inst| is not a memory write instruction"); + + switch (inst.opcode()) { + case SpvOpStore: + case SpvOpCopyMemory: + case SpvOpImageWrite: + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + case SpvOpAtomicFlagClear: + return inst.GetSingleWordInOperand(0); + case SpvOpExtInst: { + assert(GetExtensionSet(ir_context, inst) == kExtensionSetName && + "Extension set is not supported"); + + switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { + case GLSLstd450Modf: + case GLSLstd450Frexp: + return inst.GetSingleWordInOperand(3); + default: + // This assertion will fail if not all memory write extension + // instructions are handled in the switch. + assert(false && "Not all opcodes are handled"); + return 0; + } + } + default: + // This assertion will fail if not all memory write opcodes are handled in + // the switch. + assert(false && "Not all opcodes are handled"); + return 0; + } +} + +bool TransformationMoveInstructionDown::IsMemoryInstruction( + opt::IRContext* ir_context, const opt::Instruction& inst) { + return IsMemoryReadInstruction(ir_context, inst) || + IsMemoryWriteInstruction(ir_context, inst); +} + +bool TransformationMoveInstructionDown::IsBarrierInstruction( + const opt::Instruction& inst) { + switch (inst.opcode()) { + case SpvOpMemoryBarrier: + case SpvOpControlBarrier: + case SpvOpMemoryNamedBarrier: + return true; + default: + return false; + } +} + +bool TransformationMoveInstructionDown::CanSafelySwapInstructions( + opt::IRContext* ir_context, const opt::Instruction& a, + const opt::Instruction& b, const FactManager& fact_manager) { + assert(IsInstructionSupported(ir_context, a) && + IsInstructionSupported(ir_context, b) && + "Both opcodes must be supported"); + + // One of opcodes is simple - we can swap them without any side-effects. + if (IsSimpleInstruction(ir_context, a) || + IsSimpleInstruction(ir_context, b)) { + return true; + } + + // Both parameters are either memory instruction or barriers. + + // One of the opcodes is a barrier - can't swap them. + if (IsBarrierInstruction(a) || IsBarrierInstruction(b)) { + return false; + } + + // Both parameters are memory instructions. + + // Both parameters only read from memory - it's OK to swap them. + if (!IsMemoryWriteInstruction(ir_context, a) && + !IsMemoryWriteInstruction(ir_context, b)) { + return true; + } + + // At least one of parameters is a memory read instruction. + + // In theory, we can swap two memory instructions, one of which reads + // from the memory, if the read target (the pointer the memory is read from) + // and the write target (the memory is written into): + // - point to different memory regions + // - point to the same region with irrelevant value + // - point to the same region and the region is not used anymore. + // + // However, we can't currently determine if two pointers point to two + // different memory regions. That being said, if two pointers are not + // synonymous, they still might point to the same memory region. For example: + // %1 = OpVariable ... + // %2 = OpAccessChain %1 0 + // %3 = OpAccessChain %1 0 + // In this pseudo-code, %2 and %3 are not synonymous but point to the same + // memory location. This implies that we can't determine if some memory + // location is not used in the block. + // + // With this in mind, consider two cases (we will build a table for each one): + // - one instruction only reads from memory, the other one only writes to it. + // S - both point to the same memory region. + // D - both point to different memory regions. + // 0, 1, 2 - neither, one of or both of the memory regions are irrelevant. + // |-| - can't swap; |+| - can swap. + // | 0 | 1 | 2 | + // S : - + + + // D : + + + + // - both instructions write to memory. Notation is the same. + // | 0 | 1 | 2 | + // S : * + + + // D : + + + + // * - we can swap two instructions that write into the same non-irrelevant + // memory region if the written value is the same. + // + // Note that we can't always distinguish between S and D. Also note that + // in case of S, if one of the instructions is marked with + // PointeeValueIsIrrelevant, then the pointee of the other one is irrelevant + // as well even if the instruction is not marked with that fact. + // + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3723): + // This procedure can be improved when we can determine if two pointers point + // to different memory regions. + + // From now on we will denote an instruction that: + // - only reads from memory - R + // - only writes into memory - W + // - reads and writes - RW + // + // Both |a| and |b| can be either W or RW at this point. Additionally, at most + // one of them can be R. The procedure below checks all possible combinations + // of R, W and RW according to the tables above. We conservatively assume that + // both |a| and |b| point to the same memory region. + + auto memory_is_irrelevant = [ir_context, &fact_manager](uint32_t id) { + const auto* inst = ir_context->get_def_use_mgr()->GetDef(id); + if (!inst->type_id()) { + return false; + } + + const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id()); + assert(type && "|id| has invalid type"); + + if (!type->AsPointer()) { + return false; + } + + return fact_manager.PointeeValueIsIrrelevant(id); + }; + + if (IsMemoryWriteInstruction(ir_context, a) && + IsMemoryWriteInstruction(ir_context, b) && + (memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) || + memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b)))) { + // We ignore the case when the written value is the same. This is because + // the written value might not be equal to any of the instruction's + // operands. + return true; + } + + if (IsMemoryReadInstruction(ir_context, a) && + IsMemoryWriteInstruction(ir_context, b) && + !memory_is_irrelevant(GetMemoryReadTarget(ir_context, a)) && + !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b))) { + return false; + } + + if (IsMemoryWriteInstruction(ir_context, a) && + IsMemoryReadInstruction(ir_context, b) && + !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) && + !memory_is_irrelevant(GetMemoryReadTarget(ir_context, b))) { + return false; + } + + return IsMemoryReadInstruction(ir_context, a) || + IsMemoryReadInstruction(ir_context, b); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_move_instruction_down.h b/source/fuzz/transformation_move_instruction_down.h new file mode 100644 index 00000000..8b1e5edb --- /dev/null +++ b/source/fuzz/transformation_move_instruction_down.h @@ -0,0 +1,105 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMoveInstructionDown : public Transformation { + public: + explicit TransformationMoveInstructionDown( + const protobufs::TransformationMoveInstructionDown& message); + + explicit TransformationMoveInstructionDown( + const protobufs::InstructionDescriptor& instruction); + + // - |instruction| should be a descriptor of a valid instruction in the module + // - |instruction|'s opcode should be supported by this transformation + // - neither |instruction| nor its successor may be the last instruction in + // the block + // - |instruction|'s successor may not be dependent on the |instruction| + // - it should be possible to insert |instruction|'s opcode after its + // successor + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Swaps |instruction| with its successor. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + // Returns true if the |inst| is supported by this transformation. + static bool IsInstructionSupported(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns true if |inst| represents a "simple" instruction. That is, it + // neither reads from nor writes to the memory and is not a barrier. + static bool IsSimpleInstruction(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns true if |inst| reads from memory. + static bool IsMemoryReadInstruction(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns id being used by |inst| to read from. |inst| must be a memory read + // instruction (see IsMemoryReadInstruction). Returned id is not guaranteed to + // have pointer type. + static uint32_t GetMemoryReadTarget(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns true if |inst| that writes to the memory. + static bool IsMemoryWriteInstruction(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns id being used by |inst| to write into. |inst| must be a memory + // write instruction (see IsMemoryWriteInstruction). Returned id is not + // guaranteed to have pointer type. + static uint32_t GetMemoryWriteTarget(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns true if |inst| either reads from or writes to the memory + // (see IsMemoryReadInstruction and IsMemoryWriteInstruction accordingly). + static bool IsMemoryInstruction(opt::IRContext* ir_context, + const opt::Instruction& inst); + + // Returns true if |inst| is a barrier instruction. + static bool IsBarrierInstruction(const opt::Instruction& inst); + + // Returns true if it is possible to swap |a| and |b| without changing the + // module's semantics. |a| and |b| are required to be supported instructions + // (see IsInstructionSupported). In particular, if either |a| or |b| are + // memory or barrier instructions, some checks are used to only say that they + // can be swapped if the swap is definitely semantics-preserving. + static bool CanSafelySwapInstructions(opt::IRContext* ir_context, + const opt::Instruction& a, + const opt::Instruction& b, + const FactManager& fact_manager); + + protobufs::TransformationMoveInstructionDown message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_ diff --git a/source/fuzz/transformation_mutate_pointer.cpp b/source/fuzz/transformation_mutate_pointer.cpp new file mode 100644 index 00000000..93eb85ef --- /dev/null +++ b/source/fuzz/transformation_mutate_pointer.cpp @@ -0,0 +1,164 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_mutate_pointer.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationMutatePointer::TransformationMutatePointer( + const protobufs::TransformationMutatePointer& message) + : message_(message) {} + +TransformationMutatePointer::TransformationMutatePointer( + uint32_t pointer_id, uint32_t fresh_id, + const protobufs::InstructionDescriptor& insert_before) { + message_.set_pointer_id(pointer_id); + message_.set_fresh_id(fresh_id); + *message_.mutable_insert_before() = insert_before; +} + +bool TransformationMutatePointer::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // Check that |fresh_id| is fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + + auto* insert_before_inst = + FindInstruction(message_.insert_before(), ir_context); + + // Check that |insert_before| is a valid instruction descriptor. + if (!insert_before_inst) { + return false; + } + + // Check that it is possible to insert OpLoad and OpStore before + // |insert_before_inst|. We are only using OpLoad here since the result does + // not depend on the opcode. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, + insert_before_inst)) { + return false; + } + + const auto* pointer_inst = + ir_context->get_def_use_mgr()->GetDef(message_.pointer_id()); + + // Check that |pointer_id| is a result id of a valid pointer instruction. + if (!pointer_inst || !IsValidPointerInstruction(ir_context, *pointer_inst)) { + return false; + } + + // Check that the module contains an irrelevant constant that will be used to + // mutate |pointer_inst|. The constant is irrelevant so that the latter + // transformation can change its value to something more interesting. + auto constant_id = fuzzerutil::MaybeGetZeroConstant( + ir_context, transformation_context, + fuzzerutil::GetPointeeTypeIdFromPointerType(ir_context, + pointer_inst->type_id()), + true); + if (!constant_id) { + return false; + } + + assert(fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, insert_before_inst, constant_id) && + "Global constant instruction is not available before " + "|insert_before_inst|"); + + // Check that |pointer_inst| is available before |insert_before_inst|. + return fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, insert_before_inst, pointer_inst->result_id()); +} + +void TransformationMutatePointer::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + auto* insert_before_inst = + FindInstruction(message_.insert_before(), ir_context); + assert(insert_before_inst && "|insert_before| descriptor is invalid"); + + auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType( + ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id())); + + // Back up the original value. + insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpLoad, pointee_type_id, message_.fresh_id(), + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}})); + + // Insert a new value. + insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpStore, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, + {SPV_OPERAND_TYPE_ID, + {fuzzerutil::MaybeGetZeroConstant( + ir_context, *transformation_context, pointee_type_id, true)}}})); + + // Restore the original value. + insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpStore, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}})); + + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + + // Make sure analyses represent the correct state of the module. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationMutatePointer::ToMessage() const { + protobufs::Transformation result; + *result.mutable_mutate_pointer() = message_; + return result; +} + +bool TransformationMutatePointer::IsValidPointerInstruction( + opt::IRContext* ir_context, const opt::Instruction& inst) { + // |inst| must have both result id and type id and it may not cause undefined + // behaviour. + if (!inst.result_id() || !inst.type_id() || inst.opcode() == SpvOpUndef || + inst.opcode() == SpvOpConstantNull) { + return false; + } + + const auto* type = ir_context->get_type_mgr()->GetType(inst.type_id()); + assert(type && "|inst| has invalid type id"); + + const auto* pointer_type = type->AsPointer(); + + // |inst| must be a pointer. + if (!pointer_type) { + return false; + } + + // |inst| must have a supported storage class. + if (pointer_type->storage_class() != SpvStorageClassFunction && + pointer_type->storage_class() != SpvStorageClassPrivate && + pointer_type->storage_class() != SpvStorageClassWorkgroup) { + return false; + } + + // |inst|'s pointee must consist of scalars and/or composites. + return fuzzerutil::CanCreateConstant(*pointer_type->pointee_type()); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_mutate_pointer.h b/source/fuzz/transformation_mutate_pointer.h new file mode 100644 index 00000000..a411b651 --- /dev/null +++ b/source/fuzz/transformation_mutate_pointer.h @@ -0,0 +1,77 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_MUTATE_POINTER_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MUTATE_POINTER_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMutatePointer : public Transformation { + public: + explicit TransformationMutatePointer( + const protobufs::TransformationMutatePointer& message); + + explicit TransformationMutatePointer( + uint32_t pointer_id, uint32_t fresh_id, + const protobufs::InstructionDescriptor& insert_before); + + // - |fresh_id| must be fresh. + // - |insert_before| must be a valid instruction descriptor of some + // instruction in the module. + // - It should be possible to insert OpLoad and OpStore before + // |insert_before|. + // - |pointer_id| must be a result id of some instruction in the module. + // - Instruction with result id |pointer_id| must be valid (see + // IsValidPointerInstruction method). + // - There must exist an irrelevant constant in the module. Type of the + // constant must be equal to the type of the |pointer_id|'s pointee. + // - |pointer_id| must be available (according to the dominance rules) before + // |insert_before|. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Inserts the following instructions before |insert_before|: + // %fresh_id = OpLoad %pointee_type_id %pointer_id + // OpStore %pointer_id %constant_id + // OpStore %pointer_id %fresh_id + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if |inst| valid pointer according to the following: + // - |inst| has result id and type id. + // - |inst| is neither OpUndef nor OpConstantNull. + // - |inst| has a pointer type. + // - |inst|'s storage class is either Private, Function or Workgroup. + // - |inst|'s pointee type and all its constituents are either scalar or + // composite. + static bool IsValidPointerInstruction(opt::IRContext* ir_context, + const opt::Instruction& inst); + + private: + protobufs::TransformationMutatePointer message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MUTATE_POINTER_H_ diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp index 05fd923c..30da7299 100644 --- a/source/fuzz/transformation_outline_function.cpp +++ b/source/fuzz/transformation_outline_function.cpp @@ -21,20 +21,6 @@ namespace spvtools { namespace fuzz { -namespace { - -std::map<uint32_t, uint32_t> PairSequenceToMap( - const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>& - pair_sequence) { - std::map<uint32_t, uint32_t> result; - for (auto& pair : pair_sequence) { - result[pair.first()] = pair.second(); - } - return result; -} - -} // namespace - TransformationOutlineFunction::TransformationOutlineFunction( const spvtools::fuzz::protobufs::TransformationOutlineFunction& message) : message_(message) {} @@ -55,22 +41,15 @@ TransformationOutlineFunction::TransformationOutlineFunction( message_.set_new_function_region_entry_block(new_function_region_entry_block); message_.set_new_caller_result_id(new_caller_result_id); message_.set_new_callee_result_id(new_callee_result_id); - for (auto& entry : input_id_to_fresh_id) { - protobufs::UInt32Pair pair; - pair.set_first(entry.first); - pair.set_second(entry.second); - *message_.add_input_id_to_fresh_id() = pair; - } - for (auto& entry : output_id_to_fresh_id) { - protobufs::UInt32Pair pair; - pair.set_first(entry.first); - pair.set_second(entry.second); - *message_.add_output_id_to_fresh_id() = pair; - } + *message_.mutable_input_id_to_fresh_id() = + fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id); + *message_.mutable_output_id_to_fresh_id() = + fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id); } bool TransformationOutlineFunction::IsApplicable( - opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { std::set<uint32_t> ids_used_by_this_transformation; // The various new ids used by the transformation must be fresh and distinct. @@ -252,12 +231,13 @@ bool TransformationOutlineFunction::IsApplicable( // For each region input id, i.e. every id defined outside the region but // used inside the region, ... - std::map<uint32_t, uint32_t> input_id_to_fresh_id_map = - PairSequenceToMap(message_.input_id_to_fresh_id()); + auto input_id_to_fresh_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id()); for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) { // There needs to be a corresponding fresh id to be used as a function - // parameter. - if (input_id_to_fresh_id_map.count(id) == 0) { + // parameter, or overflow ids need to be available. + if (input_id_to_fresh_id_map.count(id) == 0 && + !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { return false; } // Furthermore, if the input id has pointer type it must be an OpVariable @@ -280,13 +260,15 @@ bool TransformationOutlineFunction::IsApplicable( // For each region output id -- i.e. every id defined inside the region but // used outside the region, ... - std::map<uint32_t, uint32_t> output_id_to_fresh_id_map = - PairSequenceToMap(message_.output_id_to_fresh_id()); + auto output_id_to_fresh_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id()); for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) { if ( // ... there needs to be a corresponding fresh id that can hold the - // value for this id computed in the outlined function, and ... - output_id_to_fresh_id_map.count(id) == 0 + // value for this id computed in the outlined function (or overflow ids + // must be available), and ... + (output_id_to_fresh_id_map.count(id) == 0 && + !transformation_context.GetOverflowIdSource()->HasOverflowIds()) // ... the output id must not have pointer type (to avoid creating a // struct with pointer members to pass data out of the outlined // function) @@ -323,10 +305,27 @@ void TransformationOutlineFunction::Apply( GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block); // Maps from input and output ids to fresh ids. - std::map<uint32_t, uint32_t> input_id_to_fresh_id_map = - PairSequenceToMap(message_.input_id_to_fresh_id()); - std::map<uint32_t, uint32_t> output_id_to_fresh_id_map = - PairSequenceToMap(message_.output_id_to_fresh_id()); + auto input_id_to_fresh_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id()); + auto output_id_to_fresh_id_map = + fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id()); + + // Use overflow ids to augment these maps at any locations where fresh ids are + // required but not provided. + for (uint32_t id : region_input_ids) { + if (input_id_to_fresh_id_map.count(id) == 0) { + input_id_to_fresh_id_map.insert( + {id, + transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); + } + } + for (uint32_t id : region_output_ids) { + if (output_id_to_fresh_id_map.count(id) == 0) { + output_id_to_fresh_id_map.insert( + {id, + transformation_context->GetOverflowIdSource()->GetNextOverflowId()}); + } + } UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map, output_id_to_fresh_id_map); @@ -452,7 +451,6 @@ std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds( inst, [ir_context, &inst, region_exit_block, ®ion_set, &result]( opt::Instruction* use, uint32_t /*unused*/) -> bool { - // Find the block in which this id use occurs, recording the id as // an input id if the block is outside the region, with some // exceptions detailed below. @@ -501,7 +499,6 @@ std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds( &inst, [®ion_set, ir_context, &inst, region_exit_block, &result]( opt::Instruction* use, uint32_t /*unused*/) -> bool { - // Find the block in which this id use occurs, recording the id as // an output id if the block is outside the region, with some // exceptions detailed below. @@ -631,12 +628,22 @@ TransformationOutlineFunction::PrepareFunctionPrototype( {function_type_id}}}))); // Add one parameter to the function for each input id, using the fresh ids - // provided in |input_id_to_fresh_id_map|. + // provided in |input_id_to_fresh_id_map|, or overflow ids if needed. for (auto id : region_input_ids) { + uint32_t fresh_id = input_id_to_fresh_id_map.at(id); outlined_function->AddParameter(MakeUnique<opt::Instruction>( ir_context, SpvOpFunctionParameter, - ir_context->get_def_use_mgr()->GetDef(id)->type_id(), - input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList())); + ir_context->get_def_use_mgr()->GetDef(id)->type_id(), fresh_id, + opt::Instruction::OperandList())); + + // Analyse the use of the new parameter instruction. + outlined_function->ForEachParam( + [fresh_id, ir_context](opt::Instruction* inst) { + if (inst->result_id() == fresh_id) { + ir_context->AnalyzeDefUse(inst); + } + }); + // If the input id is an irrelevant-valued variable, the same should be true // of the corresponding parameter. if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant( diff --git a/source/fuzz/transformation_outline_function.h b/source/fuzz/transformation_outline_function.h index ba439c84..dac43464 100644 --- a/source/fuzz/transformation_outline_function.h +++ b/source/fuzz/transformation_outline_function.h @@ -73,7 +73,7 @@ class TransformationOutlineFunction : public Transformation { // - Unless the type required for the new function is already known, // |message_.new_function_type_id| is used as the type id for a new function // type, and the new function uses this type. - // - The new function starts with a dummy block with id + // - The new function starts with a placeholder block with id // |message_.new_function_first_block|, which jumps straight to a successor // block, to avoid violating rules on what the first block in a function may // look like. diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp index 36984802..c4de743d 100644 --- a/source/fuzz/transformation_permute_function_parameters.cpp +++ b/source/fuzz/transformation_permute_function_parameters.cpp @@ -87,38 +87,6 @@ void TransformationPermuteFunctionParameters::Apply( auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id()); assert(function && "Can't find the function"); - auto* old_function_type_inst = - fuzzerutil::GetFunctionType(ir_context, function); - assert(old_function_type_inst && "Function must have a valid type"); - - std::vector<uint32_t> type_ids = { - old_function_type_inst->GetSingleWordInOperand(0)}; - for (auto index : message_.permutation()) { - // +1 since the first operand to OpTypeFunction is a return type. - type_ids.push_back( - old_function_type_inst->GetSingleWordInOperand(index + 1)); - } - - // Change function's type. - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type_inst) == 1 && - fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) { - // If only the current function uses |old_function_type_inst| - change it - // in-place. We can only do that if the module doesn't contain - // a function type with the permuted order of operands. - opt::Instruction::OperandList permuted_operands; - for (auto id : type_ids) { - // +1 since the first operand to OpTypeFunction is a return type. - permuted_operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); - } - - old_function_type_inst->SetInOperands(std::move(permuted_operands)); - } else { - // Either use an existing type or create a new one. - function->DefInst().SetInOperand( - 1, {fuzzerutil::FindOrCreateFunctionType( - ir_context, message_.function_type_fresh_id(), type_ids)}); - } - // Adjust OpFunctionParameter instructions // Collect ids and types from OpFunctionParameter instructions @@ -159,6 +127,28 @@ void TransformationPermuteFunctionParameters::Apply( call->SetInOperands(std::move(call_operands)); } + // Update function type. + { + // We use a separate scope here since |old_function_type_inst| might become + // a dangling pointer after the call to the fuzzerutil::UpdateFunctionType. + + auto* old_function_type_inst = + fuzzerutil::GetFunctionType(ir_context, function); + assert(old_function_type_inst && "Function must have a valid type"); + + std::vector<uint32_t> parameter_type_ids; + for (auto index : message_.permutation()) { + // +1 since the first operand to OpTypeFunction is a return type. + parameter_type_ids.push_back( + old_function_type_inst->GetSingleWordInOperand(index + 1)); + } + + // Change function's type. + fuzzerutil::UpdateFunctionType( + ir_context, function->result_id(), message_.function_type_fresh_id(), + old_function_type_inst->GetSingleWordInOperand(0), parameter_type_ids); + } + // Make sure our changes are analyzed ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); diff --git a/source/fuzz/transformation_permute_phi_operands.cpp b/source/fuzz/transformation_permute_phi_operands.cpp index 95e7a1f4..8e5bbd9d 100644 --- a/source/fuzz/transformation_permute_phi_operands.cpp +++ b/source/fuzz/transformation_permute_phi_operands.cpp @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/transformation_permute_phi_operands.h" + #include <vector> #include "source/fuzz/fuzzer_util.h" -#include "source/fuzz/transformation_permute_phi_operands.h" namespace spvtools { namespace fuzz { diff --git a/source/fuzz/transformation_propagate_instruction_up.cpp b/source/fuzz/transformation_propagate_instruction_up.cpp new file mode 100644 index 00000000..adf3a516 --- /dev/null +++ b/source/fuzz/transformation_propagate_instruction_up.cpp @@ -0,0 +1,402 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_propagate_instruction_up.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { +namespace { + +uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst, + uint32_t label_id) { + assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi"); + + for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) { + if (phi_inst.GetSingleWordInOperand(i) == label_id) { + return phi_inst.GetSingleWordInOperand(i - 1); + } + } + + return 0; +} + +bool ContainsPointers(const opt::analysis::Type& type) { + switch (type.kind()) { + case opt::analysis::Type::kPointer: + return true; + case opt::analysis::Type::kStruct: + return std::any_of(type.AsStruct()->element_types().begin(), + type.AsStruct()->element_types().end(), + [](const opt::analysis::Type* element_type) { + return ContainsPointers(*element_type); + }); + default: + return false; + } +} + +bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) { + const auto* inst_block = ir_context->get_instr_block(inst); + assert(inst_block && + "This function shouldn't be applied to global instructions or function" + "parameters"); + + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + const auto& operand = inst->GetInOperand(i); + if (operand.type != SPV_OPERAND_TYPE_ID) { + // Consider only <id> operands. + continue; + } + + auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]); + assert(dependency && "Operand has invalid id"); + + if (ir_context->get_instr_block(dependency) == inst_block && + dependency->opcode() != SpvOpPhi) { + // |dependency| is "valid" if it's an OpPhi from the same basic block or + // an instruction from a different basic block. + return false; + } + } + + return true; +} + +} // namespace + +TransformationPropagateInstructionUp::TransformationPropagateInstructionUp( + const protobufs::TransformationPropagateInstructionUp& message) + : message_(message) {} + +TransformationPropagateInstructionUp::TransformationPropagateInstructionUp( + uint32_t block_id, + const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) { + message_.set_block_id(block_id); + *message_.mutable_predecessor_id_to_fresh_id() = + fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id); +} + +bool TransformationPropagateInstructionUp::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // Check that we can apply this transformation to the |block_id|. + if (!IsApplicableToBlock(ir_context, message_.block_id())) { + return false; + } + + const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap( + message_.predecessor_id_to_fresh_id()); + for (auto id : ir_context->cfg()->preds(message_.block_id())) { + // Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id| + // map. + if (!predecessor_id_to_fresh_id.count(id)) { + return false; + } + } + + std::vector<uint32_t> maybe_fresh_ids; + maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size()); + for (const auto& entry : predecessor_id_to_fresh_id) { + maybe_fresh_ids.push_back(entry.second); + } + + // All ids must be unique and fresh. + return !fuzzerutil::HasDuplicates(maybe_fresh_ids) && + std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(), + [ir_context](uint32_t id) { + return fuzzerutil::IsFreshId(ir_context, id); + }); +} + +void TransformationPropagateInstructionUp::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto* inst = GetInstructionToPropagate(ir_context, message_.block_id()); + assert(inst && + "The block must have at least one supported instruction to propagate"); + assert(inst->result_id() && inst->type_id() && + "|inst| must have a result id and a type id"); + + opt::Instruction::OperandList op_phi_operands; + const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap( + message_.predecessor_id_to_fresh_id()); + std::unordered_set<uint32_t> visited_predecessors; + for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) { + // A block can have multiple identical predecessors. + if (visited_predecessors.count(predecessor_id)) { + continue; + } + + visited_predecessors.insert(predecessor_id); + + auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id); + + // Compute InOperands for the OpPhi instruction to be inserted later. + op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}}); + op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}}); + + // Create a clone of the |inst| to be inserted into the |predecessor_id|. + std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context)); + clone->SetResultId(new_result_id); + + fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id); + + // Adjust |clone|'s operands to account for possible dependencies on OpPhi + // instructions from the same basic block. + for (uint32_t i = 0; i < clone->NumInOperands(); ++i) { + auto& operand = clone->GetInOperand(i); + if (operand.type != SPV_OPERAND_TYPE_ID) { + // Consider only ids. + continue; + } + + const auto* dependency_inst = + ir_context->get_def_use_mgr()->GetDef(operand.words[0]); + assert(dependency_inst && "|clone| depends on an invalid id"); + + if (ir_context->get_instr_block(dependency_inst->result_id()) != + ir_context->cfg()->block(message_.block_id())) { + // We don't need to adjust anything if |dependency_inst| is from a + // different block, a global instruction or a function parameter. + continue; + } + + assert(dependency_inst->opcode() == SpvOpPhi && + "Propagated instruction can depend only on OpPhis from the same " + "basic block or instructions from different basic blocks"); + + auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id); + assert(new_id && "OpPhi instruction is missing a predecessor"); + operand.words[0] = new_id; + } + + auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, predecessor_id, clone->opcode()); + assert(insert_before_inst && "Can't insert |clone| into |predecessor_id"); + + insert_before_inst->InsertBefore(std::move(clone)); + } + + // Insert an OpPhi instruction into the basic block of |inst|. + ir_context->get_instr_block(inst)->begin()->InsertBefore( + MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, inst->type_id(), + inst->result_id(), + std::move(op_phi_operands))); + + // Remove |inst| from the basic block. + ir_context->KillInst(inst); + + // We have changed the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationPropagateInstructionUp::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_propagate_instruction_up() = message_; + return result; +} + +bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) { + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): + // We only support "simple" instructions that don't work with memory. + // We should extend this so that we support the ones that modify the memory + // too. + switch (opcode) { + case SpvOpUndef: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpArrayLength: + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpCompositeInsert: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: + case SpvOpAny: + case SpvOpAll: + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpSelect: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitFieldInsert: + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: + case SpvOpBitReverse: + case SpvOpBitCount: + case SpvOpCopyLogical: + case SpvOpPtrEqual: + case SpvOpPtrNotEqual: + return true; + default: + return false; + } +} + +opt::Instruction* +TransformationPropagateInstructionUp::GetInstructionToPropagate( + opt::IRContext* ir_context, uint32_t block_id) { + auto* block = ir_context->cfg()->block(block_id); + assert(block && "|block_id| is invalid"); + + for (auto& inst : *block) { + // We look for the first instruction in the block that satisfies the + // following rules: + // - it's not an OpPhi + // - it must be supported by this transformation + // - it may depend only on instructions from different basic blocks or on + // OpPhi instructions from the same basic block. + if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) || + !inst.type_id() || !inst.result_id()) { + continue; + } + + const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id()); + assert(inst_type && "|inst| has invalid type"); + + if (!ir_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer) && + ContainsPointers(*inst_type)) { + // OpPhi supports pointer operands only with VariablePointers or + // VariablePointersStorageBuffer capabilities. + // + // Note that VariablePointers capability implicitly declares + // VariablePointersStorageBuffer capability. + continue; + } + + if (!HasValidDependencies(ir_context, &inst)) { + continue; + } + + return &inst; + } + + return nullptr; +} + +bool TransformationPropagateInstructionUp::IsApplicableToBlock( + opt::IRContext* ir_context, uint32_t block_id) { + // Check that |block_id| is valid. + const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id); + if (!label_inst || label_inst->opcode() != SpvOpLabel) { + return false; + } + + // Check that |block| has predecessors. + const auto& predecessors = ir_context->cfg()->preds(block_id); + if (predecessors.empty()) { + return false; + } + + // The block must contain an instruction to propagate. + const auto* inst_to_propagate = + GetInstructionToPropagate(ir_context, block_id); + if (!inst_to_propagate) { + return false; + } + + // We should be able to insert |inst_to_propagate| into every predecessor of + // |block|. + return std::all_of(predecessors.begin(), predecessors.end(), + [ir_context, inst_to_propagate](uint32_t predecessor_id) { + return fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, predecessor_id, + inst_to_propagate->opcode()) != nullptr; + }); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_propagate_instruction_up.h b/source/fuzz/transformation_propagate_instruction_up.h new file mode 100644 index 00000000..8e237496 --- /dev/null +++ b/source/fuzz/transformation_propagate_instruction_up.h @@ -0,0 +1,89 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_ +#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_ + +#include <map> + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationPropagateInstructionUp : public Transformation { + public: + explicit TransformationPropagateInstructionUp( + const protobufs::TransformationPropagateInstructionUp& message); + + TransformationPropagateInstructionUp( + uint32_t block_id, + const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id); + + // - |block_id| must be a valid result id of some OpLabel instruction. + // - |block_id| must have at least one predecessor + // - |block_id| must contain an instruction that can be propagated using this + // transformation + // - the instruction can be propagated if: + // - it's not an OpPhi + // - it is supported by this transformation + // - it depends only on instructions from different basic blocks or on + // OpPhi instructions from the same basic block + // - it should be possible to insert the propagated instruction at the end of + // each |block_id|'s predecessor + // - |predecessor_id_to_fresh_id| must have an entry for at least every + // predecessor of |block_id| + // - each value in the |predecessor_id_to_fresh_id| map must be a fresh id + // - all fresh ids in the |predecessor_id_to_fresh_id| must be unique + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Inserts a copy of the propagated instruction into each |block_id|'s + // predecessor. Replaces the original instruction with an OpPhi referring + // inserted copies. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns true if this transformation can be applied to the block with id + // |block_id|. Concretely, returns true iff: + // - |block_id| is a valid id of some block in the module + // - |block_id| has predecessors + // - |block_id| contains an instruction that can be propagated + // - it is possible to insert the propagated instruction into every + // |block_id|'s predecessor + static bool IsApplicableToBlock(opt::IRContext* ir_context, + uint32_t block_id); + + private: + // Returns the instruction that will be propagated into the predecessors of + // the |block_id|. Returns nullptr if no such an instruction exists. + static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context, + uint32_t block_id); + + // Returns true if |opcode| is supported by this transformation. + static bool IsOpcodeSupported(SpvOp opcode); + + protobufs::TransformationPropagateInstructionUp message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_ diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp index 131473c7..e7494d47 100644 --- a/source/fuzz/transformation_push_id_through_variable.cpp +++ b/source/fuzz/transformation_push_id_through_variable.cpp @@ -74,9 +74,11 @@ bool TransformationPushIdThroughVariable::IsApplicable( return false; } - // |value_id| may not be an irrelevant id. - if (transformation_context.GetFactManager()->IdIsIrrelevant( - message_.value_id())) { + // We should be able to create a synonym of |value_id| if it's not irrelevant. + if (!transformation_context.GetFactManager()->IdIsIrrelevant( + message_.value_id()) && + !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context, + value_instruction)) { return false; } @@ -151,11 +153,14 @@ void TransformationPushIdThroughVariable::Apply( ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); - // Adds the fact that |message_.value_synonym_id| - // and |message_.value_id| are synonymous. - transformation_context->GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(message_.value_synonym_id(), {}), - MakeDataDescriptor(message_.value_id(), {}), ir_context); + if (!transformation_context->GetFactManager()->IdIsIrrelevant( + message_.value_id())) { + // Adds the fact that |message_.value_synonym_id| + // and |message_.value_id| are synonymous. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.value_synonym_id(), {}), + MakeDataDescriptor(message_.value_id(), {})); + } } protobufs::Transformation TransformationPushIdThroughVariable::ToMessage() diff --git a/source/fuzz/transformation_push_id_through_variable.h b/source/fuzz/transformation_push_id_through_variable.h index 81e58928..f49db317 100644 --- a/source/fuzz/transformation_push_id_through_variable.h +++ b/source/fuzz/transformation_push_id_through_variable.h @@ -36,7 +36,6 @@ class TransformationPushIdThroughVariable : public Transformation { // - |message_.value_id| must be an instruction result id that has the same // type as the pointee type of |message_.pointer_id| - // - |value_id| may not be an irrelevant id. // - |message_.value_synonym_id| must be fresh // - |message_.variable_id| must be fresh // - |message_.variable_storage_class| must be either StorageClassPrivate or @@ -52,8 +51,8 @@ class TransformationPushIdThroughVariable : public Transformation { const TransformationContext& transformation_context) const override; // Stores |value_id| to |variable_id|, loads |variable_id| to - // |value_synonym_id| and adds the fact that |value_synonym_id| and |value_id| - // are synonymous. + // |value_synonym_id|. Adds the fact that |value_synonym_id| and |value_id| + // are synonymous if |value_id| is not irrelevant. void Apply(opt::IRContext* ir_context, TransformationContext* transformation_context) const override; diff --git a/source/fuzz/transformation_record_synonymous_constants.cpp b/source/fuzz/transformation_record_synonymous_constants.cpp index b1568bff..a9c34022 100644 --- a/source/fuzz/transformation_record_synonymous_constants.cpp +++ b/source/fuzz/transformation_record_synonymous_constants.cpp @@ -15,6 +15,8 @@ #include "transformation_record_synonymous_constants.h" +#include "source/fuzz/fuzzer_util.h" + namespace spvtools { namespace fuzz { @@ -50,12 +52,12 @@ bool TransformationRecordSynonymousConstants::IsApplicable( } void TransformationRecordSynonymousConstants::Apply( - opt::IRContext* ir_context, + opt::IRContext* /*unused*/, TransformationContext* transformation_context) const { // Add the fact to the fact manager transformation_context->GetFactManager()->AddFactDataSynonym( MakeDataDescriptor(message_.constant1_id(), {}), - MakeDataDescriptor(message_.constant2_id(), {}), ir_context); + MakeDataDescriptor(message_.constant2_id(), {})); } protobufs::Transformation TransformationRecordSynonymousConstants::ToMessage() @@ -76,19 +78,17 @@ bool TransformationRecordSynonymousConstants::AreEquivalentConstants( return false; } - // The type ids must be the same - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536): Somehow - // relax this for integers (so that unsigned integer and signed integer are - // considered the same type) - if (def_1->type_id() != def_2->type_id()) { - return false; - } - auto constant1 = ir_context->get_constant_mgr()->GetConstantFromInst(def_1); auto constant2 = ir_context->get_constant_mgr()->GetConstantFromInst(def_2); assert(constant1 && constant2 && "The ids must refer to constants."); + // The types must be compatible. + if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, def_1->type_id(), + def_2->type_id())) { + return false; + } + // If either constant is null, the other is equivalent iff it is zero-like if (constant1->AsNullConstant()) { return constant2->IsZero(); diff --git a/source/fuzz/transformation_record_synonymous_constants.h b/source/fuzz/transformation_record_synonymous_constants.h index b28eeb3b..8cff0cda 100644 --- a/source/fuzz/transformation_record_synonymous_constants.h +++ b/source/fuzz/transformation_record_synonymous_constants.h @@ -32,16 +32,17 @@ class TransformationRecordSynonymousConstants : public Transformation { // - |message_.constant_id| and |message_.synonym_id| are distinct ids // of constants // - |message_.constant_id| and |message_.synonym_id| refer to constants - // that are equal or equivalent. - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536): Signed and - // unsigned integers are currently considered non-equivalent - // Two integers with the same width and value are equal, even if one is - // signed and the other is not. - // Constants are equivalent: - // - if both of them represent zero-like values of the same type - // - if they are composite constants with the same type and their - // components are pairwise equivalent. - // - |constant1_id| and |constant2_id| may not be irrelevant. + // that are equivalent. + // Constants are equivalent if at least one of the following holds: + // - they are equal (i.e. they have the same type ids and equal values) + // - both of them represent zero-like values of compatible types + // - they are composite constants with compatible types and their + // components are pairwise equivalent + // Two types are compatible if at least one of the following holds: + // - they have the same id + // - they are integer scalar types with the same width + // - they are integer vectors and their components have the same width + // (this is always the case if the components are equivalent) bool IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const override; diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp new file mode 100644 index 00000000..ea84cf25 --- /dev/null +++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp @@ -0,0 +1,232 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +namespace { +const uint32_t kOpCompositeExtractIndexLowOrderBits = 0; +const uint32_t kArithmeticInstructionIndexLeftInOperand = 0; +const uint32_t kArithmeticInstructionIndexRightInOperand = 1; +} // namespace + +TransformationReplaceAddSubMulWithCarryingExtended:: + TransformationReplaceAddSubMulWithCarryingExtended( + const spvtools::fuzz::protobufs:: + TransformationReplaceAddSubMulWithCarryingExtended& message) + : message_(message) {} + +TransformationReplaceAddSubMulWithCarryingExtended:: + TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id, + uint32_t result_id) { + message_.set_struct_fresh_id(struct_fresh_id); + message_.set_result_id(result_id); +} + +bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.struct_fresh_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) { + return false; + } + + // |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul + // instruction. The instruction must be defined. + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.result_id()); + if (instruction == nullptr) { + return false; + } + if (!TransformationReplaceAddSubMulWithCarryingExtended:: + IsInstructionSuitable(ir_context, *instruction)) { + return false; + } + + // The struct type for holding the intermediate result must exist in the + // module. The struct type is based on the operand type. + uint32_t operand_type_id = ir_context->get_def_use_mgr() + ->GetDef(instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t struct_type_id = fuzzerutil::MaybeGetStructType( + ir_context, {operand_type_id, operand_type_id}); + if (struct_type_id == 0) { + return false; + } + return true; +} + +void TransformationReplaceAddSubMulWithCarryingExtended::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + // |message_.struct_fresh_id| must be fresh. + assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) && + "|message_.struct_fresh_id| must be fresh"); + + // Get the signedness of an operand if it is an int or the signedness of a + // component if it is a vector. + auto type_id = + ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id(); + auto type = ir_context->get_type_mgr()->GetType(type_id); + bool operand_is_signed; + if (type->kind() == opt::analysis::Type::kVector) { + auto operand_type = type->AsVector()->element_type(); + operand_is_signed = operand_type->AsInteger()->IsSigned(); + } else { + operand_is_signed = type->AsInteger()->IsSigned(); + } + + auto original_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.result_id()); + + fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id()); + + // Determine the opcode of the new instruction that computes the result into a + // struct. + SpvOp new_instruction_opcode; + + switch (original_instruction->opcode()) { + case SpvOpIAdd: + new_instruction_opcode = SpvOpIAddCarry; + break; + case SpvOpISub: + new_instruction_opcode = SpvOpISubBorrow; + break; + case SpvOpIMul: + if (!operand_is_signed) { + new_instruction_opcode = SpvOpUMulExtended; + } else { + new_instruction_opcode = SpvOpSMulExtended; + } + break; + default: + assert(false && "The instruction has an unsupported opcode."); + return; + } + // Get the type of struct type id holding the intermediate result based on the + // operand type. + uint32_t operand_type_id = + ir_context->get_def_use_mgr() + ->GetDef(original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t struct_type_id = fuzzerutil::MaybeGetStructType( + ir_context, {operand_type_id, operand_type_id}); + // Avoid unused variables in release mode. + (void)struct_type_id; + assert(struct_type_id && "The struct type must exist in the module."); + + // Insert the new instruction that computes the result into a struct before + // the |original_instruction|. + original_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, new_instruction_opcode, struct_type_id, + message_.struct_fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)}}, + {SPV_OPERAND_TYPE_ID, + {original_instruction->GetSingleWordInOperand( + kArithmeticInstructionIndexRightInOperand)}}}))); + + // Insert the OpCompositeExtract after the added instruction. This instruction + // takes the first component of the struct which represents low-order bits of + // the operation. This is the original result. + original_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeExtract, original_instruction->type_id(), + message_.result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {kOpCompositeExtractIndexLowOrderBits}}}))); + + // Remove the original instruction. + ir_context->KillInst(original_instruction); + + // We have modified the module so most analyzes are now invalid. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable( + opt::IRContext* ir_context, const opt::Instruction& instruction) { + auto instruction_opcode = instruction.opcode(); + + // Only instructions OpIAdd, OpISub, OpIMul are supported. + switch (instruction_opcode) { + case SpvOpIAdd: + case SpvOpISub: + case SpvOpIMul: + break; + default: + return false; + } + uint32_t operand_1_type_id = + ir_context->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexLeftInOperand)) + ->type_id(); + + uint32_t operand_2_type_id = + ir_context->get_def_use_mgr() + ->GetDef(instruction.GetSingleWordInOperand( + kArithmeticInstructionIndexRightInOperand)) + ->type_id(); + + uint32_t result_type_id = instruction.type_id(); + + // Both type ids of the operands and the result type ids must be equal. + if (operand_1_type_id != operand_2_type_id) { + return false; + } + if (operand_2_type_id != result_type_id) { + return false; + } + + // In case of OpIAdd and OpISub, the type must be unsigned. + auto type = ir_context->get_type_mgr()->GetType(instruction.type_id()); + + switch (instruction_opcode) { + case SpvOpIAdd: + case SpvOpISub: { + // In case of OpIAdd and OpISub if the operand is a vector, the component + // type must be unsigned. Otherwise (if the operand is an int), the + // operand must be unsigned. + bool operand_is_signed = + type->AsVector() + ? type->AsVector()->element_type()->AsInteger()->IsSigned() + : type->AsInteger()->IsSigned(); + if (operand_is_signed) { + return false; + } + } break; + default: + break; + } + return true; +} + +protobufs::Transformation +TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_add_sub_mul_with_carrying_extended() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h new file mode 100644 index 00000000..49ca9427 --- /dev/null +++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceAddSubMulWithCarryingExtended + : public Transformation { + public: + explicit TransformationReplaceAddSubMulWithCarryingExtended( + const protobufs::TransformationReplaceAddSubMulWithCarryingExtended& + message); + + explicit TransformationReplaceAddSubMulWithCarryingExtended( + uint32_t struct_fresh_id, uint32_t result_id); + + // - |message_.struct_fresh_id| must be fresh. + // - |message_.result_id| must refer to an OpIAdd or OpISub or OpIMul + // instruction. In this instruction the result type id and the type ids of + // the operands must be the same. + // - The type of struct holding the intermediate result must exists in the + // module. + // - For OpIAdd, OpISub both operands must be unsigned. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // A transformation that replaces instructions OpIAdd, OpISub, OpIMul with + // pairs of instructions. The first one (OpIAddCarry, OpISubBorrow, + // OpUMulExtended, OpSMulExtended) computes the result into a struct. The + // second one extracts the appropriate component from the struct to yield the + // original result. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + // Checks if an OpIAdd, OpISub or OpIMul instruction can be used by the + // transformation. + bool static IsInstructionSuitable(opt::IRContext* ir_context, + const opt::Instruction& instruction); + + private: + protobufs::TransformationReplaceAddSubMulWithCarryingExtended message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H_ diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp index a8f94954..b7f40ef7 100644 --- a/source/fuzz/transformation_replace_constant_with_uniform.cpp +++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp @@ -90,6 +90,40 @@ TransformationReplaceConstantWithUniform::MakeLoadInstruction( operands_for_load); } +opt::Instruction* +TransformationReplaceConstantWithUniform::GetInsertBeforeInstruction( + opt::IRContext* ir_context) const { + auto* result = + FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); + if (!result) { + return nullptr; + } + + // The use might be in an OpPhi instruction. + if (result->opcode() == SpvOpPhi) { + // OpPhi instructions must be the first instructions in a block. Thus, we + // can't insert above the OpPhi instruction. Given the predecessor block + // that corresponds to the id use, get the last instruction in that block + // above which we can insert OpAccessChain and OpLoad. + return fuzzerutil::GetLastInsertBeforeInstruction( + ir_context, + result->GetSingleWordInOperand( + message_.id_use_descriptor().in_operand_index() + 1), + SpvOpLoad); + } + + // The only operand that we could've replaced in the OpBranchConditional is + // the condition id. But that operand has a boolean type and uniform variables + // can't store booleans (see the spec on OpTypeBool). Thus, |result| can't be + // an OpBranchConditional. + assert(result->opcode() != SpvOpBranchConditional && + "OpBranchConditional has no operands to replace"); + + assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, result) && + "We should be able to insert OpLoad and OpAccessChain at this point"); + return result; +} + bool TransformationReplaceConstantWithUniform::IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const { @@ -123,7 +157,7 @@ bool TransformationReplaceConstantWithUniform::IsApplicable( // by the uniform buffer element descriptor will hold a scalar value. auto constant_id_associated_with_uniform = transformation_context.GetFactManager()->GetConstantFromUniformDescriptor( - ir_context, message_.uniform_descriptor()); + message_.uniform_descriptor()); if (!constant_id_associated_with_uniform) { return false; } @@ -188,6 +222,12 @@ bool TransformationReplaceConstantWithUniform::IsApplicable( } } + // Once all checks are completed, we should be able to safely insert + // OpAccessChain and OpLoad into the module. + assert(GetInsertBeforeInstruction(ir_context) && + "There must exist an instruction that we can use to insert " + "OpAccessChain and OpLoad above"); + return true; } @@ -195,7 +235,7 @@ void TransformationReplaceConstantWithUniform::Apply( spvtools::opt::IRContext* ir_context, TransformationContext* /*unused*/) const { // Get the instruction that contains the id use we wish to replace. - auto instruction_containing_constant_use = + auto* instruction_containing_constant_use = FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); assert(instruction_containing_constant_use && "Precondition requires that the id use can be found."); @@ -210,12 +250,17 @@ void TransformationReplaceConstantWithUniform::Apply( ->GetDef(message_.id_use_descriptor().id_of_interest()) ->type_id(); + // Get an instruction that will be used to insert OpAccessChain and OpLoad. + auto* insert_before_inst = GetInsertBeforeInstruction(ir_context); + assert(insert_before_inst && + "There must exist an insertion point for OpAccessChain and OpLoad"); + // Add an access chain instruction to target the uniform element. - instruction_containing_constant_use->InsertBefore( + insert_before_inst->InsertBefore( MakeAccessChainInstruction(ir_context, constant_type_id)); // Add a load from this access chain. - instruction_containing_constant_use->InsertBefore( + insert_before_inst->InsertBefore( MakeLoadInstruction(ir_context, constant_type_id)); // Adjust the instruction containing the usage of the constant so that this diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h index b72407c8..b27fb694 100644 --- a/source/fuzz/transformation_replace_constant_with_uniform.h +++ b/source/fuzz/transformation_replace_constant_with_uniform.h @@ -17,7 +17,6 @@ #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_ #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_ -#include "source/fuzz/fact_manager.h" #include "source/fuzz/id_use_descriptor.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" @@ -84,6 +83,11 @@ class TransformationReplaceConstantWithUniform : public Transformation { std::unique_ptr<opt::Instruction> MakeLoadInstruction( spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const; + // OpAccessChain and OpLoad will be inserted above the instruction returned + // by this function. Returns nullptr if no such instruction is present. + opt::Instruction* GetInsertBeforeInstruction( + opt::IRContext* ir_context) const; + protobufs::TransformationReplaceConstantWithUniform message_; }; diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp new file mode 100644 index 00000000..9a6e429b --- /dev/null +++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp @@ -0,0 +1,127 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationReplaceCopyMemoryWithLoadStore:: + TransformationReplaceCopyMemoryWithLoadStore( + const spvtools::fuzz::protobufs:: + TransformationReplaceCopyMemoryWithLoadStore& message) + : message_(message) {} + +TransformationReplaceCopyMemoryWithLoadStore:: + TransformationReplaceCopyMemoryWithLoadStore( + uint32_t fresh_id, const protobufs::InstructionDescriptor& + copy_memory_instruction_descriptor) { + message_.set_fresh_id(fresh_id); + *message_.mutable_copy_memory_instruction_descriptor() = + copy_memory_instruction_descriptor; +} + +bool TransformationReplaceCopyMemoryWithLoadStore::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.fresh_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) { + return false; + } + // The instruction to be replaced must be defined and have opcode + // OpCopyMemory. + auto copy_memory_instruction = FindInstruction( + message_.copy_memory_instruction_descriptor(), ir_context); + if (!copy_memory_instruction || + copy_memory_instruction->opcode() != SpvOpCopyMemory) { + return false; + } + return true; +} + +void TransformationReplaceCopyMemoryWithLoadStore::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + auto copy_memory_instruction = FindInstruction( + message_.copy_memory_instruction_descriptor(), ir_context); + // |copy_memory_instruction| must be defined. + assert(copy_memory_instruction && + copy_memory_instruction->opcode() == SpvOpCopyMemory && + "The required OpCopyMemory instruction must be defined."); + + // Integrity check: Both operands must be pointers. + + // Get types of ids used as a source and target of |copy_memory_instruction|. + auto target = ir_context->get_def_use_mgr()->GetDef( + copy_memory_instruction->GetSingleWordInOperand(0)); + auto source = ir_context->get_def_use_mgr()->GetDef( + copy_memory_instruction->GetSingleWordInOperand(1)); + auto target_type_opcode = + ir_context->get_def_use_mgr()->GetDef(target->type_id())->opcode(); + auto source_type_opcode = + ir_context->get_def_use_mgr()->GetDef(source->type_id())->opcode(); + + // Keep release-mode compilers happy. (No unused variables.) + (void)target; + (void)source; + (void)target_type_opcode; + (void)source_type_opcode; + + assert(target_type_opcode == SpvOpTypePointer && + source_type_opcode == SpvOpTypePointer && + "Operands must be of type OpTypePointer"); + + // Integrity check: |source| and |target| must point to the same type. + uint32_t target_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType( + ir_context, target->type_id()); + uint32_t source_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType( + ir_context, source->type_id()); + + // Keep release-mode compilers happy. (No unused variables.) + (void)target_pointee_type; + (void)source_pointee_type; + + assert(target_pointee_type == source_pointee_type && + "Operands must have the same type to which they point to."); + + // First, insert the OpStore instruction before the OpCopyMemory instruction + // and then insert the OpLoad instruction before the OpStore instruction. + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + FindInstruction(message_.copy_memory_instruction_descriptor(), ir_context) + ->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpStore, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {target->result_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}}))) + ->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpLoad, target_pointee_type, message_.fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {source->result_id()}}}))); + + // Remove the OpCopyMemory instruction. + ir_context->KillInst(copy_memory_instruction); + + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation +TransformationReplaceCopyMemoryWithLoadStore::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_copy_memory_with_load_store() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.h b/source/fuzz/transformation_replace_copy_memory_with_load_store.h new file mode 100644 index 00000000..00eeeadb --- /dev/null +++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.h @@ -0,0 +1,57 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceCopyMemoryWithLoadStore : public Transformation { + public: + explicit TransformationReplaceCopyMemoryWithLoadStore( + const protobufs::TransformationReplaceCopyMemoryWithLoadStore& message); + + TransformationReplaceCopyMemoryWithLoadStore( + uint32_t fresh_id, const protobufs::InstructionDescriptor& + copy_memory_instruction_descriptor); + + // - |message_.fresh_id| must be fresh. + // - |message_.copy_memory_instruction_descriptor| must refer to an + // OpCopyMemory instruction. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces instruction OpCopyMemory with loading the source variable to an + // intermediate value and storing this value into the target variable of the + // original OpCopyMemory instruction. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceCopyMemoryWithLoadStore message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_ diff --git a/source/fuzz/transformation_replace_copy_object_with_store_load.cpp b/source/fuzz/transformation_replace_copy_object_with_store_load.cpp new file mode 100644 index 00000000..0d21613c --- /dev/null +++ b/source/fuzz/transformation_replace_copy_object_with_store_load.cpp @@ -0,0 +1,147 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_copy_object_with_store_load.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationReplaceCopyObjectWithStoreLoad:: + TransformationReplaceCopyObjectWithStoreLoad( + const spvtools::fuzz::protobufs:: + TransformationReplaceCopyObjectWithStoreLoad& message) + : message_(message) {} + +TransformationReplaceCopyObjectWithStoreLoad:: + TransformationReplaceCopyObjectWithStoreLoad( + uint32_t copy_object_result_id, uint32_t fresh_variable_id, + uint32_t variable_storage_class, uint32_t variable_initializer_id) { + message_.set_copy_object_result_id(copy_object_result_id); + message_.set_fresh_variable_id(fresh_variable_id); + message_.set_variable_storage_class(variable_storage_class); + message_.set_variable_initializer_id(variable_initializer_id); +} + +bool TransformationReplaceCopyObjectWithStoreLoad::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // |message_.fresh_variable_id| must be fresh. + if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_variable_id())) { + return false; + } + auto copy_object_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.copy_object_result_id()); + + // This must be a defined OpCopyObject instruction. + if (!copy_object_instruction || + copy_object_instruction->opcode() != SpvOpCopyObject) { + return false; + } + + // The opcode of the type_id instruction cannot be a OpTypePointer, + // because we cannot define a pointer to pointer. + if (ir_context->get_def_use_mgr() + ->GetDef(copy_object_instruction->type_id()) + ->opcode() == SpvOpTypePointer) { + return false; + } + + // A pointer type instruction pointing to the value type must be defined. + auto pointer_type_id = fuzzerutil::MaybeGetPointerType( + ir_context, copy_object_instruction->type_id(), + static_cast<SpvStorageClass>(message_.variable_storage_class())); + if (!pointer_type_id) { + return false; + } + + // Check that initializer is valid. + const auto* constant_inst = + ir_context->get_def_use_mgr()->GetDef(message_.variable_initializer_id()); + if (!constant_inst || !spvOpcodeIsConstant(constant_inst->opcode()) || + copy_object_instruction->type_id() != constant_inst->type_id()) { + return false; + } + // |message_.variable_storage_class| must be Private or Function. + return message_.variable_storage_class() == SpvStorageClassPrivate || + message_.variable_storage_class() == SpvStorageClassFunction; +} + +void TransformationReplaceCopyObjectWithStoreLoad::Apply( + opt::IRContext* ir_context, + TransformationContext* transformation_context) const { + auto copy_object_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.copy_object_result_id()); + // |copy_object_instruction| must be defined. + assert(copy_object_instruction && + copy_object_instruction->opcode() == SpvOpCopyObject && + "The required OpCopyObject instruction must be defined."); + // Get id used as a source by the OpCopyObject instruction. + uint32_t src_operand = copy_object_instruction->GetSingleWordOperand(2); + // A pointer type instruction pointing to the value type must be defined. + auto pointer_type_id = fuzzerutil::MaybeGetPointerType( + ir_context, copy_object_instruction->type_id(), + static_cast<SpvStorageClass>(message_.variable_storage_class())); + assert(pointer_type_id && "The required pointer type must be available."); + + // Adds a global or local variable (according to the storage class). + if (message_.variable_storage_class() == SpvStorageClassPrivate) { + fuzzerutil::AddGlobalVariable(ir_context, message_.fresh_variable_id(), + pointer_type_id, SpvStorageClassPrivate, + message_.variable_initializer_id()); + } else { + auto function_id = ir_context->get_instr_block(copy_object_instruction) + ->GetParent() + ->result_id(); + fuzzerutil::AddLocalVariable(ir_context, message_.fresh_variable_id(), + pointer_type_id, function_id, + message_.variable_initializer_id()); + } + + // First, insert the OpLoad instruction before the OpCopyObject instruction + // and then insert the OpStore instruction before the OpLoad instruction. + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_variable_id()); + copy_object_instruction + ->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpLoad, copy_object_instruction->type_id(), + message_.copy_object_result_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.fresh_variable_id()}}}))) + ->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpStore, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.fresh_variable_id()}}, + {SPV_OPERAND_TYPE_ID, {src_operand}}}))); + // Remove the CopyObject instruction. + ir_context->KillInst(copy_object_instruction); + + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + // Adds the fact that |message_.copy_object_result_id| + // and src_operand (id used by OpCopyObject) are synonymous. + transformation_context->GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(message_.copy_object_result_id(), {}), + MakeDataDescriptor(src_operand, {})); +} + +protobufs::Transformation +TransformationReplaceCopyObjectWithStoreLoad::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_copy_object_with_store_load() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_copy_object_with_store_load.h b/source/fuzz/transformation_replace_copy_object_with_store_load.h new file mode 100644 index 00000000..b9bffd40 --- /dev/null +++ b/source/fuzz/transformation_replace_copy_object_with_store_load.h @@ -0,0 +1,63 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceCopyObjectWithStoreLoad : public Transformation { + public: + explicit TransformationReplaceCopyObjectWithStoreLoad( + const protobufs::TransformationReplaceCopyObjectWithStoreLoad& message); + + TransformationReplaceCopyObjectWithStoreLoad( + uint32_t copy_object_result_id, uint32_t fresh_variable_id, + uint32_t variable_storage_class, uint32_t variable_initializer_id); + + // - |message_.copy_object_result_id| must be a result id of an OpCopyObject + // instruction. + // - |message_.fresh_variable_id| must be a fresh id given to variable used by + // OpStore. + // - |message_.variable_storage_class| must be either StorageClassPrivate or + // StorageClassFunction. + // - |message_.initializer_id| must be a result id of some constant in the + // module. Its type must be equal to the pointee type of the variable that + // will be created. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces instruction OpCopyObject with storing into a new variable and + // immediately loading from this variable to |result_id| of the original + // OpCopyObject instruction. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceCopyObjectWithStoreLoad message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_ diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp index e427f3c3..ec044009 100644 --- a/source/fuzz/transformation_replace_id_with_synonym.cpp +++ b/source/fuzz/transformation_replace_id_with_synonym.cpp @@ -57,8 +57,23 @@ bool TransformationReplaceIdWithSynonym::IsApplicable( return false; } + uint32_t type_id_of_interest = + ir_context->get_def_use_mgr()->GetDef(id_of_interest)->type_id(); + uint32_t type_id_synonym = ir_context->get_def_use_mgr() + ->GetDef(message_.synonymous_id()) + ->type_id(); + + // If the id of interest and the synonym are scalar or vector integer + // constants with different signedness, their use can only be swapped if the + // instruction is agnostic to the signedness of the operand. + if (!TypesAreCompatible(ir_context, use_instruction->opcode(), + message_.id_use_descriptor().in_operand_index(), + type_id_of_interest, type_id_synonym)) { + return false; + } + // Is the use suitable for being replaced in principle? - if (!UseCanBeReplacedWithSynonym( + if (!fuzzerutil::IdUseCanBeReplaced( ir_context, use_instruction, message_.id_use_descriptor().in_operand_index())) { return false; @@ -91,95 +106,57 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage() return result; } -bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym( - opt::IRContext* ir_context, opt::Instruction* use_instruction, - uint32_t use_in_operand_index) { - if (use_instruction->opcode() == SpvOpAccessChain && - use_in_operand_index > 0) { - // This is an access chain index. If the (sub-)object being accessed by the - // given index has struct type then we cannot replace the use with a - // synonym, as the use needs to be an OpConstant. - - // Get the top-level composite type that is being accessed. - auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef( - use_instruction->GetSingleWordInOperand(0)); - auto pointer_type = - ir_context->get_type_mgr()->GetType(object_being_accessed->type_id()); - assert(pointer_type->AsPointer()); - auto composite_type_being_accessed = - pointer_type->AsPointer()->pointee_type(); - - // Now walk the access chain, tracking the type of each sub-object of the - // composite that is traversed, until the index of interest is reached. - for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index; - index_in_operand++) { - // For vectors, matrices and arrays, getting the type of the sub-object is - // trivial. For the struct case, the sub-object type is field-sensitive, - // and depends on the constant index that is used. - if (composite_type_being_accessed->AsVector()) { - composite_type_being_accessed = - composite_type_being_accessed->AsVector()->element_type(); - } else if (composite_type_being_accessed->AsMatrix()) { - composite_type_being_accessed = - composite_type_being_accessed->AsMatrix()->element_type(); - } else if (composite_type_being_accessed->AsArray()) { - composite_type_being_accessed = - composite_type_being_accessed->AsArray()->element_type(); - } else if (composite_type_being_accessed->AsRuntimeArray()) { - composite_type_being_accessed = - composite_type_being_accessed->AsRuntimeArray()->element_type(); - } else { - assert(composite_type_being_accessed->AsStruct()); - auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef( - use_instruction->GetSingleWordInOperand(index_in_operand)); - assert(constant_index_instruction->opcode() == SpvOpConstant); - uint32_t member_index = - constant_index_instruction->GetSingleWordInOperand(0); - composite_type_being_accessed = - composite_type_being_accessed->AsStruct() - ->element_types()[member_index]; - } - } - - // We have found the composite type being accessed by the index we are - // considering replacing. If it is a struct, then we cannot do the - // replacement as struct indices must be constants. - if (composite_type_being_accessed->AsStruct()) { +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all +// opcodes that are agnostic to signedness of operands to function. +// This is not exhaustive yet. +bool TransformationReplaceIdWithSynonym::IsAgnosticToSignednessOfOperand( + SpvOp opcode, uint32_t use_in_operand_index) { + switch (opcode) { + case SpvOpSNegate: + case SpvOpNot: + case SpvOpIAdd: + case SpvOpISub: + case SpvOpIMul: + case SpvOpSDiv: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + return true; + case SpvOpAccessChain: + // The signedness of indices does not matter. + return use_in_operand_index > 0; + default: + // Conservatively assume that the id cannot be swapped in other + // instructions. return false; - } - } - - if (use_instruction->opcode() == SpvOpFunctionCall && - use_in_operand_index > 0) { - // This is a function call argument. It is not allowed to have pointer - // type. - - // Get the definition of the function being called. - auto function = ir_context->get_def_use_mgr()->GetDef( - use_instruction->GetSingleWordInOperand(0)); - // From the function definition, get the function type. - auto function_type = ir_context->get_def_use_mgr()->GetDef( - function->GetSingleWordInOperand(1)); - // OpTypeFunction's 0-th input operand is the function return type, and the - // function argument types follow. Because the arguments to OpFunctionCall - // start from input operand 1, we can use |use_in_operand_index| to get the - // type associated with this function argument. - auto parameter_type = ir_context->get_type_mgr()->GetType( - function_type->GetSingleWordInOperand(use_in_operand_index)); - if (parameter_type->AsPointer()) { - return false; - } } +} - if (use_instruction->opcode() == SpvOpImageTexelPointer && - use_in_operand_index == 2) { - // The OpImageTexelPointer instruction has a Sample parameter that in some - // situations must be an id for the value 0. To guard against disrupting - // that requirement, we do not replace this argument to that instruction. - return false; - } +bool TransformationReplaceIdWithSynonym::TypesAreCompatible( + opt::IRContext* ir_context, SpvOp opcode, uint32_t use_in_operand_index, + uint32_t type_id_1, uint32_t type_id_2) { + assert(ir_context->get_type_mgr()->GetType(type_id_1) && + ir_context->get_type_mgr()->GetType(type_id_2) && + "Type ids are invalid"); - return true; + return type_id_1 == type_id_2 || + (IsAgnosticToSignednessOfOperand(opcode, use_in_operand_index) && + fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_1, type_id_2)); } } // namespace fuzz diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h index a5a9dfda..e248d1c9 100644 --- a/source/fuzz/transformation_replace_id_with_synonym.h +++ b/source/fuzz/transformation_replace_id_with_synonym.h @@ -32,15 +32,12 @@ class TransformationReplaceIdWithSynonym : public Transformation { protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id); // - The fact manager must know that the id identified by - // |message_.id_use_descriptor| is synonomous with - // |message_.synonymous_id|. + // |message_.id_use_descriptor| is synonomous with |message_.synonymous_id|. // - Replacing the id in |message_.id_use_descriptor| by // |message_.synonymous_id| must respect SPIR-V's rules about uses being // dominated by their definitions. - // - The id must not be an index into an access chain whose base object has - // struct type, as such indices must be constants. - // - The id must not be a pointer argument to a function call (because the - // synonym might not be a memory object declaration). + // - The id use must be replaceable in principle. See + // fuzzerutil::IdUseCanBeReplaced for details. // - |fresh_id_for_temporary| must be 0. bool IsApplicable( opt::IRContext* ir_context, @@ -53,18 +50,22 @@ class TransformationReplaceIdWithSynonym : public Transformation { protobufs::Transformation ToMessage() const override; - // Checks whether various conditions hold related to the acceptability of - // replacing the id use at |use_in_operand_index| of |use_instruction| with - // a synonym. In particular, this checks that: - // - the id use is not an index into a struct field in an OpAccessChain - such - // indices must be constants, so it is dangerous to replace them. - // - the id use is not a pointer function call argument, on which there are - // restrictions that make replacement problematic. - static bool UseCanBeReplacedWithSynonym(opt::IRContext* ir_context, - opt::Instruction* use_instruction, - uint32_t use_in_operand_index); + // Returns true if |type_id_1| and |type_id_2| represent compatible types + // given the context of the instruction with |opcode| (i.e. we can replace + // an operand of |opcode| of the first type with an id of the second type + // and vice-versa). + static bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode, + uint32_t use_in_operand_index, + uint32_t type_id_1, uint32_t type_id_2); private: + // Returns true if the instruction with opcode |opcode| does not change its + // behaviour depending on the signedness of the operand at + // |use_in_operand_index|. + // Assumes that the operand must be the id of an integer scalar or vector. + static bool IsAgnosticToSignednessOfOperand(SpvOp opcode, + uint32_t use_in_operand_index); + protobufs::TransformationReplaceIdWithSynonym message_; }; diff --git a/source/fuzz/transformation_replace_irrelevant_id.cpp b/source/fuzz/transformation_replace_irrelevant_id.cpp new file mode 100644 index 00000000..5ac182a7 --- /dev/null +++ b/source/fuzz/transformation_replace_irrelevant_id.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_irrelevant_id.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/id_use_descriptor.h" + +namespace spvtools { +namespace fuzz { + +TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId( + const protobufs::TransformationReplaceIrrelevantId& message) + : message_(message) {} + +TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId( + const protobufs::IdUseDescriptor& id_use_descriptor, + uint32_t replacement_id) { + *message_.mutable_id_use_descriptor() = id_use_descriptor; + message_.set_replacement_id(replacement_id); +} + +bool TransformationReplaceIrrelevantId::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + auto id_of_interest = message_.id_use_descriptor().id_of_interest(); + + // The id must be irrelevant. + if (!transformation_context.GetFactManager()->IdIsIrrelevant( + id_of_interest)) { + return false; + } + + // Find the instruction containing the id use, which must exist. + auto use_instruction = + FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); + if (!use_instruction) { + return false; + } + + // Check that the replacement id exists and retrieve its definition. + auto replacement_id_def = + ir_context->get_def_use_mgr()->GetDef(message_.replacement_id()); + if (!replacement_id_def) { + return false; + } + + // The type of the id of interest and of the replacement id must be the same. + uint32_t type_id_of_interest = + ir_context->get_def_use_mgr()->GetDef(id_of_interest)->type_id(); + uint32_t type_replacement_id = replacement_id_def->type_id(); + if (type_id_of_interest != type_replacement_id) { + return false; + } + + // Consistency check: an irrelevant id cannot be a pointer. + assert( + !ir_context->get_type_mgr()->GetType(type_id_of_interest)->AsPointer() && + "An irrelevant id cannot be a pointer"); + + // The id use must be replaceable with any other id of the same type. + if (!fuzzerutil::IdUseCanBeReplaced( + ir_context, use_instruction, + message_.id_use_descriptor().in_operand_index())) { + return false; + } + + // The id must be available to use at the use point. + return fuzzerutil::IdIsAvailableAtUse( + ir_context, use_instruction, + message_.id_use_descriptor().in_operand_index(), + message_.replacement_id()); +} + +void TransformationReplaceIrrelevantId::Apply( + opt::IRContext* ir_context, + TransformationContext* /* transformation_context */) const { + // Find the instruction. + auto instruction_to_change = + FindInstructionContainingUse(message_.id_use_descriptor(), ir_context); + + // Replace the instruction. + instruction_to_change->SetInOperand( + message_.id_use_descriptor().in_operand_index(), + {message_.replacement_id()}); + + // Invalidate the analyses, since the usage of ids has been changed. + ir_context->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationReplaceIrrelevantId::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_irrelevant_id() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_irrelevant_id.h b/source/fuzz/transformation_replace_irrelevant_id.h new file mode 100644 index 00000000..c623b961 --- /dev/null +++ b/source/fuzz/transformation_replace_irrelevant_id.h @@ -0,0 +1,58 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceIrrelevantId : public Transformation { + public: + explicit TransformationReplaceIrrelevantId( + const protobufs::TransformationReplaceIrrelevantId& message); + + TransformationReplaceIrrelevantId( + const protobufs::IdUseDescriptor& id_use_descriptor, + uint32_t replacement_id); + + // - The id of interest in |message_.id_use_descriptor| is irrelevant + // according to the fact manager. + // - The types of the original id and of the replacement ids are the same. + // - |message_.replacement_id| is available to use at the enclosing + // instruction of |message_.id_use_descriptor|. + // - The original id is in principle replaceable with any other id of the same + // type. See fuzzerutil::IdUseCanBeReplaced for details. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the use of an irrelevant id identified by + // |message_.id_use_descriptor| with the id |message_.replacement_id|, which + // has the same type as the id of interest. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceIrrelevantId message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_ diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp index 76f083be..e78573c7 100644 --- a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp +++ b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp @@ -41,16 +41,8 @@ bool TransformationReplaceLinearAlgebraInstruction::IsApplicable( auto instruction = FindInstruction(message_.instruction_descriptor(), ir_context); - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354): - // Right now we only support certain operations. When this issue is addressed - // the following conditional can use the function |spvOpcodeIsLinearAlgebra|. - // It must be a supported linear algebra instruction. - if (instruction->opcode() != SpvOpVectorTimesScalar && - instruction->opcode() != SpvOpMatrixTimesScalar && - instruction->opcode() != SpvOpVectorTimesMatrix && - instruction->opcode() != SpvOpMatrixTimesVector && - instruction->opcode() != SpvOpMatrixTimesMatrix && - instruction->opcode() != SpvOpDot) { + // It must be a linear algebra instruction. + if (!spvOpcodeIsLinearAlgebra(instruction->opcode())) { return false; } @@ -77,6 +69,9 @@ void TransformationReplaceLinearAlgebraInstruction::Apply( FindInstruction(message_.instruction_descriptor(), ir_context); switch (linear_algebra_instruction->opcode()) { + case SpvOpTranspose: + ReplaceOpTranspose(ir_context, linear_algebra_instruction); + break; case SpvOpVectorTimesScalar: ReplaceOpVectorTimesScalar(ir_context, linear_algebra_instruction); break; @@ -92,6 +87,9 @@ void TransformationReplaceLinearAlgebraInstruction::Apply( case SpvOpMatrixTimesMatrix: ReplaceOpMatrixTimesMatrix(ir_context, linear_algebra_instruction); break; + case SpvOpOuterProduct: + ReplaceOpOuterProduct(ir_context, linear_algebra_instruction); + break; case SpvOpDot: ReplaceOpDot(ir_context, linear_algebra_instruction); break; @@ -115,6 +113,24 @@ uint32_t TransformationReplaceLinearAlgebraInstruction::GetRequiredFreshIdCount( // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354): // Right now we only support certain operations. switch (instruction->opcode()) { + case SpvOpTranspose: { + // For each matrix row, |2 * matrix_column_count| OpCompositeExtract and 1 + // OpCompositeConstruct will be inserted. + auto matrix_instruction = ir_context->get_def_use_mgr()->GetDef( + instruction->GetSingleWordInOperand(0)); + uint32_t matrix_column_count = + ir_context->get_type_mgr() + ->GetType(matrix_instruction->type_id()) + ->AsMatrix() + ->element_count(); + uint32_t matrix_row_count = ir_context->get_type_mgr() + ->GetType(matrix_instruction->type_id()) + ->AsMatrix() + ->element_type() + ->AsVector() + ->element_count(); + return matrix_row_count * (2 * matrix_column_count + 1); + } case SpvOpVectorTimesScalar: // For each vector component, 1 OpCompositeExtract and 1 OpFMul will be // inserted. @@ -213,6 +229,26 @@ uint32_t TransformationReplaceLinearAlgebraInstruction::GetRequiredFreshIdCount( return matrix_2_column_count * (2 + matrix_1_row_count * (5 * matrix_1_column_count - 1)); } + case SpvOpOuterProduct: { + // For each |vector_2| component, |vector_1_component_count + 1| + // OpCompositeExtract, |vector_1_component_count| OpFMul and 1 + // OpCompositeConstruct instructions will be inserted. + auto vector_1_instruction = ir_context->get_def_use_mgr()->GetDef( + instruction->GetSingleWordInOperand(0)); + auto vector_2_instruction = ir_context->get_def_use_mgr()->GetDef( + instruction->GetSingleWordInOperand(1)); + uint32_t vector_1_component_count = + ir_context->get_type_mgr() + ->GetType(vector_1_instruction->type_id()) + ->AsVector() + ->element_count(); + uint32_t vector_2_component_count = + ir_context->get_type_mgr() + ->GetType(vector_2_instruction->type_id()) + ->AsVector() + ->element_count(); + return 2 * vector_2_component_count * (vector_1_component_count + 1); + } case SpvOpDot: // For each pair of vector components, 2 OpCompositeExtract and 1 OpFMul // will be inserted. The first two OpFMul instructions will result the @@ -233,6 +269,80 @@ uint32_t TransformationReplaceLinearAlgebraInstruction::GetRequiredFreshIdCount( } } +void TransformationReplaceLinearAlgebraInstruction::ReplaceOpTranspose( + opt::IRContext* ir_context, + opt::Instruction* linear_algebra_instruction) const { + // Gets OpTranspose instruction information. + auto matrix_instruction = ir_context->get_def_use_mgr()->GetDef( + linear_algebra_instruction->GetSingleWordInOperand(0)); + uint32_t matrix_column_count = ir_context->get_type_mgr() + ->GetType(matrix_instruction->type_id()) + ->AsMatrix() + ->element_count(); + auto matrix_column_type = ir_context->get_type_mgr() + ->GetType(matrix_instruction->type_id()) + ->AsMatrix() + ->element_type(); + auto matrix_column_component_type = + matrix_column_type->AsVector()->element_type(); + uint32_t matrix_row_count = matrix_column_type->AsVector()->element_count(); + auto resulting_matrix_column_type = + ir_context->get_type_mgr() + ->GetType(linear_algebra_instruction->type_id()) + ->AsMatrix() + ->element_type(); + + uint32_t fresh_id_index = 0; + std::vector<uint32_t> result_column_ids(matrix_row_count); + for (uint32_t i = 0; i < matrix_row_count; i++) { + std::vector<uint32_t> column_component_ids(matrix_column_count); + for (uint32_t j = 0; j < matrix_column_count; j++) { + // Extracts the matrix column. + uint32_t matrix_column_id = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeExtract, + ir_context->get_type_mgr()->GetId(matrix_column_type), + matrix_column_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {matrix_instruction->result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {j}}}))); + + // Extracts the matrix column component. + column_component_ids[j] = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeExtract, + ir_context->get_type_mgr()->GetId(matrix_column_component_type), + column_component_ids[j], + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {matrix_column_id}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}}))); + } + + // Inserts the resulting matrix column. + opt::Instruction::OperandList in_operands; + for (auto& column_component_id : column_component_ids) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {column_component_id}}); + } + result_column_ids[i] = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeConstruct, + ir_context->get_type_mgr()->GetId(resulting_matrix_column_type), + result_column_ids[i], opt::Instruction::OperandList(in_operands))); + } + + // The OpTranspose instruction is changed to an OpCompositeConstruct + // instruction. + linear_algebra_instruction->SetOpcode(SpvOpCompositeConstruct); + linear_algebra_instruction->SetInOperand(0, {result_column_ids[0]}); + for (uint32_t i = 1; i < result_column_ids.size(); i++) { + linear_algebra_instruction->AddOperand( + {SPV_OPERAND_TYPE_ID, {result_column_ids[i]}}); + } + + fuzzerutil::UpdateModuleIdBound( + ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1)); +} + void TransformationReplaceLinearAlgebraInstruction::ReplaceOpVectorTimesScalar( opt::IRContext* ir_context, opt::Instruction* linear_algebra_instruction) const { @@ -740,6 +850,92 @@ void TransformationReplaceLinearAlgebraInstruction::ReplaceOpMatrixTimesMatrix( ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1)); } +void TransformationReplaceLinearAlgebraInstruction::ReplaceOpOuterProduct( + opt::IRContext* ir_context, + opt::Instruction* linear_algebra_instruction) const { + // Gets vector 1 information. + auto vector_1_instruction = ir_context->get_def_use_mgr()->GetDef( + linear_algebra_instruction->GetSingleWordInOperand(0)); + uint32_t vector_1_component_count = + ir_context->get_type_mgr() + ->GetType(vector_1_instruction->type_id()) + ->AsVector() + ->element_count(); + auto vector_1_component_type = ir_context->get_type_mgr() + ->GetType(vector_1_instruction->type_id()) + ->AsVector() + ->element_type(); + + // Gets vector 2 information. + auto vector_2_instruction = ir_context->get_def_use_mgr()->GetDef( + linear_algebra_instruction->GetSingleWordInOperand(1)); + uint32_t vector_2_component_count = + ir_context->get_type_mgr() + ->GetType(vector_2_instruction->type_id()) + ->AsVector() + ->element_count(); + + uint32_t fresh_id_index = 0; + std::vector<uint32_t> result_column_ids(vector_2_component_count); + for (uint32_t i = 0; i < vector_2_component_count; i++) { + // Extracts |vector_2| component. + uint32_t vector_2_component_id = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeExtract, + ir_context->get_type_mgr()->GetId(vector_1_component_type), + vector_2_component_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {vector_2_instruction->result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}}))); + + std::vector<uint32_t> column_component_ids(vector_1_component_count); + for (uint32_t j = 0; j < vector_1_component_count; j++) { + // Extracts |vector_1| component. + uint32_t vector_1_component_id = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeExtract, + ir_context->get_type_mgr()->GetId(vector_1_component_type), + vector_1_component_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {vector_1_instruction->result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {j}}}))); + + // Multiplies |vector_1| and |vector_2| components. + column_component_ids[j] = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpFMul, + ir_context->get_type_mgr()->GetId(vector_1_component_type), + column_component_ids[j], + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {vector_2_component_id}}, + {SPV_OPERAND_TYPE_ID, {vector_1_component_id}}}))); + } + + // Inserts the resulting matrix column. + opt::Instruction::OperandList in_operands; + for (auto& column_component_id : column_component_ids) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {column_component_id}}); + } + result_column_ids[i] = message_.fresh_ids(fresh_id_index++); + linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCompositeConstruct, vector_1_instruction->type_id(), + result_column_ids[i], in_operands)); + } + + // The OpOuterProduct instruction is changed to an OpCompositeConstruct + // instruction. + linear_algebra_instruction->SetOpcode(SpvOpCompositeConstruct); + linear_algebra_instruction->SetInOperand(0, {result_column_ids[0]}); + linear_algebra_instruction->SetInOperand(1, {result_column_ids[1]}); + for (uint32_t i = 2; i < result_column_ids.size(); i++) { + linear_algebra_instruction->AddOperand( + {SPV_OPERAND_TYPE_ID, {result_column_ids[i]}}); + } + + fuzzerutil::UpdateModuleIdBound( + ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1)); +} + void TransformationReplaceLinearAlgebraInstruction::ReplaceOpDot( opt::IRContext* ir_context, opt::Instruction* linear_algebra_instruction) const { diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.h b/source/fuzz/transformation_replace_linear_algebra_instruction.h index 530c1f20..05ebdd7f 100644 --- a/source/fuzz/transformation_replace_linear_algebra_instruction.h +++ b/source/fuzz/transformation_replace_linear_algebra_instruction.h @@ -52,6 +52,10 @@ class TransformationReplaceLinearAlgebraInstruction : public Transformation { private: protobufs::TransformationReplaceLinearAlgebraInstruction message_; + // Replaces an OpTranspose instruction. + void ReplaceOpTranspose(opt::IRContext* ir_context, + opt::Instruction* instruction) const; + // Replaces an OpVectorTimesScalar instruction. void ReplaceOpVectorTimesScalar(opt::IRContext* ir_context, opt::Instruction* instruction) const; @@ -72,6 +76,10 @@ class TransformationReplaceLinearAlgebraInstruction : public Transformation { void ReplaceOpMatrixTimesMatrix(opt::IRContext* ir_context, opt::Instruction* instruction) const; + // Replaces an OpOuterProduct instruction. + void ReplaceOpOuterProduct(opt::IRContext* ir_context, + opt::Instruction* instruction) const; + // Replaces an OpDot instruction. void ReplaceOpDot(opt::IRContext* ir_context, opt::Instruction* instruction) const; diff --git a/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp b/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp new file mode 100644 index 00000000..a42ffb11 --- /dev/null +++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2020 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 "transformation_replace_load_store_with_copy_memory.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "source/opcode.h" + +namespace spvtools { +namespace fuzz { + +namespace { +const uint32_t kOpStoreOperandIndexTargetVariable = 0; +const uint32_t kOpStoreOperandIndexIntermediateIdToWrite = 1; +const uint32_t kOpLoadOperandIndexSourceVariable = 2; +} // namespace + +TransformationReplaceLoadStoreWithCopyMemory:: + TransformationReplaceLoadStoreWithCopyMemory( + const spvtools::fuzz::protobufs:: + TransformationReplaceLoadStoreWithCopyMemory& message) + : message_(message) {} + +TransformationReplaceLoadStoreWithCopyMemory:: + TransformationReplaceLoadStoreWithCopyMemory( + const protobufs::InstructionDescriptor& load_instruction_descriptor, + const protobufs::InstructionDescriptor& store_instruction_descriptor) { + *message_.mutable_load_instruction_descriptor() = load_instruction_descriptor; + *message_.mutable_store_instruction_descriptor() = + store_instruction_descriptor; +} +bool TransformationReplaceLoadStoreWithCopyMemory::IsApplicable( + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { + // This transformation is only applicable to the pair of OpLoad and OpStore + // instructions. + + // The OpLoad instruction must be defined. + auto load_instruction = + FindInstruction(message_.load_instruction_descriptor(), ir_context); + if (!load_instruction || load_instruction->opcode() != SpvOpLoad) { + return false; + } + + // The OpStore instruction must be defined. + auto store_instruction = + FindInstruction(message_.store_instruction_descriptor(), ir_context); + if (!store_instruction || store_instruction->opcode() != SpvOpStore) { + return false; + } + + // Intermediate values of the OpLoad and the OpStore must match. + if (load_instruction->result_id() != + store_instruction->GetSingleWordOperand( + kOpStoreOperandIndexIntermediateIdToWrite)) { + return false; + } + + // Get storage class of the variable pointed by the source operand in OpLoad. + opt::Instruction* source_id = ir_context->get_def_use_mgr()->GetDef( + load_instruction->GetSingleWordOperand(2)); + SpvStorageClass storage_class = fuzzerutil::GetStorageClassFromPointerType( + ir_context, source_id->type_id()); + + // Iterate over all instructions between |load_instruction| and + // |store_instruction|. + for (auto it = load_instruction; it != store_instruction; + it = it->NextNode()) { + //|load_instruction| and |store_instruction| are not in the same block. + if (it == nullptr) { + return false; + } + + // We need to make sure that the value pointed to by the source of the + // OpLoad hasn't changed by the time we see the matching OpStore + // instruction. + if (IsMemoryWritingOpCode(it->opcode())) { + return false; + } else if (IsMemoryBarrierOpCode(it->opcode()) && + !IsStorageClassSafeAcrossMemoryBarriers(storage_class)) { + return false; + } + } + return true; +} + +void TransformationReplaceLoadStoreWithCopyMemory::Apply( + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { + // OpLoad and OpStore instructions must be defined. + auto load_instruction = + FindInstruction(message_.load_instruction_descriptor(), ir_context); + assert(load_instruction && load_instruction->opcode() == SpvOpLoad && + "The required OpLoad instruction must be defined."); + auto store_instruction = + FindInstruction(message_.store_instruction_descriptor(), ir_context); + assert(store_instruction && store_instruction->opcode() == SpvOpStore && + "The required OpStore instruction must be defined."); + + // Intermediate values of the OpLoad and the OpStore must match. + assert(load_instruction->result_id() == + store_instruction->GetSingleWordOperand( + kOpStoreOperandIndexIntermediateIdToWrite) && + "OpLoad and OpStore must refer to the same value."); + + // Get the ids of the source operand of the OpLoad and the target operand of + // the OpStore. + uint32_t source_variable_id = + load_instruction->GetSingleWordOperand(kOpLoadOperandIndexSourceVariable); + uint32_t target_variable_id = store_instruction->GetSingleWordOperand( + kOpStoreOperandIndexTargetVariable); + + // Insert the OpCopyMemory instruction before the OpStore instruction. + store_instruction->InsertBefore(MakeUnique<opt::Instruction>( + ir_context, SpvOpCopyMemory, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {target_variable_id}}, + {SPV_OPERAND_TYPE_ID, {source_variable_id}}}))); + + // Remove the OpStore instruction. + ir_context->KillInst(store_instruction); + + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +bool TransformationReplaceLoadStoreWithCopyMemory::IsMemoryWritingOpCode( + SpvOp op_code) { + if (spvOpcodeIsAtomicOp(op_code)) { + return op_code != SpvOpAtomicLoad; + } + switch (op_code) { + case SpvOpStore: + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: + return true; + default: + return false; + } +} + +bool TransformationReplaceLoadStoreWithCopyMemory::IsMemoryBarrierOpCode( + SpvOp op_code) { + switch (op_code) { + case SpvOpMemoryBarrier: + case SpvOpMemoryNamedBarrier: + return true; + default: + return false; + } +} + +bool TransformationReplaceLoadStoreWithCopyMemory:: + IsStorageClassSafeAcrossMemoryBarriers(SpvStorageClass storage_class) { + switch (storage_class) { + case SpvStorageClassUniformConstant: + case SpvStorageClassInput: + case SpvStorageClassUniform: + case SpvStorageClassPrivate: + case SpvStorageClassFunction: + return true; + default: + return false; + } +} + +protobufs::Transformation +TransformationReplaceLoadStoreWithCopyMemory::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_load_store_with_copy_memory() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_load_store_with_copy_memory.h b/source/fuzz/transformation_replace_load_store_with_copy_memory.h new file mode 100644 index 00000000..d6f48807 --- /dev/null +++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.h @@ -0,0 +1,76 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/fuzz/transformation_context.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceLoadStoreWithCopyMemory : public Transformation { + public: + explicit TransformationReplaceLoadStoreWithCopyMemory( + const protobufs::TransformationReplaceLoadStoreWithCopyMemory& message); + + TransformationReplaceLoadStoreWithCopyMemory( + const protobufs::InstructionDescriptor& load_instruction_descriptor, + const protobufs::InstructionDescriptor& store_instruction_descriptor); + + // - |message_.load_instruction_descriptor| must identify an OpLoad + // instruction. + // - |message_.store_instruction_descriptor| must identify an OpStore + // instruction. + // - The OpStore must write the intermediate value loaded by the OpLoad. + // - The OpLoad and the OpStore must not have certain instruction in between + // (checked by IsMemoryWritingOpCode(), IsMemoryBarrierOpCode(), + // IsStorageClassSafeAcrossMemoryBarriers()). + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Takes a pair of instruction descriptors to OpLoad and OpStore that have the + // same intermediate value and replaces the OpStore with an equivalent + // OpCopyMemory. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + // Checks if the instruction that has an |op_code| might write to + // the source operand of the OpLoad instruction. + static bool IsMemoryWritingOpCode(SpvOp op_code); + + // Checks if the instruction that has an |op_code| is a memory barrier that + // could interfere with the source operand of the OpLoad instruction + static bool IsMemoryBarrierOpCode(SpvOp op_code); + + // Checks if the |storage_class| of the source operand of the OpLoad + // instruction implies that this variable cannot change (due to other threads) + // across memory barriers. + static bool IsStorageClassSafeAcrossMemoryBarriers( + SpvStorageClass storage_class); + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceLoadStoreWithCopyMemory message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_ diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp new file mode 100644 index 00000000..d5d324b9 --- /dev/null +++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp @@ -0,0 +1,110 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationReplaceOpPhiIdFromDeadPredecessor:: + TransformationReplaceOpPhiIdFromDeadPredecessor( + const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor& + message) + : message_(message) {} + +TransformationReplaceOpPhiIdFromDeadPredecessor:: + TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id, + uint32_t pred_label_id, + uint32_t replacement_id) { + message_.set_opphi_id(opphi_id); + message_.set_pred_label_id(pred_label_id); + message_.set_replacement_id(replacement_id); +} + +bool TransformationReplaceOpPhiIdFromDeadPredecessor::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const { + // |opphi_id| must be the id of an OpPhi instruction. + auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id()); + if (!opphi_def || opphi_def->opcode() != SpvOpPhi) { + return false; + } + + // |pred_label_id| must be the label id of a dead block. + auto pred_block = ir_context->get_instr_block(message_.pred_label_id()); + if (!pred_block || pred_block->id() != message_.pred_label_id() || + !transformation_context.GetFactManager()->BlockIsDead(pred_block->id())) { + return false; + } + + // |pred_label_id| must be one of the predecessors of the block containing the + // OpPhi instruction. + bool found = false; + for (auto pred : + ir_context->cfg()->preds(ir_context->get_instr_block(opphi_def)->id())) { + if (pred == message_.pred_label_id()) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + // |replacement_id| must have the same type id as the OpPhi instruction. + auto replacement_def = + ir_context->get_def_use_mgr()->GetDef(message_.replacement_id()); + + if (!replacement_def || replacement_def->type_id() != opphi_def->type_id()) { + return false; + } + + // The replacement id must be available at the end of the predecessor. + return fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, pred_block->terminator(), replacement_def->result_id()); +} + +void TransformationReplaceOpPhiIdFromDeadPredecessor::Apply( + opt::IRContext* ir_context, + TransformationContext* /* transformation_context */) const { + // Get the OpPhi instruction. + auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id()); + + // Find the index corresponding to the operand being replaced and replace it, + // by looping through the odd-indexed input operands and finding + // |pred_label_id|. The index that we are interested in is the one before + // that. + for (uint32_t i = 1; i < opphi_def->NumInOperands(); i += 2) { + if (opphi_def->GetSingleWordInOperand(i) == message_.pred_label_id()) { + // The operand to be replaced is at index i-1. + opphi_def->SetInOperand(i - 1, {message_.replacement_id()}); + } + } + + // Invalidate the analyses because we have altered the usages of ids. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation +TransformationReplaceOpPhiIdFromDeadPredecessor::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_opphi_id_from_dead_predecessor() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h new file mode 100644 index 00000000..2833eb28 --- /dev/null +++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h @@ -0,0 +1,56 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceOpPhiIdFromDeadPredecessor : public Transformation { + public: + explicit TransformationReplaceOpPhiIdFromDeadPredecessor( + const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor& + message); + + TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id, + uint32_t pred_label_id, + uint32_t replacement_id); + + // - |message_.opphi_id| is the id of an OpPhi instruction. + // - |message_.pred_label_id| is the label id of one of the predecessors of + // the block containing the OpPhi instruction. + // - The predecessor has been recorded as dead. + // - |message_.replacement_id| is the id of an instruction with the same type + // as the OpPhi instruction, available at the end of the predecessor. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the id corresponding to predecessor |message_.pred_label_id|, in + // the OpPhi instruction |message_.opphi_id|, with |message_.replacement_id|. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message_; +}; +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_ diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp new file mode 100644 index 00000000..5ae56fd2 --- /dev/null +++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp @@ -0,0 +1,204 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { +TransformationReplaceOpSelectWithConditionalBranch:: + TransformationReplaceOpSelectWithConditionalBranch( + const spvtools::fuzz::protobufs:: + TransformationReplaceOpSelectWithConditionalBranch& message) + : message_(message) {} + +TransformationReplaceOpSelectWithConditionalBranch:: + TransformationReplaceOpSelectWithConditionalBranch( + uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) { + message_.set_select_id(select_id); + message_.set_true_block_id(true_block_id); + message_.set_false_block_id(false_block_id); +} + +bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& /* unused */) const { + assert((message_.true_block_id() || message_.false_block_id()) && + "At least one of the ids must be non-zero."); + + // Check that the non-zero ids are fresh. + std::set<uint32_t> used_ids; + for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) { + if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context, + &used_ids)) { + return false; + } + } + + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.select_id()); + + // The instruction must exist and it must be an OpSelect instruction. + if (!instruction || instruction->opcode() != SpvOpSelect) { + return false; + } + + // Check that the condition is a scalar boolean. + auto condition = ir_context->get_def_use_mgr()->GetDef( + instruction->GetSingleWordInOperand(0)); + assert(condition && "The condition should always exist in a valid module."); + + auto condition_type = + ir_context->get_type_mgr()->GetType(condition->type_id()); + if (!condition_type->AsBool()) { + return false; + } + + auto block = ir_context->get_instr_block(instruction); + assert(block && "The block containing the instruction must be found"); + + // The instruction must be the first in its block. + if (instruction->unique_id() != block->begin()->unique_id()) { + return false; + } + + // The block must not be a merge block. + if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) { + return false; + } + + // The block must have exactly one predecessor. + auto predecessors = ir_context->cfg()->preds(block->id()); + if (predecessors.size() != 1) { + return false; + } + + uint32_t pred_id = predecessors[0]; + auto predecessor = ir_context->get_instr_block(pred_id); + + // The predecessor must not be the header of a construct and it must end with + // OpBranch. + if (predecessor->GetMergeInst() != nullptr || + predecessor->terminator()->opcode() != SpvOpBranch) { + return false; + } + + return true; +} + +void TransformationReplaceOpSelectWithConditionalBranch::Apply( + opt::IRContext* ir_context, TransformationContext* /* unused */) const { + auto instruction = + ir_context->get_def_use_mgr()->GetDef(message_.select_id()); + + auto block = ir_context->get_instr_block(instruction); + + auto predecessor = + ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]); + + // Create a new block for each non-zero id in {|message_.true_branch_id|, + // |message_.false_branch_id|}. Make each newly-created block branch + // unconditionally to the instruction block. + for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) { + if (id) { + fuzzerutil::UpdateModuleIdBound(ir_context, id); + + // Create the new block. + auto new_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>( + ir_context, SpvOpLabel, 0, id, opt::Instruction::OperandList{})); + + // Add an unconditional branch from the new block to the instruction + // block. + new_block->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}})); + + // Insert the new block right after the predecessor of the instruction + // block. + block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block); + } + } + + // Delete the OpBranch instruction from the predecessor. + ir_context->KillInst(predecessor->terminator()); + + // Add an OpSelectionMerge instruction to the predecessor block, where the + // merge block is the instruction block. + predecessor->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpSelectionMerge, 0, 0, + opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}})); + + // |if_block| will be the true block, if it has been created, the instruction + // block otherwise. + uint32_t if_block = + message_.true_block_id() ? message_.true_block_id() : block->id(); + + // |else_block| will be the false block, if it has been created, the + // instruction block otherwise. + uint32_t else_block = + message_.false_block_id() ? message_.false_block_id() : block->id(); + + assert(if_block != else_block && + "|if_block| and |else_block| should always be different, if the " + "transformation is applicable."); + + // Add a conditional branching instruction to the predecessor, branching to + // |if_block| if the condition is true and to |if_false| otherwise. + predecessor->AddInstruction(MakeUnique<opt::Instruction>( + ir_context, SpvOpBranchConditional, 0, 0, + opt::Instruction::OperandList{ + {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}}, + {SPV_OPERAND_TYPE_ID, {if_block}}, + {SPV_OPERAND_TYPE_ID, {else_block}}})); + + // |if_pred| will be the true block, if it has been created, the existing + // predecessor otherwise. + uint32_t if_pred = + message_.true_block_id() ? message_.true_block_id() : predecessor->id(); + + // |else_pred| will be the false block, if it has been created, the existing + // predecessor otherwise. + uint32_t else_pred = + message_.false_block_id() ? message_.false_block_id() : predecessor->id(); + + // Replace the OpSelect instruction in the merge block with an OpPhi. + // This: OpSelect %type %cond %if %else + // will become: OpPhi %type %if %if_pred %else %else_pred + instruction->SetOpcode(SpvOpPhi); + std::vector<opt::Operand> operands; + + operands.emplace_back(instruction->GetInOperand(1)); + operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}}); + + operands.emplace_back(instruction->GetInOperand(2)); + operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}}); + + instruction->SetInOperands(std::move(operands)); + + // Invalidate all analyses, since the structure of the module was changed. + ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation +TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const { + protobufs::Transformation result; + *result.mutable_replace_opselect_with_conditional_branch() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.h b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h new file mode 100644 index 00000000..612c6468 --- /dev/null +++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h @@ -0,0 +1,61 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H +#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H + +#include "source/fuzz/transformation.h" + +namespace spvtools { +namespace fuzz { + +class TransformationReplaceOpSelectWithConditionalBranch + : public Transformation { + public: + explicit TransformationReplaceOpSelectWithConditionalBranch( + const protobufs::TransformationReplaceOpSelectWithConditionalBranch& + message); + + TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id, + uint32_t true_block_id, + uint32_t false_block_id); + + // - |message_.select_id| is the result id of an OpSelect instruction. + // - The condition of the OpSelect must be a scalar boolean. + // - The OpSelect instruction is the first instruction in its block. + // - The block containing the instruction is not a merge block, and it has a + // single predecessor, which is not a header and whose last instruction is + // OpBranch. + // - Each of |message_.true_block_id| and |message_.false_block_id| is either + // 0 or a valid fresh id, and at most one of them is 0. They must be + // distinct. + bool IsApplicable( + opt::IRContext* ir_context, + const TransformationContext& transformation_context) const override; + + // Replaces the OpSelect instruction with id |message_.select_id| with a + // conditional branch and an OpPhi instruction. + void Apply(opt::IRContext* ir_context, + TransformationContext* transformation_context) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationReplaceOpSelectWithConditionalBranch message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H diff --git a/source/fuzz/transformation_replace_parameter_with_global.cpp b/source/fuzz/transformation_replace_parameter_with_global.cpp index 8dddd600..c08d3c51 100644 --- a/source/fuzz/transformation_replace_parameter_with_global.cpp +++ b/source/fuzz/transformation_replace_parameter_with_global.cpp @@ -66,7 +66,7 @@ bool TransformationReplaceParameterWithGlobal::IsApplicable( // Check that initializer for the global variable exists in the module. if (fuzzerutil::MaybeGetZeroConstant(ir_context, transformation_context, - param_inst->type_id()) == 0) { + param_inst->type_id(), false) == 0) { return false; } @@ -97,15 +97,7 @@ void TransformationReplaceParameterWithGlobal::Apply( SpvStorageClassPrivate), SpvStorageClassPrivate, fuzzerutil::MaybeGetZeroConstant(ir_context, *transformation_context, - param_inst->type_id())); - - // Mark the global variable's pointee as irrelevant if replaced parameter is - // irrelevant. - if (transformation_context->GetFactManager()->IdIsIrrelevant( - message_.parameter_id())) { - transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( - message_.global_variable_fresh_id()); - } + param_inst->type_id(), false)); auto* function = fuzzerutil::GetFunctionFromParameterId( ir_context, message_.parameter_id()); @@ -161,38 +153,41 @@ void TransformationReplaceParameterWithGlobal::Apply( } // Remove the parameter from the function. - function->RemoveParameter(message_.parameter_id()); + fuzzerutil::RemoveParameter(ir_context, message_.parameter_id()); // Update function's type. - auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); - assert(old_function_type && "Function has invalid type"); - - // Preemptively add function's return type id. - std::vector<uint32_t> type_ids = { - old_function_type->GetSingleWordInOperand(0)}; - - // +1 and -1 since the first operand is the return type id. - for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { - if (i - 1 != parameter_index) { - type_ids.push_back(old_function_type->GetSingleWordInOperand(i)); + { + // We use a separate scope here since |old_function_type| might become a + // dangling pointer after the call to the fuzzerutil::UpdateFunctionType. + + auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); + assert(old_function_type && "Function has invalid type"); + + // +1 and -1 since the first operand is the return type id. + std::vector<uint32_t> parameter_type_ids; + for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { + if (i - 1 != parameter_index) { + parameter_type_ids.push_back( + old_function_type->GetSingleWordInOperand(i)); + } } - } - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 && - fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) { - // Change the old type in place. +1 since the first operand is the result - // type id of the function. - old_function_type->RemoveInOperand(parameter_index + 1); - } else { - // Find an existing or create a new function type. - function->DefInst().SetInOperand( - 1, {fuzzerutil::FindOrCreateFunctionType( - ir_context, message_.function_type_fresh_id(), type_ids)}); + fuzzerutil::UpdateFunctionType( + ir_context, function->result_id(), message_.function_type_fresh_id(), + old_function_type->GetSingleWordInOperand(0), parameter_type_ids); } // Make sure our changes are analyzed ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); + + // Mark the pointee of the global variable storing the parameter's value as + // irrelevant if replaced parameter is irrelevant. + if (transformation_context->GetFactManager()->IdIsIrrelevant( + message_.parameter_id())) { + transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant( + message_.global_variable_fresh_id()); + } } protobufs::Transformation TransformationReplaceParameterWithGlobal::ToMessage() @@ -206,23 +201,7 @@ bool TransformationReplaceParameterWithGlobal::IsParameterTypeSupported( const opt::analysis::Type& type) { // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403): // Think about other type instructions we can add here. - switch (type.kind()) { - case opt::analysis::Type::kBool: - case opt::analysis::Type::kInteger: - case opt::analysis::Type::kFloat: - case opt::analysis::Type::kArray: - case opt::analysis::Type::kMatrix: - case opt::analysis::Type::kVector: - return true; - case opt::analysis::Type::kStruct: - return std::all_of(type.AsStruct()->element_types().begin(), - type.AsStruct()->element_types().end(), - [](const opt::analysis::Type* element_type) { - return IsParameterTypeSupported(*element_type); - }); - default: - return false; - } + return fuzzerutil::CanCreateConstant(type); } } // namespace fuzz diff --git a/source/fuzz/transformation_replace_params_with_struct.cpp b/source/fuzz/transformation_replace_params_with_struct.cpp index ee9a738c..cc8021ad 100644 --- a/source/fuzz/transformation_replace_params_with_struct.cpp +++ b/source/fuzz/transformation_replace_params_with_struct.cpp @@ -28,8 +28,7 @@ TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct( TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct( const std::vector<uint32_t>& parameter_id, uint32_t fresh_function_type_id, uint32_t fresh_parameter_id, - const std::unordered_map<uint32_t, uint32_t>& - caller_id_to_fresh_composite_id) { + const std::map<uint32_t, uint32_t>& caller_id_to_fresh_composite_id) { message_.set_fresh_function_type_id(fresh_function_type_id); message_.set_fresh_parameter_id(fresh_parameter_id); @@ -37,9 +36,8 @@ TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct( message_.add_parameter_id(id); } - message_.mutable_caller_id_to_fresh_composite_id()->insert( - caller_id_to_fresh_composite_id.begin(), - caller_id_to_fresh_composite_id.end()); + *message_.mutable_caller_id_to_fresh_composite_id() = + fuzzerutil::MapToRepeatedUInt32Pair(caller_id_to_fresh_composite_id); } bool TransformationReplaceParamsWithStruct::IsApplicable( @@ -103,13 +101,16 @@ bool TransformationReplaceParamsWithStruct::IsApplicable( return false; } + const auto caller_id_to_fresh_composite_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.caller_id_to_fresh_composite_id()); + // Check that |callee_id_to_fresh_composite_id| is valid. for (const auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) { // Check that the callee is present in the map. It's ok if the map contains // more ids that there are callees (those ids will not be used). - if (!message_.caller_id_to_fresh_composite_id().contains( - inst->result_id())) { + if (!caller_id_to_fresh_composite_id.count(inst->result_id())) { return false; } } @@ -118,7 +119,7 @@ bool TransformationReplaceParamsWithStruct::IsApplicable( std::vector<uint32_t> fresh_ids = {message_.fresh_function_type_id(), message_.fresh_parameter_id()}; - for (const auto& entry : message_.caller_id_to_fresh_composite_id()) { + for (const auto& entry : caller_id_to_fresh_composite_id) { fresh_ids.push_back(entry.second); } @@ -151,21 +152,12 @@ void TransformationReplaceParamsWithStruct::Apply( // Compute indices of replaced parameters. This will be used to adjust // OpFunctionCall instructions and create OpCompositeConstruct instructions at // every call site. - std::vector<uint32_t> indices_of_replaced_params; - { - // We want to destroy |params| after the loop because it will contain - // dangling pointers when we remove parameters from the function. - auto params = fuzzerutil::GetParameters(ir_context, function->result_id()); - for (auto id : message_.parameter_id()) { - auto it = std::find_if(params.begin(), params.end(), - [id](const opt::Instruction* param) { - return param->result_id() == id; - }); - assert(it != params.end() && "Parameter's id is invalid"); - indices_of_replaced_params.push_back( - static_cast<uint32_t>(it - params.begin())); - } - } + const auto indices_of_replaced_params = + ComputeIndicesOfReplacedParameters(ir_context); + + const auto caller_id_to_fresh_composite_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.caller_id_to_fresh_composite_id()); // Update all function calls. for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) { @@ -179,17 +171,18 @@ void TransformationReplaceParamsWithStruct::Apply( } // Remove arguments from the function call. We do it in a separate loop - // and in reverse order to make sure we have removed correct operands. - for (auto it = indices_of_replaced_params.rbegin(); - it != indices_of_replaced_params.rend(); ++it) { + // and in decreasing order to make sure we have removed correct operands. + for (auto index : std::set<uint32_t, std::greater<uint32_t>>( + indices_of_replaced_params.begin(), + indices_of_replaced_params.end())) { // +1 since the first in operand to OpFunctionCall is the result id of // the function. - inst->RemoveInOperand(*it + 1); + inst->RemoveInOperand(index + 1); } // Insert OpCompositeConstruct before the function call. auto fresh_composite_id = - message_.caller_id_to_fresh_composite_id().at(inst->result_id()); + caller_id_to_fresh_composite_id.at(inst->result_id()); inst->InsertBefore(MakeUnique<opt::Instruction>( ir_context, SpvOpCompositeConstruct, struct_type_id, fresh_composite_id, std::move(composite_components))); @@ -227,47 +220,34 @@ void TransformationReplaceParamsWithStruct::Apply( {SPV_OPERAND_TYPE_ID, {message_.fresh_parameter_id()}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast<uint32_t>(i)}}})); - function->RemoveParameter(param_inst->result_id()); + fuzzerutil::RemoveParameter(ir_context, param_inst->result_id()); } // Update function's type. - auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); - assert(old_function_type && "Function has invalid type"); - - std::vector<uint32_t> type_ids = { - // Result type of the function. - old_function_type->GetSingleWordInOperand(0)}; - - // +1 since the first in operand to OpTypeFunction is the result type id - // of the function. - for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { - if (std::find(indices_of_replaced_params.begin(), - indices_of_replaced_params.end(), - i - 1) == indices_of_replaced_params.end()) { - type_ids.push_back(old_function_type->GetSingleWordInOperand(i)); - } - } - - type_ids.push_back(struct_type_id); - - if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 && - fuzzerutil::FindFunctionType(ir_context, type_ids) == 0) { - // Update |old_function_type| in place. - opt::Instruction::OperandList replaced_operands; - for (auto id : type_ids) { - replaced_operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); + { + // We use a separate scope here since |old_function_type| might become a + // dangling pointer after the call to the fuzzerutil::UpdateFunctionType. + + auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function); + assert(old_function_type && "Function has invalid type"); + + // +1 since the first in operand to OpTypeFunction is the result type id + // of the function. + std::vector<uint32_t> parameter_type_ids; + for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) { + if (std::find(indices_of_replaced_params.begin(), + indices_of_replaced_params.end(), + i - 1) == indices_of_replaced_params.end()) { + parameter_type_ids.push_back( + old_function_type->GetSingleWordInOperand(i)); + } } - old_function_type->SetInOperands(std::move(replaced_operands)); + parameter_type_ids.push_back(struct_type_id); - // Make sure domination rules are satisfied. - old_function_type->RemoveFromList(); - ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type)); - } else { - // Create a new function type or use an existing one. - function->DefInst().SetInOperand( - 1, {fuzzerutil::FindOrCreateFunctionType( - ir_context, message_.fresh_function_type_id(), type_ids)}); + fuzzerutil::UpdateFunctionType( + ir_context, function->result_id(), message_.fresh_function_type_id(), + old_function_type->GetSingleWordInOperand(0), parameter_type_ids); } // Make sure our changes are analyzed @@ -286,23 +266,7 @@ bool TransformationReplaceParamsWithStruct::IsParameterTypeSupported( const opt::analysis::Type& param_type) { // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403): // Consider adding support for more types of parameters. - switch (param_type.kind()) { - case opt::analysis::Type::kBool: - case opt::analysis::Type::kInteger: - case opt::analysis::Type::kFloat: - case opt::analysis::Type::kArray: - case opt::analysis::Type::kVector: - case opt::analysis::Type::kMatrix: - return true; - case opt::analysis::Type::kStruct: - return std::all_of(param_type.AsStruct()->element_types().begin(), - param_type.AsStruct()->element_types().end(), - [](const opt::analysis::Type* type) { - return IsParameterTypeSupported(*type); - }); - default: - return false; - } + return fuzzerutil::CanCreateConstant(param_type); } uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType( @@ -315,5 +279,30 @@ uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType( return fuzzerutil::MaybeGetStructType(ir_context, component_type_ids); } +std::vector<uint32_t> +TransformationReplaceParamsWithStruct::ComputeIndicesOfReplacedParameters( + opt::IRContext* ir_context) const { + assert(!message_.parameter_id().empty() && + "There must be at least one parameter to replace"); + + const auto* function = fuzzerutil::GetFunctionFromParameterId( + ir_context, message_.parameter_id(0)); + assert(function && "|parameter_id|s are invalid"); + + std::vector<uint32_t> result; + + auto params = fuzzerutil::GetParameters(ir_context, function->result_id()); + for (auto id : message_.parameter_id()) { + auto it = std::find_if(params.begin(), params.end(), + [id](const opt::Instruction* param) { + return param->result_id() == id; + }); + assert(it != params.end() && "Parameter's id is invalid"); + result.push_back(static_cast<uint32_t>(it - params.begin())); + } + + return result; +} + } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/transformation_replace_params_with_struct.h b/source/fuzz/transformation_replace_params_with_struct.h index 0ff73405..7e40de89 100644 --- a/source/fuzz/transformation_replace_params_with_struct.h +++ b/source/fuzz/transformation_replace_params_with_struct.h @@ -15,7 +15,7 @@ #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_ #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_ -#include <unordered_map> +#include <map> #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/transformation.h" @@ -33,8 +33,7 @@ class TransformationReplaceParamsWithStruct : public Transformation { TransformationReplaceParamsWithStruct( const std::vector<uint32_t>& parameter_id, uint32_t fresh_function_type_id, uint32_t fresh_parameter_id, - const std::unordered_map<uint32_t, uint32_t>& - caller_id_to_fresh_composite_id); + const std::map<uint32_t, uint32_t>& caller_id_to_fresh_composite_id); // - Each element of |parameter_id| is a valid result id of some // OpFunctionParameter instruction. All parameter ids must correspond to @@ -74,6 +73,12 @@ class TransformationReplaceParamsWithStruct : public Transformation { // transformation (see docs on the IsApplicable method to learn more). uint32_t MaybeGetRequiredStructType(opt::IRContext* ir_context) const; + // Returns a vector of indices of parameters to replace. Concretely, i'th + // element is the index of the parameter with result id |parameter_id[i]| in + // its function. + std::vector<uint32_t> ComputeIndicesOfReplacedParameters( + opt::IRContext* ir_context) const; + protobufs::TransformationReplaceParamsWithStruct message_; }; diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp index 845ac69e..6cf2104d 100644 --- a/source/fuzz/transformation_set_loop_control.cpp +++ b/source/fuzz/transformation_set_loop_control.cpp @@ -42,8 +42,8 @@ bool TransformationSetLoopControl::IsApplicable( return false; } - // We sanity-check that the transformation does not try to set any meaningless - // bits of the loop control mask. + // We assert that the transformation does not try to set any meaningless bits + // of the loop control mask. uint32_t all_loop_control_mask_bits_set = SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask | diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp index b020d98a..5e2babac 100644 --- a/source/fuzz/transformation_split_block.cpp +++ b/source/fuzz/transformation_split_block.cpp @@ -83,27 +83,9 @@ bool TransformationSplitBlock::IsApplicable( // Splitting the block must not separate the definition of an OpSampledImage // from its use: the SPIR-V data rules require them to be in the same block. - std::set<uint32_t> sampled_image_result_ids; - bool before_split = true; - for (auto& instruction : *block_to_split) { - if (&instruction == &*split_before) { - before_split = false; - } - if (before_split) { - if (instruction.opcode() == SpvOpSampledImage) { - sampled_image_result_ids.insert(instruction.result_id()); - } - } else { - if (!instruction.WhileEachInId( - [&sampled_image_result_ids](uint32_t* id) -> bool { - return !sampled_image_result_ids.count(*id); - })) { - return false; - } - } - } - - return true; + return !fuzzerutil:: + SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse( + block_to_split, instruction_to_split_before); } void TransformationSplitBlock::Apply( @@ -135,11 +117,10 @@ void TransformationSplitBlock::Apply( // predecessor operand so that the block they used to be inside is now the // predecessor. new_bb->ForEachPhiInst([block_to_split](opt::Instruction* phi_inst) { - // The following assertion is a sanity check. It is guaranteed to hold - // if IsApplicable holds. - assert(phi_inst->NumInOperands() == 2 && - "We can only split a block before an OpPhi if block has exactly " - "one predecessor."); + assert( + phi_inst->NumInOperands() == 2 && + "Precondition: a block can only be split before an OpPhi if the block" + "has exactly one predecessor."); phi_inst->SetInOperand(1, {block_to_split->id()}); }); diff --git a/source/fuzz/transformation_swap_commutable_operands.cpp b/source/fuzz/transformation_swap_commutable_operands.cpp index b7622a23..e2f8360f 100644 --- a/source/fuzz/transformation_swap_commutable_operands.cpp +++ b/source/fuzz/transformation_swap_commutable_operands.cpp @@ -31,8 +31,7 @@ TransformationSwapCommutableOperands::TransformationSwapCommutableOperands( } bool TransformationSwapCommutableOperands::IsApplicable( - opt::IRContext* ir_context, const TransformationContext& /*unused*/ - ) const { + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { auto instruction = FindInstruction(message_.instruction_descriptor(), ir_context); if (instruction == nullptr) return false; @@ -46,8 +45,7 @@ bool TransformationSwapCommutableOperands::IsApplicable( } void TransformationSwapCommutableOperands::Apply( - opt::IRContext* ir_context, TransformationContext* /*unused*/ - ) const { + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { auto instruction = FindInstruction(message_.instruction_descriptor(), ir_context); // By design, the instructions defined to be commutative have exactly two diff --git a/source/fuzz/transformation_swap_conditional_branch_operands.cpp b/source/fuzz/transformation_swap_conditional_branch_operands.cpp index 25f48c4a..5e39e88f 100644 --- a/source/fuzz/transformation_swap_conditional_branch_operands.cpp +++ b/source/fuzz/transformation_swap_conditional_branch_operands.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_swap_conditional_branch_operands.h" + #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.cpp b/source/fuzz/transformation_toggle_access_chain_instruction.cpp index ca24a18a..e68fc65a 100644 --- a/source/fuzz/transformation_toggle_access_chain_instruction.cpp +++ b/source/fuzz/transformation_toggle_access_chain_instruction.cpp @@ -33,8 +33,7 @@ TransformationToggleAccessChainInstruction:: } bool TransformationToggleAccessChainInstruction::IsApplicable( - opt::IRContext* ir_context, const TransformationContext& /*unused*/ - ) const { + opt::IRContext* ir_context, const TransformationContext& /*unused*/) const { auto instruction = FindInstruction(message_.instruction_descriptor(), ir_context); if (instruction == nullptr) { @@ -56,8 +55,7 @@ bool TransformationToggleAccessChainInstruction::IsApplicable( } void TransformationToggleAccessChainInstruction::Apply( - opt::IRContext* ir_context, TransformationContext* /*unused*/ - ) const { + opt::IRContext* ir_context, TransformationContext* /*unused*/) const { auto instruction = FindInstruction(message_.instruction_descriptor(), ir_context); SpvOp opcode = instruction->opcode(); diff --git a/source/fuzz/transformation_vector_shuffle.cpp b/source/fuzz/transformation_vector_shuffle.cpp index b3bd5935..52a6fe83 100644 --- a/source/fuzz/transformation_vector_shuffle.cpp +++ b/source/fuzz/transformation_vector_shuffle.cpp @@ -153,6 +153,13 @@ void TransformationVectorShuffle::Apply( ir_context->InvalidateAnalysesExceptFor( opt::IRContext::Analysis::kAnalysisNone); + // If the new instruction is irrelevant (because it is in a dead block), it + // cannot participate in any DataSynonym fact. + if (transformation_context->GetFactManager()->IdIsIrrelevant( + message_.fresh_id())) { + return; + } + // Add synonym facts relating the defined elements of the shuffle result to // the vector components that they come from. for (uint32_t component_index = 0; @@ -205,8 +212,7 @@ void TransformationVectorShuffle::Apply( // Add a fact relating this input vector component with the associated // result component. transformation_context->GetFactManager()->AddFactDataSynonym( - descriptor_for_result_component, descriptor_for_source_component, - ir_context); + descriptor_for_result_component, descriptor_for_source_component); } } diff --git a/source/link/CMakeLists.txt b/source/link/CMakeLists.txt index d3083192..bb058ea2 100644 --- a/source/link/CMakeLists.txt +++ b/source/link/CMakeLists.txt @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -add_library(SPIRV-Tools-link +add_library(SPIRV-Tools-link STATIC linker.cpp ) diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp index 5525bbc5..eb08f8fe 100644 --- a/source/name_mapper.cpp +++ b/source/name_mapper.cpp @@ -324,7 +324,7 @@ std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type, if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) { return desc->name; } else { - // Invalid input. Just give something sane. + // Invalid input. Just give something. return std::string("StorageClass") + to_string(word); } } diff --git a/source/opcode.cpp b/source/opcode.cpp index f93cfd37..8305bcf0 100644 --- a/source/opcode.cpp +++ b/source/opcode.cpp @@ -719,3 +719,32 @@ std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) { return {}; } } + +bool spvOpcodeIsAccessChain(SpvOp opcode) { + switch (opcode) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + return true; + default: + return false; + } +} + +bool spvOpcodeIsBit(SpvOp opcode) { + switch (opcode) { + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + case SpvOpBitReverse: + case SpvOpBitCount: + return true; + default: + return false; + } +} diff --git a/source/opcode.h b/source/opcode.h index 9aeba76e..3702cb35 100644 --- a/source/opcode.h +++ b/source/opcode.h @@ -134,14 +134,20 @@ bool spvOpcodeIsDebug(SpvOp opcode); // where the order of the operands is irrelevant. bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode); -// Returns true for opcodes that represents linear algebra instructions. +// Returns true for opcodes that represent linear algebra instructions. bool spvOpcodeIsLinearAlgebra(SpvOp opcode); -// Returns true for opcodes that represents an image sample instruction. +// Returns true for opcodes that represent image sample instructions. bool spvOpcodeIsImageSample(SpvOp opcode); // Returns a vector containing the indices of the memory semantics <id> // operands for |opcode|. std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode); +// Returns true for opcodes that represent access chain instructions. +bool spvOpcodeIsAccessChain(SpvOp opcode); + +// Returns true for opcodes that represent bit instructions. +bool spvOpcodeIsBit(SpvOp opcode); + #endif // SOURCE_OPCODE_H_ diff --git a/source/operand.cpp b/source/operand.cpp index 09105958..7b2b98f2 100644 --- a/source/operand.cpp +++ b/source/operand.cpp @@ -265,7 +265,6 @@ const char* spvOperandTypeStr(spv_operand_type_t type) { case SPV_OPERAND_TYPE_NONE: return "NONE"; default: - assert(0 && "Unhandled operand type!"); break; } return "unknown"; @@ -371,13 +370,35 @@ bool spvOperandIsConcreteMask(spv_operand_type_t type) { } bool spvOperandIsOptional(spv_operand_type_t type) { - return SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE <= type && - type <= SPV_OPERAND_TYPE_LAST_OPTIONAL_TYPE; + switch (type) { + case SPV_OPERAND_TYPE_OPTIONAL_ID: + case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER: + case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: + case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_OPTIONAL_CIV: + return true; + default: + break; + } + // Any variable operand is also optional. + return spvOperandIsVariable(type); } bool spvOperandIsVariable(spv_operand_type_t type) { - return SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE <= type && - type <= SPV_OPERAND_TYPE_LAST_VARIABLE_TYPE; + switch (type) { + case SPV_OPERAND_TYPE_VARIABLE_ID: + case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER_ID: + case SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER: + return true; + default: + break; + } + return false; } bool spvExpandOperandSequenceOnce(spv_operand_type_t type, diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 0047c346..3630a060 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -226,14 +226,14 @@ set(SPIRV_TOOLS_OPT_SOURCES wrap_opkill.cpp ) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() spvtools_pch(SPIRV_TOOLS_OPT_SOURCES pch_source_opt) -add_library(SPIRV-Tools-opt ${SPIRV_TOOLS_OPT_SOURCES}) +add_library(SPIRV-Tools-opt STATIC ${SPIRV_TOOLS_OPT_SOURCES}) spvtools_default_compile_options(SPIRV-Tools-opt) target_include_directories(SPIRV-Tools-opt @@ -245,7 +245,7 @@ target_include_directories(SPIRV-Tools-opt ) # We need the assembling and disassembling functionalities in the main library. target_link_libraries(SPIRV-Tools-opt - PUBLIC ${SPIRV_TOOLS}) + PUBLIC ${SPIRV_TOOLS}-static) set_property(TARGET SPIRV-Tools-opt PROPERTY FOLDER "SPIRV-Tools libraries") spvtools_check_symbol_exports(SPIRV-Tools-opt) diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp index b7557874..39d468f9 100644 --- a/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/source/opt/aggressive_dead_code_elim_pass.cpp @@ -22,6 +22,7 @@ #include "source/cfa.h" #include "source/latest_version_glsl_std_450_header.h" +#include "source/opt/eliminate_dead_functions_util.h" #include "source/opt/iterator.h" #include "source/opt/reflect.h" #include "source/spirv_constant.h" @@ -532,6 +533,17 @@ bool AggressiveDCEPass::AggressiveDCE(Function* func) { AddToWorklist(dec); } + // Add DebugScope and DebugInlinedAt for |liveInst| to the work list. + if (liveInst->GetDebugScope().GetLexicalScope() != kNoDebugScope) { + auto* scope = get_def_use_mgr()->GetDef( + liveInst->GetDebugScope().GetLexicalScope()); + AddToWorklist(scope); + } + if (liveInst->GetDebugInlinedAt() != kNoInlinedAt) { + auto* inlined_at = + get_def_use_mgr()->GetDef(liveInst->GetDebugInlinedAt()); + AddToWorklist(inlined_at); + } worklist_.pop(); } @@ -696,8 +708,8 @@ Pass::Status AggressiveDCEPass::ProcessImpl() { // been marked, it is safe to remove dead global values. modified |= ProcessGlobalValues(); - // Sanity check. - assert(to_kill_.size() == 0 || modified); + assert((to_kill_.empty() || modified) && + "A dead instruction was identified, but no change recorded."); // Kill all dead instructions. for (auto inst : to_kill_) { @@ -727,8 +739,8 @@ bool AggressiveDCEPass::EliminateDeadFunctions() { 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; } @@ -737,12 +749,6 @@ bool AggressiveDCEPass::EliminateDeadFunctions() { return modified; } -void AggressiveDCEPass::EliminateFunction(Function* func) { - // Remove all of the instruction in the function body - func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); }, - true); -} - bool AggressiveDCEPass::ProcessGlobalValues() { // Remove debug and annotation statements referencing dead instructions. // This must be done before killing the instructions, otherwise there are @@ -950,6 +956,7 @@ void AggressiveDCEPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h index 2ce5b577..f02e729f 100644 --- a/source/opt/aggressive_dead_code_elim_pass.h +++ b/source/opt/aggressive_dead_code_elim_pass.h @@ -127,9 +127,6 @@ class AggressiveDCEPass : public MemPass { // Erases functions that are unreachable from the entry points of the module. bool EliminateDeadFunctions(); - // Removes |func| from the module and deletes all its instructions. - void EliminateFunction(Function* func); - // For function |func|, mark all Stores to non-function-scope variables // and block terminating instructions as live. Recursively mark the values // they use. When complete, mark any non-live instructions to be deleted. diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp index 2de92502..2d19bcb3 100644 --- a/source/opt/ccp_pass.cpp +++ b/source/opt/ccp_pass.cpp @@ -135,6 +135,7 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) { } return it->second; }; + uint32_t next_id = context()->module()->IdBound(); Instruction* folded_inst = context()->get_instruction_folder().FoldInstructionToConstant(instr, map_func); @@ -143,6 +144,14 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) { // instructions. When folding we can only generate new constants. assert(folded_inst->IsConstant() && "CCP is only interested in constant."); values_[instr->result_id()] = folded_inst->result_id(); + + // If the folded instruction has just been created, its result ID will + // match the previous ID bound. When this happens, we need to indicate + // that CCP has modified the IR, independently of whether the constant is + // actually propagated. See + // https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 for details. + if (folded_inst->result_id() >= next_id) created_new_constant_ = true; + return SSAPropagator::kInteresting; } @@ -266,16 +275,22 @@ SSAPropagator::PropStatus CCPPass::VisitInstruction(Instruction* instr, } bool CCPPass::ReplaceValues() { - bool retval = false; + // Even if we make no changes to the function's IR, propagation may have + // created new constants. Even if those constants cannot be replaced in + // the IR, the constant definition itself is a change. To reflect this, + // we initialize the IR changed indicator with the value of the + // created_new_constant_ indicator. For an example, see the bug reported + // in https://github.com/KhronosGroup/SPIRV-Tools/issues/3636. + bool changed_ir = created_new_constant_; for (const auto& it : values_) { 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); + changed_ir |= context()->ReplaceAllUsesWith(id, cst_id); } } - return retval; + return changed_ir; } bool CCPPass::PropagateConstants(Function* fp) { @@ -313,6 +328,8 @@ void CCPPass::Initialize() { values_[inst.result_id()] = kVaryingSSAId; } } + + created_new_constant_ = false; } Pass::Status CCPPass::Process() { diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h index 527f459c..83c4ab97 100644 --- a/source/opt/ccp_pass.h +++ b/source/opt/ccp_pass.h @@ -105,6 +105,9 @@ class CCPPass : public MemPass { // Propagator engine used. std::unique_ptr<SSAPropagator> propagator_; + + // True if the pass created new constant instructions during propagation. + bool created_new_constant_; }; } // namespace opt diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp index 68b940f1..67091531 100644 --- a/source/opt/compact_ids_pass.cpp +++ b/source/opt/compact_ids_pass.cpp @@ -21,6 +21,24 @@ namespace spvtools { namespace opt { +namespace { + +// Returns the remapped id of |id| from |result_id_mapping|. If the remapped +// id does not exist, adds a new one to |result_id_mapping| and returns it. +uint32_t GetRemappedId( + std::unordered_map<uint32_t, uint32_t>* result_id_mapping, uint32_t id) { + auto it = result_id_mapping->find(id); + if (it == result_id_mapping->end()) { + const uint32_t new_id = + static_cast<uint32_t>(result_id_mapping->size()) + 1; + const auto insertion_result = result_id_mapping->emplace(id, new_id); + it = insertion_result.first; + assert(insertion_result.second); + } + return it->second; +} + +} // namespace Pass::Status CompactIdsPass::Process() { bool modified = false; @@ -34,18 +52,10 @@ Pass::Status CompactIdsPass::Process() { if (spvIsIdType(type)) { assert(operand->words.size() == 1); uint32_t& id = operand->words[0]; - auto it = result_id_mapping.find(id); - if (it == result_id_mapping.end()) { - const uint32_t new_id = - static_cast<uint32_t>(result_id_mapping.size()) + 1; - const auto insertion_result = - result_id_mapping.emplace(id, new_id); - it = insertion_result.first; - assert(insertion_result.second); - } - if (id != it->second) { + uint32_t new_id = GetRemappedId(&result_id_mapping, id); + if (id != new_id) { modified = true; - id = it->second; + id = new_id; // Update data cached in the instruction object. if (type == SPV_OPERAND_TYPE_RESULT_ID) { inst->SetResultId(id); @@ -56,6 +66,23 @@ Pass::Status CompactIdsPass::Process() { } ++operand; } + + uint32_t scope_id = inst->GetDebugScope().GetLexicalScope(); + if (scope_id != kNoDebugScope) { + uint32_t new_id = GetRemappedId(&result_id_mapping, scope_id); + if (scope_id != new_id) { + inst->UpdateLexicalScope(new_id); + modified = true; + } + } + uint32_t inlinedat_id = inst->GetDebugInlinedAt(); + if (inlinedat_id != kNoInlinedAt) { + uint32_t new_id = GetRemappedId(&result_id_mapping, inlinedat_id); + if (inlinedat_id != new_id) { + inst->UpdateDebugInlinedAt(new_id); + modified = true; + } + } }, true); diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp index 6057356c..cf24295d 100644 --- a/source/opt/constants.cpp +++ b/source/opt/constants.cpp @@ -396,6 +396,12 @@ uint32_t ConstantManager::GetFloatConst(float val) { return GetDefiningInstruction(c)->result_id(); } +uint32_t ConstantManager::GetSIntConst(int32_t val) { + Type* sint_type = context()->get_type_mgr()->GetSIntType(); + const Constant* c = GetConstant(sint_type, {static_cast<uint32_t>(val)}); + return GetDefiningInstruction(c)->result_id(); +} + std::vector<const analysis::Constant*> Constant::GetVectorComponents( analysis::ConstantManager* const_mgr) const { std::vector<const analysis::Constant*> components; diff --git a/source/opt/constants.h b/source/opt/constants.h index 9518b5b6..e17ae6b6 100644 --- a/source/opt/constants.h +++ b/source/opt/constants.h @@ -630,6 +630,9 @@ class ConstantManager { // Returns the id of a 32-bit floating point constant with value |val|. uint32_t GetFloatConst(float val); + // Returns the id of a 32-bit signed integer constant with value |val|. + uint32_t GetSIntConst(int32_t val); + private: // Creates a Constant instance with the given type and a vector of constant // defining words. Returns a unique pointer to the created Constant instance diff --git a/source/opt/dead_insert_elim_pass.cpp b/source/opt/dead_insert_elim_pass.cpp index 7d563438..46f4f124 100644 --- a/source/opt/dead_insert_elim_pass.cpp +++ b/source/opt/dead_insert_elim_pass.cpp @@ -196,6 +196,7 @@ bool DeadInsertElimPass::EliminateDeadInsertsOnePass(Function* func) { } const uint32_t id = ii->result_id(); get_def_use_mgr()->ForEachUser(id, [&ii, this](Instruction* user) { + if (user->IsOpenCL100DebugInstr()) return; switch (user->opcode()) { case SpvOpCompositeInsert: case SpvOpPhi: diff --git a/source/opt/debug_info_manager.cpp b/source/opt/debug_info_manager.cpp index cc631fc1..346134d4 100644 --- a/source/opt/debug_info_manager.cpp +++ b/source/opt/debug_info_manager.cpp @@ -33,10 +33,13 @@ static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4; static const uint32_t kDebugDeclareOperandVariableIndex = 5; static const uint32_t kDebugValueOperandLocalVariableIndex = 4; static const uint32_t kDebugValueOperandExpressionIndex = 6; +static const uint32_t kDebugValueOperandIndexesIndex = 7; static const uint32_t kDebugOperationOperandOperationIndex = 4; static const uint32_t kOpVariableOperandStorageClassIndex = 2; static const uint32_t kDebugLocalVariableOperandParentIndex = 9; -static const uint32_t kDebugOperationOperandOpCodeIndex = 4; +static const uint32_t kExtInstInstructionInIdx = 1; +static const uint32_t kDebugGlobalVariableOperandFlagsIndex = 12; +static const uint32_t kDebugLocalVariableOperandFlagsIndex = 10; namespace spvtools { namespace opt { @@ -96,6 +99,13 @@ void DebugInfoManager::RegisterDbgFunction(Instruction* inst) { assert(inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction && "inst is not a DebugFunction"); auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex); + // Do not register function that has been optimized away + auto fn_inst = GetDbgInst(fn_id); + if (fn_inst != nullptr) { + assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugInfoNone); + return; + } assert( fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() && "Register DebugFunction for a function that already has DebugFunction"); @@ -369,7 +379,7 @@ Instruction* DebugInfoManager::CloneDebugInlinedAt(uint32_t clone_inlined_at_id, std::move(new_inlined_at)); } -bool DebugInfoManager::IsDebugDeclared(uint32_t variable_id) { +bool DebugInfoManager::IsVariableDebugDeclared(uint32_t variable_id) { auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id); return dbg_decl_itr != var_id_to_dbg_decl_.end(); } @@ -446,9 +456,47 @@ bool DebugInfoManager::IsDeclareVisibleToInstr(Instruction* dbg_declare, return IsAncestorOfScope(instr_scope_id, decl_scope_id); } -void DebugInfoManager::AddDebugValue(Instruction* scope_and_line, - uint32_t variable_id, uint32_t value_id, - Instruction* insert_pos) { +Instruction* DebugInfoManager::AddDebugValueWithIndex( + uint32_t dbg_local_var_id, uint32_t value_id, uint32_t expr_id, + uint32_t index_id, Instruction* insert_before) { + uint32_t result_id = context()->TakeNextId(); + if (!result_id) return nullptr; + std::unique_ptr<Instruction> new_dbg_value(new Instruction( + context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), + result_id, + { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {context() + ->get_feature_mgr() + ->GetExtInstImportId_OpenCL100DebugInfo()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {dbg_local_var_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {expr_id == 0 ? GetEmptyDebugExpression()->result_id() : expr_id}}, + })); + if (index_id) { + new_dbg_value->AddOperand( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {index_id}}); + } + + Instruction* added_dbg_value = + insert_before->InsertBefore(std::move(new_dbg_value)); + AnalyzeDebugInst(added_dbg_value); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value); + if (context()->AreAnalysesValid( + IRContext::Analysis::kAnalysisInstrToBlockMapping)) { + auto insert_blk = context()->get_instr_block(insert_before); + context()->set_instr_block(added_dbg_value, insert_blk); + } + return added_dbg_value; +} + +void DebugInfoManager::AddDebugValueIfVarDeclIsVisible( + Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id, + Instruction* insert_pos) { auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id); if (dbg_decl_itr == var_id_to_dbg_decl_.end()) return; @@ -456,36 +504,6 @@ void DebugInfoManager::AddDebugValue(Instruction* scope_and_line, for (auto* dbg_decl_or_val : dbg_decl_itr->second) { if (!IsDeclareVisibleToInstr(dbg_decl_or_val, instr_scope_id)) continue; - uint32_t result_id = context()->TakeNextId(); - std::unique_ptr<Instruction> new_dbg_value(new Instruction( - context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), - result_id, - { - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, - {context() - ->get_feature_mgr() - ->GetExtInstImportId_OpenCL100DebugInfo()}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, - {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, - {dbg_decl_or_val->GetSingleWordOperand( - kDebugValueOperandLocalVariableIndex)}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}}, - {spv_operand_type_t::SPV_OPERAND_TYPE_ID, - {GetEmptyDebugExpression()->result_id()}}, - })); - - if (dbg_decl_or_val->NumOperands() > - kDebugValueOperandExpressionIndex + 1) { - assert(dbg_decl_or_val->GetOpenCL100DebugOpcode() == - OpenCLDebugInfo100DebugValue); - for (uint32_t i = kDebugValueOperandExpressionIndex + 1; - i < dbg_decl_or_val->NumOperands(); ++i) { - new_dbg_value->AddOperand({spv_operand_type_t::SPV_OPERAND_TYPE_ID, - {dbg_decl_or_val->GetSingleWordOperand(i)}}); - } - } - // Avoid inserting the new DebugValue between OpPhi or OpVariable // instructions. Instruction* insert_before = insert_pos->NextNode(); @@ -494,17 +512,19 @@ void DebugInfoManager::AddDebugValue(Instruction* scope_and_line, insert_before = insert_before->NextNode(); } + uint32_t index_id = 0; + if (dbg_decl_or_val->NumOperands() > kDebugValueOperandIndexesIndex) { + index_id = + dbg_decl_or_val->GetSingleWordOperand(kDebugValueOperandIndexesIndex); + } + Instruction* added_dbg_value = - insert_before->InsertBefore(std::move(new_dbg_value)); - added_dbg_value->UpdateDebugInfo(scope_and_line); + AddDebugValueWithIndex(dbg_decl_or_val->GetSingleWordOperand( + kDebugValueOperandLocalVariableIndex), + value_id, 0, index_id, insert_before); + assert(added_dbg_value != nullptr); + added_dbg_value->UpdateDebugInfoFrom(scope_and_line); AnalyzeDebugInst(added_dbg_value); - if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) - context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value); - if (context()->AreAnalysesValid( - IRContext::Analysis::kAnalysisInstrToBlockMapping)) { - auto insert_blk = context()->get_instr_block(insert_before); - context()->set_instr_block(added_dbg_value, insert_blk); - } } } @@ -542,42 +562,140 @@ uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare( return 0; } -void DebugInfoManager::AnalyzeDebugInst(Instruction* dbg_inst) { - if (!dbg_inst->IsOpenCL100DebugInstr()) return; +bool DebugInfoManager::IsDebugDeclare(Instruction* instr) { + if (!instr->IsOpenCL100DebugInstr()) return false; + return instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare || + GetVariableIdOfDebugValueUsedForDeclare(instr) != 0; +} + +void DebugInfoManager::ReplaceAllUsesInDebugScopeWithPredicate( + uint32_t before, uint32_t after, + const std::function<bool(Instruction*)>& predicate) { + auto scope_id_to_users_itr = scope_id_to_users_.find(before); + if (scope_id_to_users_itr != scope_id_to_users_.end()) { + for (Instruction* inst : scope_id_to_users_itr->second) { + if (predicate(inst)) inst->UpdateLexicalScope(after); + } + scope_id_to_users_[after] = scope_id_to_users_itr->second; + scope_id_to_users_.erase(scope_id_to_users_itr); + } + auto inlinedat_id_to_users_itr = inlinedat_id_to_users_.find(before); + if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) { + for (Instruction* inst : inlinedat_id_to_users_itr->second) { + if (predicate(inst)) inst->UpdateDebugInlinedAt(after); + } + inlinedat_id_to_users_[after] = inlinedat_id_to_users_itr->second; + inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr); + } +} + +void DebugInfoManager::ClearDebugScopeAndInlinedAtUses(Instruction* inst) { + auto scope_id_to_users_itr = scope_id_to_users_.find(inst->result_id()); + if (scope_id_to_users_itr != scope_id_to_users_.end()) { + scope_id_to_users_.erase(scope_id_to_users_itr); + } + auto inlinedat_id_to_users_itr = + inlinedat_id_to_users_.find(inst->result_id()); + if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) { + inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr); + } +} + +void DebugInfoManager::AnalyzeDebugInst(Instruction* inst) { + if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) { + auto& users = scope_id_to_users_[inst->GetDebugScope().GetLexicalScope()]; + users.insert(inst); + } + if (inst->GetDebugInlinedAt() != kNoInlinedAt) { + auto& users = inlinedat_id_to_users_[inst->GetDebugInlinedAt()]; + users.insert(inst); + } + + if (!inst->IsOpenCL100DebugInstr()) return; - RegisterDbgInst(dbg_inst); + RegisterDbgInst(inst); - if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) { - assert(GetDebugFunction(dbg_inst->GetSingleWordOperand( + if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) { + assert(GetDebugFunction(inst->GetSingleWordOperand( kDebugFunctionOperandFunctionIndex)) == nullptr && "Two DebugFunction instruction exists for a single OpFunction."); - RegisterDbgFunction(dbg_inst); + RegisterDbgFunction(inst); } if (deref_operation_ == nullptr && - dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation && - dbg_inst->GetSingleWordOperand(kDebugOperationOperandOpCodeIndex) == + inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation && + inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex) == OpenCLDebugInfo100Deref) { - deref_operation_ = dbg_inst; + deref_operation_ = inst; } if (debug_info_none_inst_ == nullptr && - dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) { - debug_info_none_inst_ = dbg_inst; + inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) { + debug_info_none_inst_ = inst; } - if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(dbg_inst)) { - empty_debug_expr_inst_ = dbg_inst; + if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(inst)) { + empty_debug_expr_inst_ = inst; } - if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) { + if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) { uint32_t var_id = - dbg_inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); - RegisterDbgDeclare(var_id, dbg_inst); + inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); + RegisterDbgDeclare(var_id, inst); } - if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(dbg_inst)) { - RegisterDbgDeclare(var_id, dbg_inst); + if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(inst)) { + RegisterDbgDeclare(var_id, inst); + } +} + +void DebugInfoManager::ConvertDebugGlobalToLocalVariable( + Instruction* dbg_global_var, Instruction* local_var) { + if (dbg_global_var->GetOpenCL100DebugOpcode() != + OpenCLDebugInfo100DebugGlobalVariable) { + return; + } + assert(local_var->opcode() == SpvOpVariable || + local_var->opcode() == SpvOpFunctionParameter); + + // Convert |dbg_global_var| to DebugLocalVariable + dbg_global_var->SetInOperand(kExtInstInstructionInIdx, + {OpenCLDebugInfo100DebugLocalVariable}); + auto flags = dbg_global_var->GetSingleWordOperand( + kDebugGlobalVariableOperandFlagsIndex); + for (uint32_t i = dbg_global_var->NumInOperands() - 1; + i >= kDebugLocalVariableOperandFlagsIndex; --i) { + dbg_global_var->RemoveOperand(i); + } + dbg_global_var->SetOperand(kDebugLocalVariableOperandFlagsIndex, {flags}); + context()->ForgetUses(dbg_global_var); + context()->AnalyzeUses(dbg_global_var); + + // Create a DebugDeclare + std::unique_ptr<Instruction> new_dbg_decl(new Instruction( + context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(), + context()->TakeNextId(), + { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {context() + ->get_feature_mgr() + ->GetExtInstImportId_OpenCL100DebugInfo()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {static_cast<uint32_t>(OpenCLDebugInfo100DebugDeclare)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {dbg_global_var->result_id()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {local_var->result_id()}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {GetEmptyDebugExpression()->result_id()}}, + })); + auto* added_dbg_decl = + local_var->NextNode()->InsertBefore(std::move(new_dbg_decl)); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_decl); + if (context()->AreAnalysesValid( + IRContext::Analysis::kAnalysisInstrToBlockMapping)) { + auto insert_blk = context()->get_instr_block(local_var); + context()->set_instr_block(added_dbg_decl, insert_blk); } } @@ -607,6 +725,17 @@ void DebugInfoManager::AnalyzeDebugInsts(Module& module) { } void DebugInfoManager::ClearDebugInfo(Instruction* instr) { + auto scope_id_to_users_itr = + scope_id_to_users_.find(instr->GetDebugScope().GetLexicalScope()); + if (scope_id_to_users_itr != scope_id_to_users_.end()) { + scope_id_to_users_itr->second.erase(instr); + } + auto inlinedat_id_to_users_itr = + inlinedat_id_to_users_.find(instr->GetDebugInlinedAt()); + if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) { + inlinedat_id_to_users_itr->second.erase(instr); + } + if (instr == nullptr || !instr->IsOpenCL100DebugInstr()) { return; } @@ -638,7 +767,8 @@ void DebugInfoManager::ClearDebugInfo(Instruction* instr) { dbg_instr_itr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation && dbg_instr_itr->GetSingleWordOperand( - kDebugOperationOperandOpCodeIndex) == OpenCLDebugInfo100Deref) { + kDebugOperationOperandOperationIndex) == + OpenCLDebugInfo100Deref) { deref_operation_ = &*dbg_instr_itr; break; } diff --git a/source/opt/debug_info_manager.h b/source/opt/debug_info_manager.h index ff91b5c5..6e7373bc 100644 --- a/source/opt/debug_info_manager.h +++ b/source/opt/debug_info_manager.h @@ -133,17 +133,27 @@ class DebugInfoManager { uint32_t BuildDebugInlinedAtChain(uint32_t callee_inlined_at, DebugInlinedAtContext* inlined_at_ctx); - // Return true if |variable_id| has DebugDeclare or DebugVal. - bool IsDebugDeclared(uint32_t variable_id); + // Returns true if there is a debug declaration instruction whose + // 'Local Variable' operand is |variable_id|. + bool IsVariableDebugDeclared(uint32_t variable_id); - // Kill all DebugDeclares for |variable_id| + // Kills all debug declaration instructions with Deref whose 'Local Variable' + // operand is |variable_id|. void KillDebugDeclares(uint32_t variable_id); // Generates a DebugValue instruction with value |value_id| for every local // variable that is in the scope of |scope_and_line| and whose memory is // |variable_id| and inserts it after the instruction |insert_pos|. - void AddDebugValue(Instruction* scope_and_line, uint32_t variable_id, - uint32_t value_id, Instruction* insert_pos); + void AddDebugValueIfVarDeclIsVisible(Instruction* scope_and_line, + uint32_t variable_id, uint32_t value_id, + Instruction* insert_pos); + + // Generates a DebugValue instruction with |dbg_local_var_id|, |value_id|, + // |expr_id|, |index_id| operands and inserts it before |insert_before|. + Instruction* AddDebugValueWithIndex(uint32_t dbg_local_var_id, + uint32_t value_id, uint32_t expr_id, + uint32_t index_id, + Instruction* insert_before); // Erases |instr| from data structures of this class. void ClearDebugInfo(Instruction* instr); @@ -153,6 +163,24 @@ class DebugInfoManager { // Function storage class. Otherwise, returns 0. uint32_t GetVariableIdOfDebugValueUsedForDeclare(Instruction* inst); + // Converts DebugGlobalVariable |dbg_global_var| to a DebugLocalVariable and + // creates a DebugDeclare mapping the new DebugLocalVariable to |local_var|. + void ConvertDebugGlobalToLocalVariable(Instruction* dbg_global_var, + Instruction* local_var); + + // Returns true if |instr| is a debug declaration instruction. + bool IsDebugDeclare(Instruction* instr); + + // Replace all uses of |before| id that is an operand of a DebugScope with + // |after| id if those uses (instruction) return true for |predicate|. + void ReplaceAllUsesInDebugScopeWithPredicate( + uint32_t before, uint32_t after, + const std::function<bool(Instruction*)>& predicate); + + // Removes uses of DebugScope |inst| from |scope_id_to_users_| or uses of + // DebugInlinedAt |inst| from |inlinedat_id_to_users_|. + void ClearDebugScopeAndInlinedAtUses(Instruction* inst); + private: IRContext* context() { return context_; } @@ -210,6 +238,14 @@ class DebugInfoManager { std::unordered_map<uint32_t, std::unordered_set<Instruction*>> var_id_to_dbg_decl_; + // Mapping from DebugScope ids to users. + std::unordered_map<uint32_t, std::unordered_set<Instruction*>> + scope_id_to_users_; + + // Mapping from DebugInlinedAt ids to users. + std::unordered_map<uint32_t, std::unordered_set<Instruction*>> + inlinedat_id_to_users_; + // DebugOperation whose OpCode is OpenCLDebugInfo100Deref. Instruction* deref_operation_; diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp index 7e61506b..55287f44 100644 --- a/source/opt/dominator_tree.cpp +++ b/source/opt/dominator_tree.cpp @@ -22,8 +22,8 @@ // Calculates the dominator or postdominator tree for a given function. // 1 - Compute the successors and predecessors for each BasicBlock. We add a -// dummy node for the start node or for postdominators the exit. This node will -// point to all entry or all exit nodes. +// placeholder node for the start node or for postdominators the exit. This node +// will point to all entry or all exit nodes. // 2 - Using the CFA::DepthFirstTraversal get a depth first postordered list of // all BasicBlocks. Using the successors (or for postdominator, predecessors) // calculated in step 1 to traverse the tree. @@ -107,8 +107,9 @@ class BasicBlockSuccessorHelper { public: // For compliance with the dominance tree computation, entry nodes are - // connected to a single dummy node. - BasicBlockSuccessorHelper(Function& func, const BasicBlock* dummy_start_node, + // connected to a single placeholder node. + BasicBlockSuccessorHelper(Function& func, + const BasicBlock* placeholder_start_node, bool post); // CFA::CalculateDominators requires std::vector<BasicBlock*>. @@ -139,23 +140,24 @@ class BasicBlockSuccessorHelper { // Build the successors and predecessors map for each basic blocks |f|. // If |invert_graph_| is true, all edges are reversed (successors becomes // predecessors and vice versa). - // For convenience, the start of the graph is |dummy_start_node|. + // For convenience, the start of the graph is |placeholder_start_node|. // The dominator tree construction requires a unique entry node, which cannot - // be guaranteed for the postdominator graph. The |dummy_start_node| BB is - // here to gather all entry nodes. - void CreateSuccessorMap(Function& f, const BasicBlock* dummy_start_node); + // be guaranteed for the postdominator graph. The |placeholder_start_node| BB + // is here to gather all entry nodes. + void CreateSuccessorMap(Function& f, + const BasicBlock* placeholder_start_node); }; template <typename BBType> BasicBlockSuccessorHelper<BBType>::BasicBlockSuccessorHelper( - Function& func, const BasicBlock* dummy_start_node, bool invert) + Function& func, const BasicBlock* placeholder_start_node, bool invert) : invert_graph_(invert) { - CreateSuccessorMap(func, dummy_start_node); + CreateSuccessorMap(func, placeholder_start_node); } template <typename BBType> void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap( - Function& f, const BasicBlock* dummy_start_node) { + Function& f, const BasicBlock* placeholder_start_node) { std::map<uint32_t, BasicBlock*> id_to_BB_map; auto GetSuccessorBasicBlock = [&f, &id_to_BB_map](uint32_t successor_id) { BasicBlock*& Succ = id_to_BB_map[successor_id]; @@ -173,11 +175,10 @@ void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap( if (invert_graph_) { // For the post dominator tree, we see the inverted graph. // successors_ in the inverted graph are the predecessors in the CFG. - // The tree construction requires 1 entry point, so we add a dummy node - // that is connected to all function exiting basic blocks. - // An exiting basic block is a block with an OpKill, OpUnreachable, - // OpReturn, OpReturnValue, or OpTerminateInvocation as terminator - // instruction. + // The tree construction requires 1 entry point, so we add a placeholder + // node that is connected to all function exiting basic blocks. An exiting + // basic block is a block with an OpKill, OpUnreachable, OpReturn, + // OpReturnValue, or OpTerminateInvocation as terminator instruction. for (BasicBlock& bb : f) { if (bb.hasSuccessor()) { BasicBlockListTy& pred_list = predecessors_[&bb]; @@ -192,14 +193,15 @@ void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap( pred_list.push_back(succ); }); } else { - successors_[dummy_start_node].push_back(&bb); - predecessors_[&bb].push_back(const_cast<BasicBlock*>(dummy_start_node)); + successors_[placeholder_start_node].push_back(&bb); + predecessors_[&bb].push_back( + const_cast<BasicBlock*>(placeholder_start_node)); } } } else { - successors_[dummy_start_node].push_back(f.entry().get()); + successors_[placeholder_start_node].push_back(f.entry().get()); predecessors_[f.entry().get()].push_back( - const_cast<BasicBlock*>(dummy_start_node)); + const_cast<BasicBlock*>(placeholder_start_node)); for (BasicBlock& bb : f) { BasicBlockListTy& succ_list = successors_[&bb]; @@ -288,7 +290,7 @@ DominatorTreeNode* DominatorTree::GetOrInsertNode(BasicBlock* bb) { } void DominatorTree::GetDominatorEdges( - const Function* f, const BasicBlock* dummy_start_node, + const Function* f, const BasicBlock* placeholder_start_node, std::vector<std::pair<BasicBlock*, BasicBlock*>>* edges) { // Each time the depth first traversal calls the postorder callback // std::function we push that node into the postorder vector to create our @@ -302,7 +304,7 @@ void DominatorTree::GetDominatorEdges( // BB are derived from F, so we need to const cast it at some point // no modification is made on F. BasicBlockSuccessorHelper<BasicBlock> helper{ - *const_cast<Function*>(f), dummy_start_node, postdominator_}; + *const_cast<Function*>(f), placeholder_start_node, postdominator_}; // The successor function tells DepthFirstTraversal how to move to successive // nodes by providing an interface to get a list of successor nodes from any @@ -316,7 +318,7 @@ void DominatorTree::GetDominatorEdges( // If we're building a post dominator tree we traverse the tree in reverse // using the predecessor function in place of the successor function and vice // versa. - DepthFirstSearchPostOrder(dummy_start_node, successor_functor, + DepthFirstSearchPostOrder(placeholder_start_node, successor_functor, postorder_function); *edges = CFA<BasicBlock>::CalculateDominators(postorder, predecessor_functor); } @@ -329,12 +331,12 @@ void DominatorTree::InitializeTree(const CFG& cfg, const Function* f) { return; } - const BasicBlock* dummy_start_node = + const BasicBlock* placeholder_start_node = postdominator_ ? cfg.pseudo_exit_block() : cfg.pseudo_entry_block(); // Get the immediate dominator for each node. std::vector<std::pair<BasicBlock*, BasicBlock*>> edges; - GetDominatorEdges(f, dummy_start_node, &edges); + GetDominatorEdges(f, placeholder_start_node, &edges); // Transform the vector<pair> into the tree structure which we can use to // efficiently query dominance. @@ -380,7 +382,7 @@ void DominatorTree::DumpTreeAsDot(std::ostream& out_stream) const { } // Print the arrow from the parent to this node. Entry nodes will not have - // parents so draw them as children from the dummy node. + // parents so draw them as children from the placeholder node. if (node->parent_) { out_stream << node->parent_->bb_->id() << " -> " << node->bb_->id() << ";\n"; diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp index 8a389593..6b5234bb 100644 --- a/source/opt/eliminate_dead_functions_util.cpp +++ b/source/opt/eliminate_dead_functions_util.cpp @@ -21,9 +21,35 @@ namespace eliminatedeadfunctionsutil { Module::iterator EliminateFunction(IRContext* context, Module::iterator* func_iter) { + bool first_func = *func_iter == context->module()->begin(); + bool seen_func_end = false; (*func_iter) - ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); }, - true); + ->ForEachInst( + [context, first_func, func_iter, &seen_func_end](Instruction* inst) { + if (inst->opcode() == SpvOpFunctionEnd) { + seen_func_end = true; + } + // Move non-semantic instructions to the previous function or + // global values if this is the first function. + if (seen_func_end && inst->opcode() == SpvOpExtInst) { + assert(inst->IsNonSemanticInstruction()); + std::unique_ptr<Instruction> clone(inst->Clone(context)); + context->ForgetUses(inst); + context->AnalyzeDefUse(clone.get()); + if (first_func) { + context->AddGlobalValue(std::move(clone)); + } else { + auto prev_func_iter = *func_iter; + --prev_func_iter; + prev_func_iter->AddNonSemanticInstruction(std::move(clone)); + } + inst->ToNop(); + } else { + context->KillNonSemanticInfo(inst); + context->KillInst(inst); + } + }, + true, true); return func_iter->Erase(); } diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp index 1c8cdc89..010eec9c 100644 --- a/source/opt/folding_rules.cpp +++ b/source/opt/folding_rules.cpp @@ -1467,7 +1467,7 @@ FoldingRule CompositeConstructFeedingExtract() { type_mgr->GetType(element_def->type_id())->AsVector(); if (element_type) { uint32_t vector_size = element_type->element_count(); - if (vector_size < element_index) { + if (vector_size <= element_index) { // The element we want comes after this vector. element_index -= vector_size; } else { diff --git a/source/opt/function.cpp b/source/opt/function.cpp index 320f8cab..52054eae 100644 --- a/source/opt/function.cpp +++ b/source/opt/function.cpp @@ -47,31 +47,40 @@ Function* Function::Clone(IRContext* ctx) const { } clone->SetFunctionEnd(std::unique_ptr<Instruction>(EndInst()->Clone(ctx))); + + clone->non_semantic_.reserve(non_semantic_.size()); + for (auto& non_semantic : non_semantic_) { + clone->AddNonSemanticInstruction( + std::unique_ptr<Instruction>(non_semantic->Clone(ctx))); + } return clone; } void Function::ForEachInst(const std::function<void(Instruction*)>& f, - bool run_on_debug_line_insts) { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) { WhileEachInst( [&f](Instruction* inst) { f(inst); return true; }, - run_on_debug_line_insts); + run_on_debug_line_insts, run_on_non_semantic_insts); } void Function::ForEachInst(const std::function<void(const Instruction*)>& f, - bool run_on_debug_line_insts) const { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) const { WhileEachInst( [&f](const Instruction* inst) { f(inst); return true; }, - run_on_debug_line_insts); + run_on_debug_line_insts, run_on_non_semantic_insts); } bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f, - bool run_on_debug_line_insts) { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) { if (def_inst_) { if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) { return false; @@ -99,13 +108,26 @@ bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f, } } - if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts); + if (end_inst_) { + if (!end_inst_->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + + if (run_on_non_semantic_insts) { + for (auto& non_semantic : non_semantic_) { + if (!non_semantic->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + } return true; } bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f, - bool run_on_debug_line_insts) const { + bool run_on_debug_line_insts, + bool run_on_non_semantic_insts) const { if (def_inst_) { if (!static_cast<const Instruction*>(def_inst_.get()) ->WhileEachInst(f, run_on_debug_line_insts)) { @@ -133,9 +155,21 @@ bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f, } } - if (end_inst_) - return static_cast<const Instruction*>(end_inst_.get()) - ->WhileEachInst(f, run_on_debug_line_insts); + if (end_inst_) { + if (!static_cast<const Instruction*>(end_inst_.get()) + ->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + + if (run_on_non_semantic_insts) { + for (auto& non_semantic : non_semantic_) { + if (!static_cast<const Instruction*>(non_semantic.get()) + ->WhileEachInst(f, run_on_debug_line_insts)) { + return false; + } + } + } return true; } @@ -193,6 +227,18 @@ BasicBlock* Function::InsertBasicBlockBefore( return nullptr; } +bool Function::HasEarlyReturn() const { + auto post_dominator_analysis = + blocks_.front()->GetLabel()->context()->GetPostDominatorAnalysis(this); + for (auto& block : blocks_) { + if (spvOpcodeIsReturn(block->tail()->opcode()) && + !post_dominator_analysis->Dominates(block.get(), entry().get())) { + return true; + } + } + return false; +} + bool Function::IsRecursive() const { IRContext* ctx = blocks_.front()->GetLabel()->context(); IRContext::ProcessFunction mark_visited = [this](Function* fp) { diff --git a/source/opt/function.h b/source/opt/function.h index f5035f08..b7c17a6b 100644 --- a/source/opt/function.h +++ b/source/opt/function.h @@ -79,6 +79,11 @@ class Function { // Saves the given function end instruction. inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst); + // Add a non-semantic instruction that succeeds this function in the module. + // These instructions are maintained in the order they are added. + inline void AddNonSemanticInstruction( + std::unique_ptr<Instruction> non_semantic); + // Returns the given function end instruction. inline Instruction* EndInst() { return end_inst_.get(); } inline const Instruction* EndInst() const { return end_inst_.get(); } @@ -115,19 +120,24 @@ class Function { } // Runs the given function |f| on instructions in this function, in order, - // and optionally on debug line instructions that might precede them. + // and optionally on debug line instructions that might precede them and + // non-semantic instructions that succceed the function. void ForEachInst(const std::function<void(Instruction*)>& f, - bool run_on_debug_line_insts = false); + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false); void ForEachInst(const std::function<void(const Instruction*)>& f, - bool run_on_debug_line_insts = false) const; + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false) const; // Runs the given function |f| on instructions in this function, in order, - // and optionally on debug line instructions that might precede them. - // If |f| returns false, iteration is terminated and this function returns - // false. + // and optionally on debug line instructions that might precede them and + // non-semantic instructions that succeed the function. If |f| returns + // false, iteration is terminated and this function returns false. bool WhileEachInst(const std::function<bool(Instruction*)>& f, - bool run_on_debug_line_insts = false); + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false); bool WhileEachInst(const std::function<bool(const Instruction*)>& f, - bool run_on_debug_line_insts = false) const; + bool run_on_debug_line_insts = false, + bool run_on_non_semantic_insts = false) const; // Runs the given function |f| on each parameter instruction in this function, // in order, and optionally on debug line instructions that might precede @@ -148,7 +158,10 @@ class Function { BasicBlock* InsertBasicBlockBefore(std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position); - // Return true if the function calls itself either directly or indirectly. + // Returns true if the function has a return block other than the exit block. + bool HasEarlyReturn() const; + + // Returns true if the function calls itself either directly or indirectly. bool IsRecursive() const; // Pretty-prints all the basic blocks in this function into a std::string. @@ -172,6 +185,8 @@ class Function { std::vector<std::unique_ptr<BasicBlock>> blocks_; // The OpFunctionEnd instruction. std::unique_ptr<Instruction> end_inst_; + // Non-semantic instructions succeeded by this function. + std::vector<std::unique_ptr<Instruction>> non_semantic_; }; // Pretty-prints |func| to |str|. Returns |str|. @@ -235,6 +250,11 @@ inline void Function::SetFunctionEnd(std::unique_ptr<Instruction> end_inst) { end_inst_ = std::move(end_inst); } +inline void Function::AddNonSemanticInstruction( + std::unique_ptr<Instruction> non_semantic) { + non_semantic_.emplace_back(std::move(non_semantic)); +} + } // namespace opt } // namespace spvtools diff --git a/source/opt/graphics_robust_access_pass.cpp b/source/opt/graphics_robust_access_pass.cpp index db14020d..46483e48 100644 --- a/source/opt/graphics_robust_access_pass.cpp +++ b/source/opt/graphics_robust_access_pass.cpp @@ -320,9 +320,13 @@ void GraphicsRobustAccessPass::ClampIndicesForAccessChain( maxval_width *= 2; } // Determine the type for |maxval|. + uint32_t next_id = context()->module()->IdBound(); analysis::Integer signed_type_for_query(maxval_width, true); auto* maxval_type = type_mgr->GetRegisteredType(&signed_type_for_query)->AsInteger(); + if (next_id != context()->module()->IdBound()) { + module_status_.modified = true; + } // Access chain indices are treated as signed, so limit the maximum value // of the index so it will always be positive for a signed clamp operation. maxval = std::min(maxval, ((uint64_t(1) << (maxval_width - 1)) - 1)); diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp index ef94d0d6..6021a7c5 100644 --- a/source/opt/inline_pass.cpp +++ b/source/opt/inline_pass.cpp @@ -619,6 +619,14 @@ bool InlinePass::GenInlineCode( assert(resId != 0); AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr, call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope()); + } else { + // Even though it is very unlikely, it is possible that the result id of + // the void-function call is used, so we need to generate an instruction + // with that result id. + std::unique_ptr<Instruction> undef_inst( + new Instruction(context(), SpvOpUndef, call_inst_itr->type_id(), + call_inst_itr->result_id(), {})); + context()->AddGlobalValue(std::move(undef_inst)); } // Move instructions of original caller block after call instruction. diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp index 4587343f..3691414c 100644 --- a/source/opt/inst_bindless_check_pass.cpp +++ b/source/opt/inst_bindless_check_pass.cpp @@ -26,13 +26,19 @@ static const int kSpvImageSampledImageIdInIdx = 0; static const int kSpvLoadPtrIdInIdx = 0; static const int kSpvAccessChainBaseIdInIdx = 0; static const int kSpvAccessChainIndex0IdInIdx = 1; -static const int kSpvTypePointerTypeIdInIdx = 1; static const int kSpvTypeArrayLengthIdInIdx = 1; static const int kSpvConstantValueInIdx = 0; static const int kSpvVariableStorageClassInIdx = 0; } // anonymous namespace +// Avoid unused variable warning/error on Linux +#ifndef NDEBUG +#define USE_ASSERT(x) assert(x) +#else +#define USE_ASSERT(x) ((void)(x)) +#endif + namespace spvtools { namespace opt { @@ -48,14 +54,25 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength( 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); + // If desc index checking is not enabled, we know the offset of initialization + // entries is 1, so we can avoid loading this value and just add 1 to the + // descriptor set. + if (!desc_idx_enabled_) { + uint32_t desc_set_idx_id = + builder->GetUintConstantId(var2desc_set_[var_id] + 1); + return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id}, + builder); + } else { + uint32_t desc_set_base_id = + builder->GetUintConstantId(kDebugInputBindlessInitOffset); + uint32_t desc_set_idx_id = + builder->GetUintConstantId(var2desc_set_[var_id]); + return GenDebugDirectRead( + {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id}, + builder); + } } uint32_t InstBindlessCheckPass::CloneOriginalReference( @@ -156,13 +173,9 @@ uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) { return 0; } -Instruction* InstBindlessCheckPass::GetDescriptorTypeInst( - Instruction* var_inst) { - 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); - return get_def_use_mgr()->GetDef(desc_type_id); +Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) { + uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst); + return get_def_use_mgr()->GetDef(pte_ty_id); } bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, @@ -187,7 +200,7 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, return false; break; } - Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst); + Instruction* desc_type_inst = GetPointeeTypeInst(var_inst); switch (desc_type_inst->opcode()) { case SpvOpTypeArray: case SpvOpTypeRuntimeArray: @@ -195,11 +208,11 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, // do not want to instrument loads of descriptors here which are part of // an image-based reference. if (ptr_inst->NumInOperands() < 3) return false; - ref->index_id = + ref->desc_idx_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); break; default: - ref->index_id = 0; + ref->desc_idx_id = 0; break; } return true; @@ -229,14 +242,14 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, ref->ptr_id = desc_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->desc_idx_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 = + ref->desc_idx_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx); ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx); Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id); @@ -251,9 +264,150 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst, return true; } +uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id, + uint32_t stride_deco) { + uint32_t stride = 0xdeadbeef; + bool found = !get_decoration_mgr()->WhileEachDecoration( + ty_id, stride_deco, [&stride](const Instruction& deco_inst) { + stride = deco_inst.GetSingleWordInOperand(2u); + return false; + }); + USE_ASSERT(found && "stride not found"); + return stride; +} + +uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + const analysis::Type* sz_ty = type_mgr->GetType(ty_id); + if (sz_ty->kind() == analysis::Type::kPointer) { + // Assuming PhysicalStorageBuffer pointer + return 8; + } + uint32_t size = 1; + if (sz_ty->kind() == analysis::Type::kMatrix) { + const analysis::Matrix* m_ty = sz_ty->AsMatrix(); + size = m_ty->element_count() * size; + uint32_t stride = FindStride(ty_id, SpvDecorationMatrixStride); + if (stride != 0) return size * stride; + sz_ty = m_ty->element_type(); + } + if (sz_ty->kind() == analysis::Type::kVector) { + const analysis::Vector* v_ty = sz_ty->AsVector(); + size = v_ty->element_count() * size; + sz_ty = v_ty->element_type(); + } + switch (sz_ty->kind()) { + case analysis::Type::kFloat: { + const analysis::Float* f_ty = sz_ty->AsFloat(); + size *= f_ty->width(); + } break; + case analysis::Type::kInteger: { + const analysis::Integer* i_ty = sz_ty->AsInteger(); + size *= i_ty->width(); + } break; + default: { assert(false && "unexpected type"); } break; + } + size /= 8; + return size; +} + +uint32_t InstBindlessCheckPass::GenLastByteIdx(ref_analysis* ref, + InstructionBuilder* builder) { + // Find outermost buffer type and its access chain index + Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id); + Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst); + uint32_t buff_ty_id; + uint32_t ac_in_idx = 1; + switch (desc_ty_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0); + ++ac_in_idx; + break; + default: + assert(desc_ty_inst->opcode() == SpvOpTypeStruct && + "unexpected descriptor type"); + buff_ty_id = desc_ty_inst->result_id(); + break; + } + // Process remaining access chain indices + Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id); + uint32_t curr_ty_id = buff_ty_id; + uint32_t sum_id = 0; + while (ac_in_idx < ac_inst->NumInOperands()) { + uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx); + Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id); + Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id); + uint32_t curr_offset_id = 0; + switch (curr_ty_inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeMatrix: { + // Get array/matrix stride and multiply by current index + uint32_t stride_deco = (curr_ty_inst->opcode() == SpvOpTypeMatrix) + ? SpvDecorationMatrixStride + : SpvDecorationArrayStride; + uint32_t arr_stride = FindStride(curr_ty_id, stride_deco); + uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride); + Instruction* curr_offset_inst = builder->AddBinaryOp( + GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_id); + curr_offset_id = curr_offset_inst->result_id(); + // Get element type for next step + curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0); + } break; + case SpvOpTypeVector: { + // Stride is size of component type + uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u); + uint32_t vec_stride = ByteSize(comp_ty_id); + uint32_t vec_stride_id = builder->GetUintConstantId(vec_stride); + Instruction* curr_offset_inst = builder->AddBinaryOp( + GetUintId(), SpvOpIMul, vec_stride_id, curr_idx_id); + curr_offset_id = curr_offset_inst->result_id(); + // Get element type for next step + curr_ty_id = comp_ty_id; + } break; + case SpvOpTypeStruct: { + // Get buffer byte offset for the referenced member + assert(curr_idx_inst->opcode() == SpvOpConstant && + "unexpected struct index"); + uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0); + uint32_t member_offset = 0xdeadbeef; + bool found = !get_decoration_mgr()->WhileEachDecoration( + curr_ty_id, SpvDecorationOffset, + [&member_idx, &member_offset](const Instruction& deco_inst) { + if (deco_inst.GetSingleWordInOperand(1u) != member_idx) + return true; + member_offset = deco_inst.GetSingleWordInOperand(3u); + return false; + }); + USE_ASSERT(found && "member offset not found"); + curr_offset_id = builder->GetUintConstantId(member_offset); + // Get element type for next step + curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx); + } break; + default: { assert(false && "unexpected non-composite type"); } break; + } + if (sum_id == 0) + sum_id = curr_offset_id; + else { + Instruction* sum_inst = + builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id); + sum_id = sum_inst->result_id(); + } + ++ac_in_idx; + } + // Add in offset of last byte of referenced object + uint32_t bsize = ByteSize(curr_ty_id); + uint32_t last = bsize - 1; + uint32_t last_id = builder->GetUintConstantId(last); + Instruction* sum_inst = + builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id); + return sum_inst->result_id(); +} + void InstBindlessCheckPass::GenCheckCode( - uint32_t check_id, uint32_t error_id, uint32_t length_id, - uint32_t stage_idx, ref_analysis* ref, + uint32_t check_id, uint32_t error_id, uint32_t offset_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( @@ -279,9 +433,19 @@ void InstBindlessCheckPass::GenCheckCode( // Gen invalid block new_blk_ptr.reset(new BasicBlock(std::move(invalid_label))); builder.SetInsertPoint(&*new_blk_ptr); - 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); + uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder); + if (offset_id != 0) + GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx, + {error_id, u_index_id, offset_id, length_id}, &builder); + else if (buffer_bounds_enabled_) + // So all error modes will use same debug stream write function + GenDebugStreamWrite( + uid2offset_[ref->ref_inst->unique_id()], stage_idx, + {error_id, u_index_id, length_id, builder.GetUintConstantId(0)}, + &builder); + else + 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 @@ -305,7 +469,7 @@ void InstBindlessCheckPass::GenCheckCode( context()->KillInst(ref->ref_inst); } -void InstBindlessCheckPass::GenBoundsCheckCode( +void InstBindlessCheckPass::GenDescIdxCheckCode( BasicBlock::iterator ref_inst_itr, UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { @@ -318,19 +482,19 @@ void InstBindlessCheckPass::GenBoundsCheckCode( // 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); - Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst); + Instruction* desc_type_inst = GetPointeeTypeInst(var_inst); 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* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_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_ || + } else if (!desc_idx_enabled_ || desc_type_inst->opcode() != SpvOpTypeRuntimeArray) { return; } @@ -352,9 +516,9 @@ void InstBindlessCheckPass::GenBoundsCheckCode( // 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, + Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, + ref.desc_idx_id, length_id); + GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref, new_blocks); // Move original block's remaining code into remainder/merge block and add // to new blocks @@ -362,13 +526,30 @@ void InstBindlessCheckPass::GenBoundsCheckCode( MovePostludeCode(ref_block_itr, back_blk_ptr); } -void InstBindlessCheckPass::GenInitCheckCode( +void InstBindlessCheckPass::GenDescInitCheckCode( 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; + // Determine if we can only do initialization check + bool init_check = false; + if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) { + init_check = true; + } else { + // For now, only do bounds check for non-aggregate types. Otherwise + // just do descriptor initialization check. + // TODO(greg-lunarg): Do bounds check for aggregate loads and stores + Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id); + Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst); + uint32_t pte_type_op = pte_type_inst->opcode(); + if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray || + pte_type_op == SpvOpTypeStruct) + init_check = true; + } + // If initialization check and not enabled, return + if (init_check && !desc_init_enabled_) 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); @@ -376,19 +557,25 @@ void InstBindlessCheckPass::GenInitCheckCode( 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 + // If initialization check, use reference value of zero. + // Else use the index of the last byte referenced. + uint32_t ref_id = init_check ? builder.GetUintConstantId(0u) + : GenLastByteIdx(&ref, &builder); + // Read initialization/bounds 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 + if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u); + uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder); + // Generate runtime initialization/bounds 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); + Instruction* ult_inst = + builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id); + uint32_t error = + init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB; + uint32_t error_id = builder.GetUintConstantId(error); + GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id, + init_check ? builder.GetUintConstantId(0u) : init_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(); @@ -400,7 +587,7 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() { InitializeInstrument(); // If runtime array length support enabled, create variable mappings. Length // support is always enabled if descriptor init check is enabled. - if (input_length_enabled_) + if (desc_idx_enabled_ || buffer_bounds_enabled_) for (auto& anno : get_module()->annotations()) if (anno.opcode() == SpvOpDecorate) { if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) @@ -418,19 +605,19 @@ Pass::Status InstBindlessCheckPass::ProcessImpl() { [this](BasicBlock::iterator ref_inst_itr, UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { - return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx, - new_blocks); + return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); }; bool modified = InstProcessEntryPointCallTree(pfn); - if (input_init_enabled_) { + if (desc_init_enabled_ || buffer_bounds_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); + return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx, + new_blocks); }; modified |= InstProcessEntryPointCallTree(pfn); } diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h index 9335fa5b..50dfd95d 100644 --- a/source/opt/inst_bindless_check_pass.h +++ b/source/opt/inst_bindless_check_pass.h @@ -28,12 +28,24 @@ namespace opt { // external design may change as the layer evolves. class InstBindlessCheckPass : public InstrumentPass { public: - // Preferred Interface + // Old interface to support testing pre-buffer-overrun capability 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) {} + bool desc_idx_enable, bool desc_init_enable) + : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, false), + desc_idx_enabled_(desc_idx_enable), + desc_init_enabled_(desc_init_enable), + buffer_bounds_enabled_(false) {} + + // New interface supporting buffer overrun checking + InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, + bool desc_idx_enable, bool desc_init_enable, + bool buffer_bounds_enable) + : InstrumentPass( + desc_set, shader_id, kInstValidationIdBindless, + desc_idx_enable || desc_init_enable || buffer_bounds_enable), + desc_idx_enabled_(desc_idx_enable), + desc_init_enabled_(desc_init_enable), + buffer_bounds_enabled_(buffer_bounds_enable) {} ~InstBindlessCheckPass() override = default; @@ -46,13 +58,11 @@ class InstBindlessCheckPass : public InstrumentPass { // 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 + // descriptors. GenDescIdxCheckCode checks that an index into a descriptor + // array (array of images or buffers) is in-bounds. GenDescInitCheckCode // 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. + // SPV_EXT_descriptor_indexing extension is enabled, and initialized large + // enough to handle the reference, if RobustBufferAccess is disabled. // // The functions are designed to be passed to // InstrumentPass::InstProcessEntryPointCallTree(), which applies the @@ -89,15 +99,15 @@ class InstBindlessCheckPass : public InstrumentPass { // // The Descriptor Array Size is the size of the descriptor array which was // indexed. - 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 GenDescIdxCheckCode( + 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); + void GenDescInitCheckCode( + 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. @@ -118,10 +128,20 @@ class InstBindlessCheckPass : public InstrumentPass { uint32_t load_id; uint32_t ptr_id; uint32_t var_id; - uint32_t index_id; + uint32_t desc_idx_id; Instruction* ref_inst; } ref_analysis; + // Return size of type |ty_id| in bytes. + uint32_t ByteSize(uint32_t ty_id); + + // Return stride of type |ty_id| with decoration |stride_deco|. Return 0 + // if not found + uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco); + + // Generate index of last byte referenced by buffer reference |ref| + uint32_t GenLastByteIdx(ref_analysis* ref, InstructionBuilder* builder); + // Clone original original reference encapsulated by |ref| into |builder|. // This may generate more than one instruction if neccessary. uint32_t CloneOriginalReference(ref_analysis* ref, @@ -131,8 +151,8 @@ class InstBindlessCheckPass : public InstrumentPass { // references through. Else return 0. uint32_t GetImageId(Instruction* inst); - // Get descriptor type inst of variable |var_inst|. - Instruction* GetDescriptorTypeInst(Instruction* var_inst); + // Get pointee type inst of pointer value |ptr_inst|. + Instruction* GetPointeeTypeInst(Instruction* ptr_inst); // Analyze descriptor reference |ref_inst| and save components into |ref|. // Return true if |ref_inst| is a descriptor reference, false otherwise. @@ -145,22 +165,25 @@ class InstBindlessCheckPass : public InstrumentPass { // 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, + void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_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. + // Apply GenDescIdxCheckCode to every instruction in module. Then apply + // GenDescInitCheckCode to every instruction in module. Pass::Status ProcessImpl(); // Enable instrumentation of runtime array length checking - bool input_length_enabled_; + bool desc_idx_enabled_; // Enable instrumentation of descriptor initialization checking - bool input_init_enabled_; + bool desc_init_enabled_; + + // Enable instrumentation of buffer overrun checking + bool buffer_bounds_enabled_; // Mapping from variable to descriptor set std::unordered_map<uint32_t, uint32_t> var2desc_set_; diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index 126848e7..96182613 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp @@ -183,8 +183,9 @@ void Instruction::ToBinaryWithoutAttachedDebugInsts( std::vector<uint32_t>* binary) const { const uint32_t num_words = 1 + NumOperandWords(); binary->push_back((num_words << 16) | static_cast<uint16_t>(opcode_)); - for (const auto& operand : operands_) + for (const auto& operand : operands_) { binary->insert(binary->end(), operand.words.begin(), operand.words.end()); + } } void Instruction::ReplaceOperands(const OperandList& new_operands) { @@ -283,8 +284,7 @@ bool Instruction::IsVulkanStorageImage() const { // Check if the image is sampled. If we do not know for sure that it is, // then assume it is a storage image. - auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); - return s != 1; + return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1; } bool Instruction::IsVulkanSampledImage() const { @@ -318,8 +318,7 @@ bool Instruction::IsVulkanSampledImage() const { // Check if the image is sampled. If we know for sure that it is, // then return true. - auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); - return s == 1; + return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) == 1; } bool Instruction::IsVulkanStorageTexelBuffer() const { @@ -502,16 +501,50 @@ uint32_t Instruction::GetTypeComponent(uint32_t element) const { return subtype; } -Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& i) { - i.get()->InsertBefore(this); - return i.release(); +void Instruction::UpdateLexicalScope(uint32_t scope) { + dbg_scope_.SetLexicalScope(scope); + for (auto& i : dbg_line_insts_) { + i.dbg_scope_.SetLexicalScope(scope); + } + if (!IsDebugLineInst(opcode()) && + context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { + context()->get_debug_info_mgr()->AnalyzeDebugInst(this); + } +} + +void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { + dbg_scope_.SetInlinedAt(new_inlined_at); + for (auto& i : dbg_line_insts_) { + i.dbg_scope_.SetInlinedAt(new_inlined_at); + } + if (!IsDebugLineInst(opcode()) && + context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { + context()->get_debug_info_mgr()->AnalyzeDebugInst(this); + } +} + +void Instruction::UpdateDebugInfoFrom(const Instruction* from) { + if (from == nullptr) return; + clear_dbg_line_insts(); + if (!from->dbg_line_insts().empty()) + dbg_line_insts().push_back(from->dbg_line_insts()[0]); + SetDebugScope(from->GetDebugScope()); + if (!IsDebugLineInst(opcode()) && + context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { + context()->get_debug_info_mgr()->AnalyzeDebugInst(this); + } +} + +Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& inst) { + inst.get()->InsertBefore(this); + return inst.release(); } Instruction* Instruction::InsertBefore( std::vector<std::unique_ptr<Instruction>>&& list) { Instruction* first_node = list.front().get(); - for (auto& i : list) { - i.release()->InsertBefore(this); + for (auto& inst : list) { + inst.release()->InsertBefore(this); } list.clear(); return first_node; @@ -568,10 +601,13 @@ bool Instruction::IsValidBasePointer() const { } OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const { - if (opcode() != SpvOpExtInst) return OpenCLDebugInfo100InstructionsMax; + if (opcode() != SpvOpExtInst) { + return OpenCLDebugInfo100InstructionsMax; + } - if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) + if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { return OpenCLDebugInfo100InstructionsMax; + } if (GetSingleWordInOperand(kExtInstSetIdInIdx) != context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) { @@ -622,6 +658,7 @@ bool Instruction::IsFoldableByFoldScalar() const { if (!folder.IsFoldableOpcode(opcode())) { return false; } + Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); if (!folder.IsFoldableType(type)) { return false; @@ -889,6 +926,16 @@ bool Instruction::IsOpcodeSafeToDelete() const { } } +bool Instruction::IsNonSemanticInstruction() const { + if (!HasResultId()) return false; + if (opcode() != SpvOpExtInst) return false; + + auto import_inst = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(0)); + std::string import_name = import_inst->GetInOperand(0).AsString(); + return import_name.find("NonSemantic.") == 0; +} + void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id, uint32_t ext_set, std::vector<uint32_t>* binary) const { @@ -908,8 +955,10 @@ void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id, static_cast<uint32_t>(dbg_opcode), }; binary->insert(binary->end(), operands.begin(), operands.end()); - if (GetLexicalScope() != kNoDebugScope) binary->push_back(GetLexicalScope()); - if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt()); + if (GetLexicalScope() != kNoDebugScope) { + binary->push_back(GetLexicalScope()); + if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt()); + } } } // namespace opt diff --git a/source/opt/instruction.h b/source/opt/instruction.h index e99a5ae8..252e8cb5 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -246,6 +246,11 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // Clear line-related debug instructions attached to this instruction. void clear_dbg_line_insts() { dbg_line_insts_.clear(); } + // Set line-related debug instructions. + void set_dbg_line_insts(const std::vector<Instruction>& lines) { + dbg_line_insts_ = lines; + } + // Same semantics as in the base class except the list the InstructionList // containing |pos| will now assume ownership of |this|. // inline void MoveBefore(Instruction* pos); @@ -296,12 +301,14 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { inline void SetDebugScope(const DebugScope& scope); inline const DebugScope& GetDebugScope() const { return dbg_scope_; } // Updates DebugInlinedAt of DebugScope and OpLine. - inline void UpdateDebugInlinedAt(uint32_t new_inlined_at); + void UpdateDebugInlinedAt(uint32_t new_inlined_at); inline uint32_t GetDebugInlinedAt() const { return dbg_scope_.GetInlinedAt(); } + // Updates lexical scope of DebugScope and OpLine. + void UpdateLexicalScope(uint32_t scope); // Updates OpLine and DebugScope based on the information of |from|. - inline void UpdateDebugInfo(const Instruction* from); + void UpdateDebugInfoFrom(const Instruction* from); // Remove the |index|-th operand void RemoveOperand(uint32_t index) { operands_.erase(operands_.begin() + index); @@ -544,6 +551,9 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax; } + // Returns true if this instructions a non-semantic instruction. + bool IsNonSemanticInstruction() const; + // Dump this instruction on stderr. Useful when running interactive // debuggers. void Dump() const; @@ -660,21 +670,6 @@ inline void Instruction::SetDebugScope(const DebugScope& scope) { } } -inline void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { - dbg_scope_.SetInlinedAt(new_inlined_at); - for (auto& i : dbg_line_insts_) { - i.dbg_scope_.SetInlinedAt(new_inlined_at); - } -} - -inline void Instruction::UpdateDebugInfo(const Instruction* from) { - if (from == nullptr) return; - clear_dbg_line_insts(); - if (!from->dbg_line_insts().empty()) - dbg_line_insts().push_back(from->dbg_line_insts()[0]); - SetDebugScope(from->GetDebugScope()); -} - inline void Instruction::SetResultType(uint32_t ty_id) { // TODO(dsinclair): Allow setting a type id if there wasn't one // previously. Need to make room in the operands_ array to place the result, @@ -744,21 +739,21 @@ inline void Instruction::ForEachInst( } inline void Instruction::ForEachId(const std::function<void(uint32_t*)>& f) { - for (auto& opnd : operands_) - if (spvIsIdType(opnd.type)) f(&opnd.words[0]); + for (auto& operand : operands_) + if (spvIsIdType(operand.type)) f(&operand.words[0]); } inline void Instruction::ForEachId( const std::function<void(const uint32_t*)>& f) const { - for (const auto& opnd : operands_) - if (spvIsIdType(opnd.type)) f(&opnd.words[0]); + for (const auto& operand : operands_) + if (spvIsIdType(operand.type)) f(&operand.words[0]); } inline bool Instruction::WhileEachInId( const std::function<bool(uint32_t*)>& f) { - for (auto& opnd : operands_) { - if (spvIsInIdType(opnd.type)) { - if (!f(&opnd.words[0])) return false; + for (auto& operand : operands_) { + if (spvIsInIdType(operand.type) && !f(&operand.words[0])) { + return false; } } return true; @@ -766,9 +761,9 @@ inline bool Instruction::WhileEachInId( inline bool Instruction::WhileEachInId( const std::function<bool(const uint32_t*)>& f) const { - for (const auto& opnd : operands_) { - if (spvIsInIdType(opnd.type)) { - if (!f(&opnd.words[0])) return false; + for (const auto& operand : operands_) { + if (spvIsInIdType(operand.type) && !f(&operand.words[0])) { + return false; } } return true; @@ -791,13 +786,13 @@ inline void Instruction::ForEachInId( inline bool Instruction::WhileEachInOperand( const std::function<bool(uint32_t*)>& f) { - for (auto& opnd : operands_) { - switch (opnd.type) { + for (auto& operand : operands_) { + switch (operand.type) { case SPV_OPERAND_TYPE_RESULT_ID: case SPV_OPERAND_TYPE_TYPE_ID: break; default: - if (!f(&opnd.words[0])) return false; + if (!f(&operand.words[0])) return false; break; } } @@ -806,13 +801,13 @@ inline bool Instruction::WhileEachInOperand( inline bool Instruction::WhileEachInOperand( const std::function<bool(const uint32_t*)>& f) const { - for (const auto& opnd : operands_) { - switch (opnd.type) { + for (const auto& operand : operands_) { + switch (operand.type) { case SPV_OPERAND_TYPE_RESULT_ID: case SPV_OPERAND_TYPE_TYPE_ID: break; default: - if (!f(&opnd.words[0])) return false; + if (!f(&operand.words[0])) return false; break; } } @@ -821,16 +816,16 @@ inline bool Instruction::WhileEachInOperand( inline void Instruction::ForEachInOperand( const std::function<void(uint32_t*)>& f) { - WhileEachInOperand([&f](uint32_t* op) { - f(op); + WhileEachInOperand([&f](uint32_t* operand) { + f(operand); return true; }); } inline void Instruction::ForEachInOperand( const std::function<void(const uint32_t*)>& f) const { - WhileEachInOperand([&f](const uint32_t* op) { - f(op); + WhileEachInOperand([&f](const uint32_t* operand) { + f(operand); return true; }); } diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp index e6a55a8a..6ee3cf04 100644 --- a/source/opt/instrument_pass.cpp +++ b/source/opt/instrument_pass.cpp @@ -281,14 +281,42 @@ void InstrumentPass::GenDebugStreamWrite( (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args); } +bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) { + for (auto& id : ids) { + Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id); + if (!spvOpcodeIsConstant(id_inst->opcode())) return false; + } + return true; +} + uint32_t InstrumentPass::GenDebugDirectRead( - const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) { + const std::vector<uint32_t>& offset_ids, InstructionBuilder* ref_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(); + // If optimizing direct reads and the call has already been generated, + // use its result + if (opt_direct_reads_) { + uint32_t res_id = call2id_[args]; + if (res_id != 0) return res_id; + } + // If the offsets are all constants, the call can be moved to the first block + // of the function where its result can be reused. One example where this is + // profitable is for uniform buffer references, of which there are often many. + InstructionBuilder builder(ref_builder->GetContext(), + &*ref_builder->GetInsertPoint(), + ref_builder->GetPreservedAnalysis()); + bool insert_in_first_block = opt_direct_reads_ && AllConstant(offset_ids); + if (insert_in_first_block) { + Instruction* insert_before = &*curr_func_->begin()->tail(); + builder.SetInsertPoint(insert_before); + } + uint32_t res_id = + builder.AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id(); + if (insert_in_first_block) call2id_[args] = res_id; + return res_id; } bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const { @@ -819,21 +847,52 @@ uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) { return func_id; } +void InstrumentPass::SplitBlock( + BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr, + std::vector<std::unique_ptr<BasicBlock>>* new_blocks) { + // Make sure def/use analysis is done before we start moving instructions + // out of function + (void)get_def_use_mgr(); + // Move original block's preceding instructions into first new block + std::unique_ptr<BasicBlock> first_blk_ptr; + MovePreludeCode(inst_itr, block_itr, &first_blk_ptr); + InstructionBuilder builder( + context(), &*first_blk_ptr, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + uint32_t split_blk_id = TakeNextId(); + std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id)); + (void)builder.AddBranch(split_blk_id); + new_blocks->push_back(std::move(first_blk_ptr)); + // Move remaining instructions into split block and add to new blocks + std::unique_ptr<BasicBlock> split_blk_ptr( + new BasicBlock(std::move(split_label))); + MovePostludeCode(block_itr, &*split_blk_ptr); + new_blocks->push_back(std::move(split_blk_ptr)); +} + bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx, InstProcessFunction& pfn) { + curr_func_ = func; + call2id_.clear(); + bool first_block_split = false; bool modified = false; - // Compute function index - uint32_t function_idx = 0; - for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) { - if (&*fii == func) break; - ++function_idx; - } - std::vector<std::unique_ptr<BasicBlock>> new_blks; + // Apply instrumentation function to each instruction. // Using block iterators here because of block erasures and insertions. + std::vector<std::unique_ptr<BasicBlock>> new_blks; for (auto bi = func->begin(); bi != func->end(); ++bi) { for (auto ii = bi->begin(); ii != bi->end();) { - // Generate instrumentation if warranted - pfn(ii, bi, stage_idx, &new_blks); + // Split all executable instructions out of first block into a following + // block. This will allow function calls to be inserted into the first + // block without interfering with the instrumentation algorithm. + if (opt_direct_reads_ && !first_block_split) { + if (ii->opcode() != SpvOpVariable) { + SplitBlock(ii, bi, &new_blks); + first_block_split = true; + } + } else { + pfn(ii, bi, stage_idx, &new_blks); + } + // If no new code, continue if (new_blks.size() == 0) { ++ii; continue; diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h index f6884d2d..03c99bd9 100644 --- a/source/opt/instrument_pass.h +++ b/source/opt/instrument_pass.h @@ -82,12 +82,15 @@ class InstrumentPass : public Pass { protected: // Create instrumentation pass for |validation_id| which utilizes descriptor // set |desc_set| for debug input and output buffers and writes |shader_id| - // into debug output records. - InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id) + // into debug output records. |opt_direct_reads| indicates that the pass + // will see direct input buffer reads and should prepare to optimize them. + InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id, + bool opt_direct_reads = false) : Pass(), desc_set_(desc_set), shader_id_(shader_id), - validation_id_(validation_id) {} + validation_id_(validation_id), + opt_direct_reads_(opt_direct_reads) {} // Initialize state for instrumentation of module. void InitializeInstrument(); @@ -196,6 +199,9 @@ class InstrumentPass : public Pass { const std::vector<uint32_t>& validation_ids, InstructionBuilder* builder); + // Return true if all instructions in |ids| are constants or spec constants. + bool AllConstant(const std::vector<uint32_t>& ids); + // 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: @@ -284,6 +290,12 @@ class InstrumentPass : public Pass { // if it doesn't exist. uint32_t GetDirectReadFunctionId(uint32_t param_cnt); + // Split block |block_itr| into two new blocks where the second block + // contains |inst_itr| and place in |new_blocks|. + void SplitBlock(BasicBlock::iterator inst_itr, + UptrVectorIterator<BasicBlock> block_itr, + std::vector<std::unique_ptr<BasicBlock>>* new_blocks); + // 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 @@ -428,6 +440,29 @@ class InstrumentPass : public Pass { // Post-instrumentation same-block op ids std::unordered_map<uint32_t, uint32_t> same_block_post_; + + // Map function calls to result id. Clear for every function. + // This is for debug input reads with constant arguments that + // have been generated into the first block of the function. + // This mechanism is used to avoid multiple identical debug + // input buffer reads. + struct vector_hash_ { + std::size_t operator()(const std::vector<uint32_t>& v) const { + std::size_t hash = v.size(); + for (auto& u : v) { + hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21); + } + return hash; + } + }; + std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_; + + // Function currently being instrumented + Function* curr_func_; + + // Optimize direct debug input buffer reads. Specifically, move all such + // reads with constant args to first block and reuse them. + bool opt_direct_reads_; }; } // namespace opt diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h index b0c1d2ec..fe5feff5 100644 --- a/source/opt/ir_builder.h +++ b/source/opt/ir_builder.h @@ -601,15 +601,15 @@ class InstructionBuilder { return preserved_analyses_ & analysis; } - // Updates the def/use manager if the user requested it. If he did not request - // an update, this function does nothing. + // Updates the def/use manager if the user requested it. If an update was not + // requested, this function does nothing. inline void UpdateDefUseMgr(Instruction* insn) { if (IsAnalysisUpdateRequested(IRContext::kAnalysisDefUse)) GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn); } - // Updates the instruction to block analysis if the user requested it. If he - // did not request an update, this function does nothing. + // Updates the instruction to block analysis if the user requested it. If + // an update was not requested, this function does nothing. inline void UpdateInstrToBlockMapping(Instruction* insn) { if (IsAnalysisUpdateRequested(IRContext::kAnalysisInstrToBlockMapping) && parent_) diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp index 0791097c..3e610d70 100644 --- a/source/opt/ir_context.cpp +++ b/source/opt/ir_context.cpp @@ -181,6 +181,7 @@ Instruction* IRContext::KillInst(Instruction* inst) { } } if (AreAnalysesValid(kAnalysisDebugInfo)) { + get_debug_info_mgr()->ClearDebugScopeAndInlinedAtUses(inst); get_debug_info_mgr()->ClearDebugInfo(inst); } if (type_mgr_ && IsTypeInst(inst->opcode())) { @@ -213,6 +214,30 @@ Instruction* IRContext::KillInst(Instruction* inst) { return next_instruction; } +void IRContext::KillNonSemanticInfo(Instruction* inst) { + if (!inst->HasResultId()) return; + std::vector<Instruction*> work_list; + std::vector<Instruction*> to_kill; + std::unordered_set<Instruction*> seen; + work_list.push_back(inst); + + while (!work_list.empty()) { + auto* i = work_list.back(); + work_list.pop_back(); + get_def_use_mgr()->ForEachUser( + i, [&work_list, &to_kill, &seen](Instruction* user) { + if (user->IsNonSemanticInstruction() && seen.insert(user).second) { + work_list.push_back(user); + to_kill.push_back(user); + } + }); + } + + for (auto* dead : to_kill) { + KillInst(dead); + } +} + bool IRContext::KillDef(uint32_t id) { Instruction* def = get_def_use_mgr()->GetDef(id); if (def != nullptr) { @@ -222,23 +247,21 @@ bool IRContext::KillDef(uint32_t id) { return false; } -void IRContext::KillDebugDeclareInsts(Function* fn) { - fn->ForEachInst([this](Instruction* inst) { - if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) - KillInst(inst); - }); -} - bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) { - return ReplaceAllUsesWithPredicate( - before, after, [](Instruction*, uint32_t) { return true; }); + return ReplaceAllUsesWithPredicate(before, after, + [](Instruction*) { return true; }); } bool IRContext::ReplaceAllUsesWithPredicate( uint32_t before, uint32_t after, - const std::function<bool(Instruction*, uint32_t)>& predicate) { + const std::function<bool(Instruction*)>& predicate) { if (before == after) return false; + if (AreAnalysesValid(kAnalysisDebugInfo)) { + get_debug_info_mgr()->ReplaceAllUsesInDebugScopeWithPredicate(before, after, + predicate); + } + // Ensure that |after| has been registered as def. assert(get_def_use_mgr()->GetDef(after) && "'after' is not a registered def."); @@ -246,7 +269,7 @@ bool IRContext::ReplaceAllUsesWithPredicate( std::vector<std::pair<Instruction*, uint32_t>> uses_to_update; get_def_use_mgr()->ForEachUse( before, [&predicate, &uses_to_update](Instruction* user, uint32_t index) { - if (predicate(user, index)) { + if (predicate(user)) { uses_to_update.emplace_back(user, index); } }); @@ -284,7 +307,6 @@ bool IRContext::ReplaceAllUsesWithPredicate( } AnalyzeUses(user); } - return true; } diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h index 37be8365..5aa25acd 100644 --- a/source/opt/ir_context.h +++ b/source/opt/ir_context.h @@ -403,8 +403,8 @@ class IRContext { // instruction exists. Instruction* KillInst(Instruction* inst); - // Deletes DebugDeclare instructions in the given function |fn|. - void KillDebugDeclareInsts(Function* fn); + // Removes the non-semantic instruction tree that uses |inst|'s result id. + void KillNonSemanticInfo(Instruction* inst); // Returns true if all of the given analyses are valid. bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; } @@ -418,13 +418,13 @@ class IRContext { bool ReplaceAllUsesWith(uint32_t before, uint32_t after); // Replace all uses of |before| id with |after| id if those uses - // (instruction, operand pair) return true for |predicate|. Returns true if + // (instruction) return true for |predicate|. Returns true if // any replacement happens. This method does not kill the definition of the // |before| id. If |after| is the same as |before|, does nothing and return // false. bool ReplaceAllUsesWithPredicate( uint32_t before, uint32_t after, - const std::function<bool(Instruction*, uint32_t)>& predicate); + const std::function<bool(Instruction*)>& predicate); // Returns true if all of the analyses that are suppose to be valid are // actually valid. diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp index acd41cd6..a10812e4 100644 --- a/source/opt/ir_loader.cpp +++ b/source/opt/ir_loader.cpp @@ -167,13 +167,22 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { } else if (IsTypeInst(opcode)) { module_->AddType(std::move(spv_inst)); } else if (IsConstantInst(opcode) || opcode == SpvOpVariable || - opcode == SpvOpUndef || - (opcode == SpvOpExtInst && - spvExtInstIsNonSemantic(inst->ext_inst_type))) { + opcode == SpvOpUndef) { module_->AddGlobalValue(std::move(spv_inst)); } else if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) { module_->AddExtInstDebugInfo(std::move(spv_inst)); + } else if (opcode == SpvOpExtInst && + spvExtInstIsNonSemantic(inst->ext_inst_type)) { + // If there are no functions, add the non-semantic instructions to the + // global values. Otherwise append it to the list of the last function. + auto func_begin = module_->begin(); + auto func_end = module_->end(); + if (func_begin == func_end) { + module_->AddGlobalValue(std::move(spv_inst)); + } else { + (--func_end)->AddNonSemanticInstruction(std::move(spv_inst)); + } } else { Errorf(consumer_, src, loc, "Unhandled inst type (opcode: %d) found outside function " diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp index 9b8c112e..b57dd29a 100644 --- a/source/opt/local_access_chain_convert_pass.cpp +++ b/source/opt/local_access_chain_convert_pass.cpp @@ -77,6 +77,15 @@ void LocalAccessChainConvertPass::AppendConstantOperands( bool LocalAccessChainConvertPass::ReplaceAccessChainLoad( const Instruction* address_inst, Instruction* original_load) { // Build and append load of variable in ptrInst + if (address_inst->NumInOperands() == 1) { + // An access chain with no indices is essentially a copy. All that is + // needed is to propagate the address. + context()->ReplaceAllUsesWith( + address_inst->result_id(), + address_inst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)); + return true; + } + std::vector<std::unique_ptr<Instruction>> new_inst; uint32_t varId; uint32_t varPteTypeId; @@ -109,6 +118,18 @@ bool LocalAccessChainConvertPass::ReplaceAccessChainLoad( bool LocalAccessChainConvertPass::GenAccessChainStoreReplacement( const Instruction* ptrInst, uint32_t valId, std::vector<std::unique_ptr<Instruction>>* newInsts) { + if (ptrInst->NumInOperands() == 1) { + // An access chain with no indices is essentially a copy. However, we still + // have to create a new store because the old ones will be deleted. + BuildAndAppendInst( + SpvOpStore, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}}, + newInsts); + return true; + } + // Build and append load of variable in ptrInst uint32_t varId; uint32_t varPteTypeId; @@ -246,11 +267,13 @@ Pass::Status LocalAccessChainConvertPass::ConvertLocalAccessChains( if (!GenAccessChainStoreReplacement(ptrInst, valId, &newInsts)) { return Status::Failure; } + size_t num_of_instructions_to_skip = newInsts.size() - 1; dead_instructions.push_back(&*ii); ++ii; ii = ii.InsertBefore(std::move(newInsts)); - ++ii; - ++ii; + for (size_t i = 0; i < num_of_instructions_to_skip; ++i) { + ++ii; + } modified = true; } break; default: @@ -346,6 +369,7 @@ void LocalAccessChainConvertPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp index bd5d7510..5f35ee1a 100644 --- a/source/opt/local_single_block_elim_pass.cpp +++ b/source/opt/local_single_block_elim_pass.cpp @@ -83,7 +83,8 @@ bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim( auto prev_store = var2store_.find(varId); if (prev_store != var2store_.end() && instructions_to_save.count(prev_store->second) == 0 && - !context()->get_debug_info_mgr()->IsDebugDeclared(varId)) { + !context()->get_debug_info_mgr()->IsVariableDebugDeclared( + varId)) { instructions_to_kill.push_back(prev_store->second); modified = true; } @@ -231,6 +232,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp index 23841075..c3479749 100644 --- a/source/opt/local_single_store_elim_pass.cpp +++ b/source/opt/local_single_store_elim_pass.cpp @@ -88,6 +88,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() { "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", "SPV_KHR_subgroup_vote", + "SPV_KHR_8bit_storage", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", @@ -142,12 +143,12 @@ bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) { // the DebugDeclare. uint32_t var_id = var_inst->result_id(); if (all_rewritten && - context()->get_debug_info_mgr()->IsDebugDeclared(var_id)) { + context()->get_debug_info_mgr()->IsVariableDebugDeclared(var_id)) { const analysis::Type* var_type = context()->get_type_mgr()->GetType(var_inst->type_id()); const analysis::Type* store_type = var_type->AsPointer()->pointee_type(); if (!(store_type->AsStruct() || store_type->AsArray())) { - context()->get_debug_info_mgr()->AddDebugValue( + context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible( store_inst, var_id, store_inst->GetSingleWordInOperand(1), store_inst); context()->get_debug_info_mgr()->KillDebugDeclares(var_id); diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp index ed0dd28f..b5b56309 100644 --- a/source/opt/loop_descriptor.cpp +++ b/source/opt/loop_descriptor.cpp @@ -191,14 +191,13 @@ bool Loop::GetInductionInitValue(const Instruction* induction, if (!constant) return false; if (value) { - const analysis::Integer* type = - constant->AsIntConstant()->type()->AsInteger(); - - if (type->IsSigned()) { - *value = constant->AsIntConstant()->GetS32BitValue(); - } else { - *value = constant->AsIntConstant()->GetU32BitValue(); + const analysis::Integer* type = constant->type()->AsInteger(); + if (!type) { + return false; } + + *value = type->IsSigned() ? constant->GetSignExtendedValue() + : constant->GetZeroExtendedValue(); } return true; @@ -511,7 +510,7 @@ void Loop::ComputeLoopStructuredOrder( } LoopDescriptor::LoopDescriptor(IRContext* context, const Function* f) - : loops_(), dummy_top_loop_(nullptr) { + : loops_(), placeholder_top_loop_(nullptr) { PopulateList(context, f); } @@ -592,7 +591,7 @@ void LoopDescriptor::PopulateList(IRContext* context, const Function* f) { } } for (Loop* loop : loops_) { - if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop); + if (!loop->HasParent()) placeholder_top_loop_.nested_loops_.push_back(loop); } } @@ -682,22 +681,19 @@ bool Loop::FindNumberOfIterations(const Instruction* induction, if (!upper_bound) return false; // Must be integer because of the opcode on the condition. - int64_t condition_value = 0; + const analysis::Integer* type = upper_bound->type()->AsInteger(); - const analysis::Integer* type = - upper_bound->AsIntConstant()->type()->AsInteger(); - - if (type->width() > 32) { + if (!type || type->width() > 64) { return false; } - if (type->IsSigned()) { - condition_value = upper_bound->AsIntConstant()->GetS32BitValue(); - } else { - condition_value = upper_bound->AsIntConstant()->GetU32BitValue(); - } + int64_t condition_value = type->IsSigned() + ? upper_bound->GetSignExtendedValue() + : upper_bound->GetZeroExtendedValue(); // Find the instruction which is stepping through the loop. + // + // GetInductionStepOperation returns nullptr if |step_inst| is OpConstantNull. Instruction* step_inst = GetInductionStepOperation(induction); if (!step_inst) return false; @@ -986,7 +982,7 @@ void LoopDescriptor::ClearLoops() { // Adds a new loop nest to the descriptor set. Loop* LoopDescriptor::AddLoopNest(std::unique_ptr<Loop> new_loop) { Loop* loop = new_loop.release(); - if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop); + if (!loop->HasParent()) placeholder_top_loop_.nested_loops_.push_back(loop); // Iterate from inner to outer most loop, adding basic block to loop mapping // as we go. for (Loop& current_loop : @@ -1000,7 +996,7 @@ Loop* LoopDescriptor::AddLoopNest(std::unique_ptr<Loop> new_loop) { } void LoopDescriptor::RemoveLoop(Loop* loop) { - Loop* parent = loop->GetParent() ? loop->GetParent() : &dummy_top_loop_; + Loop* parent = loop->GetParent() ? loop->GetParent() : &placeholder_top_loop_; parent->nested_loops_.erase(std::find(parent->nested_loops_.begin(), parent->nested_loops_.end(), loop)); std::for_each( diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h index 6e2b8289..4b4f8bc7 100644 --- a/source/opt/loop_descriptor.h +++ b/source/opt/loop_descriptor.h @@ -406,8 +406,8 @@ class Loop { // the iterators. bool loop_is_marked_for_removal_; - // This is only to allow LoopDescriptor::dummy_top_loop_ to add top level - // loops as child. + // This is only to allow LoopDescriptor::placeholder_top_loop_ to add top + // level loops as child. friend class LoopDescriptor; friend class LoopUtils; }; @@ -430,14 +430,14 @@ class LoopDescriptor { // Disable copy constructor, to avoid double-free on destruction. LoopDescriptor(const LoopDescriptor&) = delete; // Move constructor. - LoopDescriptor(LoopDescriptor&& other) : dummy_top_loop_(nullptr) { + LoopDescriptor(LoopDescriptor&& other) : placeholder_top_loop_(nullptr) { // We need to take ownership of the Loop objects in the other // LoopDescriptor, to avoid double-free. loops_ = std::move(other.loops_); other.loops_.clear(); basic_block_to_loop_ = std::move(other.basic_block_to_loop_); other.basic_block_to_loop_.clear(); - dummy_top_loop_ = std::move(other.dummy_top_loop_); + placeholder_top_loop_ = std::move(other.placeholder_top_loop_); } // Destructor @@ -470,25 +470,27 @@ class LoopDescriptor { // Iterators for post order depth first traversal of the loops. // Inner most loops will be visited first. - inline iterator begin() { return iterator::begin(&dummy_top_loop_); } - inline iterator end() { return iterator::end(&dummy_top_loop_); } + inline iterator begin() { return iterator::begin(&placeholder_top_loop_); } + inline iterator end() { return iterator::end(&placeholder_top_loop_); } inline const_iterator begin() const { return cbegin(); } inline const_iterator end() const { return cend(); } inline const_iterator cbegin() const { - return const_iterator::begin(&dummy_top_loop_); + return const_iterator::begin(&placeholder_top_loop_); } inline const_iterator cend() const { - return const_iterator::end(&dummy_top_loop_); + return const_iterator::end(&placeholder_top_loop_); } // Iterators for pre-order depth first traversal of the loops. // Inner most loops will be visited first. - inline pre_iterator pre_begin() { return ++pre_iterator(&dummy_top_loop_); } + inline pre_iterator pre_begin() { + return ++pre_iterator(&placeholder_top_loop_); + } inline pre_iterator pre_end() { return pre_iterator(); } inline const_pre_iterator pre_begin() const { return pre_cbegin(); } inline const_pre_iterator pre_end() const { return pre_cend(); } inline const_pre_iterator pre_cbegin() const { - return ++const_pre_iterator(&dummy_top_loop_); + return ++const_pre_iterator(&placeholder_top_loop_); } inline const_pre_iterator pre_cend() const { return const_pre_iterator(); } @@ -524,14 +526,14 @@ class LoopDescriptor { void RemoveLoop(Loop* loop); void SetAsTopLoop(Loop* loop) { - assert(std::find(dummy_top_loop_.begin(), dummy_top_loop_.end(), loop) == - dummy_top_loop_.end() && + assert(std::find(placeholder_top_loop_.begin(), placeholder_top_loop_.end(), + loop) == placeholder_top_loop_.end() && "already registered"); - dummy_top_loop_.nested_loops_.push_back(loop); + placeholder_top_loop_.nested_loops_.push_back(loop); } - Loop* GetDummyRootLoop() { return &dummy_top_loop_; } - const Loop* GetDummyRootLoop() const { return &dummy_top_loop_; } + Loop* GetPlaceholderRootLoop() { return &placeholder_top_loop_; } + const Loop* GetPlaceholderRootLoop() const { return &placeholder_top_loop_; } private: // TODO(dneto): This should be a vector of unique_ptr. But VisualStudio 2013 @@ -558,8 +560,8 @@ class LoopDescriptor { // objects. LoopContainerType loops_; - // Dummy root: this "loop" is only there to help iterators creation. - Loop dummy_top_loop_; + // Placeholder root: this "loop" is only there to help iterators creation. + Loop placeholder_top_loop_; std::unordered_map<uint32_t, Loop*> basic_block_to_loop_; diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp index b640542d..071c27cb 100644 --- a/source/opt/loop_peeling.cpp +++ b/source/opt/loop_peeling.cpp @@ -1063,7 +1063,7 @@ LoopPeelingPass::LoopPeelingInfo::HandleInequality(CmpOperator cmp_op, } uint32_t cast_iteration = 0; - // sanity check: can we fit |iteration| in a uint32_t ? + // Integrity check: can we fit |iteration| in a uint32_t ? if (static_cast<uint64_t>(iteration) < std::numeric_limits<uint32_t>::max()) { cast_iteration = static_cast<uint32_t>(iteration); } diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp index 40cf6bc2..6cdced46 100644 --- a/source/opt/loop_unroller.cpp +++ b/source/opt/loop_unroller.cpp @@ -286,6 +286,9 @@ class LoopUnrollerUtilsImpl { // to be the actual value of the phi at that point. void LinkLastPhisToStart(Loop* loop) const; + // Kill all debug declaration instructions from |bb|. + void KillDebugDeclares(BasicBlock* bb); + // A pointer to the IRContext. Used to add/remove instructions and for usedef // chains. IRContext* context_; @@ -598,6 +601,20 @@ void LoopUnrollerUtilsImpl::FullyUnroll(Loop* loop) { IRContext::Analysis::kAnalysisDefUse); } +void LoopUnrollerUtilsImpl::KillDebugDeclares(BasicBlock* bb) { + // We cannot kill an instruction inside BasicBlock::ForEachInst() + // because it will generate dangling pointers. We use |to_be_killed| + // to kill them after the loop. + std::vector<Instruction*> to_be_killed; + + bb->ForEachInst([&to_be_killed, this](Instruction* inst) { + if (context_->get_debug_info_mgr()->IsDebugDeclare(inst)) { + to_be_killed.push_back(inst); + } + }); + for (auto* inst : to_be_killed) context_->KillInst(inst); +} + // Copy a given basic block, give it a new result_id, and store the new block // and the id mapping in the state. |preserve_instructions| is used to determine // whether or not this function should edit instructions other than the @@ -608,6 +625,9 @@ void LoopUnrollerUtilsImpl::CopyBasicBlock(Loop* loop, const BasicBlock* itr, BasicBlock* basic_block = itr->Clone(context_); basic_block->SetParent(itr->GetParent()); + // We do not want to duplicate DebugDeclare. + KillDebugDeclares(basic_block); + // Assign each result a new unique ID and keep a mapping of the old ids to // the new ones. AssignNewResultIds(basic_block); @@ -674,21 +694,21 @@ void LoopUnrollerUtilsImpl::CopyBody(Loop* loop, bool eliminate_conditions) { std::vector<Instruction*> inductions; loop->GetInductionVariables(inductions); for (size_t index = 0; index < inductions.size(); ++index) { - Instruction* master_copy = inductions[index]; + Instruction* primary_copy = inductions[index]; - assert(master_copy->result_id() != 0); + assert(primary_copy->result_id() != 0); Instruction* induction_clone = - state_.ids_to_new_inst[state_.new_inst[master_copy->result_id()]]; + state_.ids_to_new_inst[state_.new_inst[primary_copy->result_id()]]; state_.new_phis_.push_back(induction_clone); assert(induction_clone->result_id() != 0); if (!state_.previous_phis_.empty()) { - state_.new_inst[master_copy->result_id()] = GetPhiDefID( + state_.new_inst[primary_copy->result_id()] = GetPhiDefID( state_.previous_phis_[index], state_.previous_latch_block_->id()); } else { // Do not replace the first phi block ids. - state_.new_inst[master_copy->result_id()] = master_copy->result_id(); + state_.new_inst[primary_copy->result_id()] = primary_copy->result_id(); } } @@ -729,13 +749,19 @@ void LoopUnrollerUtilsImpl::FoldConditionBlock(BasicBlock* condition_block, Instruction& old_branch = *condition_block->tail(); uint32_t new_target = old_branch.GetSingleWordOperand(operand_label); + DebugScope scope = old_branch.GetDebugScope(); + const std::vector<Instruction> lines = old_branch.dbg_line_insts(); + context_->KillInst(&old_branch); // Add the new unconditional branch to the merge block. InstructionBuilder builder( context_, condition_block, IRContext::Analysis::kAnalysisDefUse | IRContext::Analysis::kAnalysisInstrToBlockMapping); - builder.AddBranch(new_target); + Instruction* new_branch = builder.AddBranch(new_target); + + new_branch->set_dbg_line_insts(lines); + new_branch->SetDebugScope(scope); } void LoopUnrollerUtilsImpl::CloseUnrolledLoop(Loop* loop) { diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp index 502fc6b6..d805ecf3 100644 --- a/source/opt/loop_unswitch_pass.cpp +++ b/source/opt/loop_unswitch_pass.cpp @@ -594,9 +594,9 @@ bool LoopUnswitchPass::ProcessFunction(Function* f) { bool loop_changed = true; while (loop_changed) { loop_changed = false; - for (Loop& loop : - make_range(++TreeDFIterator<Loop>(loop_descriptor.GetDummyRootLoop()), - TreeDFIterator<Loop>())) { + for (Loop& loop : make_range( + ++TreeDFIterator<Loop>(loop_descriptor.GetPlaceholderRootLoop()), + TreeDFIterator<Loop>())) { if (processed_loop.count(&loop)) continue; processed_loop.insert(&loop); diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp index 2421c2ca..b43eb317 100644 --- a/source/opt/merge_return_pass.cpp +++ b/source/opt/merge_return_pass.cpp @@ -111,7 +111,7 @@ bool MergeReturnPass::ProcessStructured( } RecordImmediateDominators(function); - AddDummySwitchAroundFunction(); + AddSingleCaseSwitchAroundFunction(); std::list<BasicBlock*> order; cfg()->ComputeStructuredOrder(function, &*function->begin(), &order); @@ -223,7 +223,8 @@ void MergeReturnPass::ProcessStructuredBlock(BasicBlock* block) { if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue || tail_opcode == SpvOpUnreachable) { - assert(CurrentState().InBreakable() && "Should be in the dummy construct."); + assert(CurrentState().InBreakable() && + "Should be in the placeholder construct."); BranchToBlock(block, CurrentState().BreakMergeId()); return_blocks_.insert(block->id()); } @@ -408,7 +409,7 @@ bool MergeReturnPass::PredicateBlocks( if (!predicated->insert(block).second) break; // Skip structured subgraphs. assert(state->InBreakable() && - "Should be in the dummy construct at the very least."); + "Should be in the placeholder construct at the very least."); Instruction* break_merge_inst = state->BreakMergeInst(); uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0); while (state->BreakMergeId() == merge_block_id) { @@ -768,7 +769,7 @@ void MergeReturnPass::InsertAfterElement(BasicBlock* element, list->insert(pos, new_element); } -void MergeReturnPass::AddDummySwitchAroundFunction() { +void MergeReturnPass::AddSingleCaseSwitchAroundFunction() { CreateReturnBlock(); CreateReturn(final_return_block_); @@ -776,7 +777,7 @@ void MergeReturnPass::AddDummySwitchAroundFunction() { cfg()->RegisterBlock(final_return_block_); } - CreateDummySwitch(final_return_block_); + CreateSingleCaseSwitch(final_return_block_); } BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { @@ -811,7 +812,7 @@ BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { return new_block; } -void MergeReturnPass::CreateDummySwitch(BasicBlock* merge_target) { +void MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) { // Insert the switch before any code is run. We have to split the entry // block to make sure the OpVariable instructions remain in the entry block. BasicBlock* start_block = &*function_->begin(); diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h index fe85557a..06a3e7b5 100644 --- a/source/opt/merge_return_pass.h +++ b/source/opt/merge_return_pass.h @@ -48,13 +48,13 @@ namespace opt { * is the final return. This block should branch to the new return block (its * direct successor). If the current block is within structured control flow, * the branch destination should be the innermost construct's merge. This - * merge will always exist because a dummy switch is added around the + * merge will always exist because a single case switch is added around the * entire function. If the merge block produces any live values it will need to * be predicated. While the merge is nested in structured control flow, the * predication path should branch to the merge block of the inner-most loop * (or switch if no loop) it is contained in. Once structured control flow has - * been exited, it will be at the merge of the dummy switch, which will simply - * return. + * been exited, it will be at the merge of the single case switch, which will + * simply return. * * In the final return block, the return value should be loaded and returned. * Memory promotion passes should be able to promote the newly introduced @@ -73,7 +73,7 @@ namespace opt { * || * \/ * - * 0 (dummy switch header) + * 0 (single case switch header) * | * 1 (loop header) * / \ @@ -83,7 +83,7 @@ namespace opt { * / \ * | 3 (original code in 3) * \ / - * (ret) 4 (dummy switch merge) + * (ret) 4 (single case switch merge) * * In the above (simple) example, the return originally in |2| is passed through * the loop merge. That merge is predicated such that the old body of the block @@ -277,7 +277,7 @@ class MergeReturnPass : public MemPass { // current function where the switch and case value are both zero and the // default is the merge block. Returns after the switch is executed. Sets // |final_return_block_|. - void AddDummySwitchAroundFunction(); + void AddSingleCaseSwitchAroundFunction(); // Creates a new basic block that branches to |header_label_id|. Returns the // new basic block. The block will be the second last basic block in the @@ -286,7 +286,7 @@ class MergeReturnPass : public MemPass { // Creates a one case switch around the executable code of the function with // |merge_target| as the merge node. - void CreateDummySwitch(BasicBlock* merge_target); + void CreateSingleCaseSwitch(BasicBlock* merge_target); // Returns true if |function| has an unreachable block that is not a continue // target that simply branches back to the header, or a merge block containing diff --git a/source/opt/module.cpp b/source/opt/module.cpp index 2959d3d9..67076315 100644 --- a/source/opt/module.cpp +++ b/source/opt/module.cpp @@ -98,7 +98,10 @@ void Module::ForEachInst(const std::function<void(Instruction*)>& f, DELEGATE(ext_inst_debuginfo_); DELEGATE(annotations_); DELEGATE(types_values_); - for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts); + for (auto& i : functions_) { + i->ForEachInst(f, run_on_debug_line_insts, + /* run_on_non_semantic_insts = */ true); + } #undef DELEGATE } @@ -120,8 +123,9 @@ void Module::ForEachInst(const std::function<void(const Instruction*)>& f, for (auto& i : types_values_) DELEGATE(i); for (auto& i : ext_inst_debuginfo_) DELEGATE(i); for (auto& i : functions_) { - static_cast<const Function*>(i.get())->ForEachInst(f, - run_on_debug_line_insts); + static_cast<const Function*>(i.get())->ForEachInst( + f, run_on_debug_line_insts, + /* run_on_non_semantic_insts = */ true); } if (run_on_debug_line_insts) { for (auto& i : trailing_dbg_line_info_) DELEGATE(i); diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 25adee97..b891124e 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -427,6 +427,12 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); RegisterPass(CreateAggressiveDCEPass()); + } else if (pass_name == "inst-buff-oob-check") { + RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true)); + RegisterPass(CreateSimplificationPass()); + RegisterPass(CreateDeadBranchElimPass()); + RegisterPass(CreateBlockMergePass()); + RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "inst-buff-addr-check") { RegisterPass(CreateInstBuffAddrCheckPass(7, 23)); RegisterPass(CreateAggressiveDCEPass()); @@ -579,8 +585,8 @@ bool Optimizer::Run(const uint32_t* original_binary, #ifndef NDEBUG // We do not keep the result id of DebugScope in struct DebugScope. - // Instead, we assign random ids for them, which results in sanity - // check failures. We want to skip the sanity check when the module + // Instead, we assign random ids for them, which results in integrity + // check failures. We want to skip the integrity check when the module // contains DebugScope instructions. if (status == opt::Pass::Status::SuccessWithoutChange && !context->module()->ContainsDebugScope()) { @@ -894,10 +900,12 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() { Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, bool input_length_enable, - bool input_init_enable) { + bool input_init_enable, + bool input_buff_oob_enable) { return MakeUnique<Optimizer::PassToken::Impl>( MakeUnique<opt::InstBindlessCheckPass>( - desc_set, shader_id, input_length_enable, input_init_enable)); + desc_set, shader_id, input_length_enable, input_init_enable, + input_buff_oob_enable)); } Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set, diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp index 6df690dc..dd6cbbde 100644 --- a/source/opt/private_to_local_pass.cpp +++ b/source/opt/private_to_local_pass.cpp @@ -138,7 +138,7 @@ bool PrivateToLocalPass::MoveVariable(Instruction* variable, function->begin()->begin()->InsertBefore(move(var)); // Update uses where the type may have changed. - return UpdateUses(variable->result_id()); + return UpdateUses(variable); } uint32_t PrivateToLocalPass::GetNewType(uint32_t old_type_id) { @@ -157,6 +157,10 @@ uint32_t PrivateToLocalPass::GetNewType(uint32_t old_type_id) { bool PrivateToLocalPass::IsValidUse(const Instruction* inst) const { // The cases in this switch have to match the cases in |UpdateUse|. // If we don't know how to update it, it is not valid. + if (inst->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugGlobalVariable) { + return true; + } switch (inst->opcode()) { case SpvOpLoad: case SpvOpStore: @@ -175,10 +179,16 @@ bool PrivateToLocalPass::IsValidUse(const Instruction* inst) const { } } -bool PrivateToLocalPass::UpdateUse(Instruction* inst) { +bool PrivateToLocalPass::UpdateUse(Instruction* inst, Instruction* user) { // The cases in this switch have to match the cases in |IsValidUse|. If we // don't think it is valid, the optimization will not view the variable as a // candidate, and therefore the use will not be updated. + if (inst->GetOpenCL100DebugOpcode() == + OpenCLDebugInfo100DebugGlobalVariable) { + context()->get_debug_info_mgr()->ConvertDebugGlobalToLocalVariable(inst, + user); + return true; + } switch (inst->opcode()) { case SpvOpLoad: case SpvOpStore: @@ -196,7 +206,7 @@ bool PrivateToLocalPass::UpdateUse(Instruction* inst) { context()->AnalyzeUses(inst); // Update uses where the type may have changed. - if (!UpdateUses(inst->result_id())) { + if (!UpdateUses(inst)) { return false; } } break; @@ -211,13 +221,14 @@ bool PrivateToLocalPass::UpdateUse(Instruction* inst) { return true; } -bool PrivateToLocalPass::UpdateUses(uint32_t id) { +bool PrivateToLocalPass::UpdateUses(Instruction* inst) { + uint32_t id = inst->result_id(); std::vector<Instruction*> uses; context()->get_def_use_mgr()->ForEachUser( id, [&uses](Instruction* use) { uses.push_back(use); }); for (Instruction* use : uses) { - if (!UpdateUse(use)) { + if (!UpdateUse(use, inst)) { return false; } } diff --git a/source/opt/private_to_local_pass.h b/source/opt/private_to_local_pass.h index 3f9135c0..c6127d67 100644 --- a/source/opt/private_to_local_pass.h +++ b/source/opt/private_to_local_pass.h @@ -63,8 +63,8 @@ class PrivateToLocalPass : public Pass { // Updates |inst|, and any instruction dependent on |inst|, to reflect the // change of the base pointer now pointing to the function storage class. - bool UpdateUse(Instruction* inst); - bool UpdateUses(uint32_t id); + bool UpdateUse(Instruction* inst, Instruction* user); + bool UpdateUses(Instruction* inst); }; } // namespace opt diff --git a/source/opt/register_pressure.cpp b/source/opt/register_pressure.cpp index cb246744..5750c6d4 100644 --- a/source/opt/register_pressure.cpp +++ b/source/opt/register_pressure.cpp @@ -163,7 +163,7 @@ class ComputeRegisterLiveness { // Propagates the register liveness information of each loop iterators. void DoLoopLivenessUnification() { - for (const Loop* loop : *loop_desc_.GetDummyRootLoop()) { + for (const Loop* loop : *loop_desc_.GetPlaceholderRootLoop()) { DoLoopLivenessUnification(*loop); } } diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp index 36c0c0d7..d71d605b 100644 --- a/source/opt/scalar_replacement_pass.cpp +++ b/source/opt/scalar_replacement_pass.cpp @@ -25,6 +25,10 @@ #include "source/opt/types.h" #include "source/util/make_unique.h" +static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4; +static const uint32_t kDebugValueOperandValueIndex = 5; +static const uint32_t kDebugValueOperandExpressionIndex = 6; + namespace spvtools { namespace opt { @@ -80,6 +84,20 @@ Pass::Status ScalarReplacementPass::ReplaceVariable( std::vector<Instruction*> dead; bool replaced_all_uses = get_def_use_mgr()->WhileEachUser( inst, [this, &replacements, &dead](Instruction* user) { + if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) { + if (ReplaceWholeDebugDeclare(user, replacements)) { + dead.push_back(user); + return true; + } + return false; + } + if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) { + if (ReplaceWholeDebugValue(user, replacements)) { + dead.push_back(user); + return true; + } + return false; + } if (!IsAnnotationInst(user->opcode())) { switch (user->opcode()) { case SpvOpLoad: @@ -144,6 +162,58 @@ Pass::Status ScalarReplacementPass::ReplaceVariable( return Status::SuccessWithChange; } +bool ScalarReplacementPass::ReplaceWholeDebugDeclare( + Instruction* dbg_decl, const std::vector<Instruction*>& replacements) { + // Insert Deref operation to the front of the operation list of |dbg_decl|. + Instruction* dbg_expr = context()->get_def_use_mgr()->GetDef( + dbg_decl->GetSingleWordOperand(kDebugValueOperandExpressionIndex)); + auto* deref_expr = + context()->get_debug_info_mgr()->DerefDebugExpression(dbg_expr); + + // Add DebugValue instruction with Indexes operand and Deref operation. + int32_t idx = 0; + for (const auto* var : replacements) { + uint32_t dbg_local_variable = + dbg_decl->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex); + uint32_t index_id = context()->get_constant_mgr()->GetSIntConst(idx); + + Instruction* added_dbg_value = + context()->get_debug_info_mgr()->AddDebugValueWithIndex( + dbg_local_variable, + /*value_id=*/var->result_id(), /*expr_id=*/deref_expr->result_id(), + index_id, /*insert_before=*/var->NextNode()); + if (added_dbg_value == nullptr) return false; + added_dbg_value->UpdateDebugInfoFrom(dbg_decl); + ++idx; + } + return true; +} + +bool ScalarReplacementPass::ReplaceWholeDebugValue( + Instruction* dbg_value, const std::vector<Instruction*>& replacements) { + int32_t idx = 0; + BasicBlock* block = context()->get_instr_block(dbg_value); + for (auto var : replacements) { + // Clone the DebugValue. + std::unique_ptr<Instruction> new_dbg_value(dbg_value->Clone(context())); + uint32_t new_id = TakeNextId(); + if (new_id == 0) return false; + new_dbg_value->SetResultId(new_id); + // Update 'Value' operand to the |replacements|. + new_dbg_value->SetOperand(kDebugValueOperandValueIndex, {var->result_id()}); + // Append 'Indexes' operand. + new_dbg_value->AddOperand( + {SPV_OPERAND_TYPE_ID, + {context()->get_constant_mgr()->GetSIntConst(idx)}}); + // Insert the new DebugValue to the basic block. + auto* added_instr = dbg_value->InsertBefore(std::move(new_dbg_value)); + get_def_use_mgr()->AnalyzeInstDefUse(added_instr); + context()->set_instr_block(added_instr, block); + ++idx; + } + return true; +} + bool ScalarReplacementPass::ReplaceWholeLoad( Instruction* load, const std::vector<Instruction*>& replacements) { // Replaces the load of the entire composite with a load from each replacement @@ -177,6 +247,7 @@ bool ScalarReplacementPass::ReplaceWholeLoad( where = where.InsertBefore(std::move(newLoad)); get_def_use_mgr()->AnalyzeInstDefUse(&*where); context()->set_instr_block(&*where, block); + where->UpdateDebugInfoFrom(load); loads.push_back(&*where); } @@ -195,6 +266,7 @@ bool ScalarReplacementPass::ReplaceWholeLoad( } where = where.InsertBefore(std::move(compositeConstruct)); get_def_use_mgr()->AnalyzeInstDefUse(&*where); + where->UpdateDebugInfoFrom(load); context()->set_instr_block(&*where, block); context()->ReplaceAllUsesWith(load->result_id(), compositeId); return true; @@ -226,6 +298,7 @@ bool ScalarReplacementPass::ReplaceWholeStore( {SPV_OPERAND_TYPE_ID, {storeInput}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {elementIndex++}}})); auto iter = where.InsertBefore(std::move(extract)); + iter->UpdateDebugInfoFrom(store); get_def_use_mgr()->AnalyzeInstDefUse(&*iter); context()->set_instr_block(&*iter, block); @@ -242,6 +315,7 @@ bool ScalarReplacementPass::ReplaceWholeStore( newStore->AddOperand(std::move(copy)); } iter = where.InsertBefore(std::move(newStore)); + iter->UpdateDebugInfoFrom(store); get_def_use_mgr()->AnalyzeInstDefUse(&*iter); context()->set_instr_block(&*iter, block); } @@ -281,6 +355,7 @@ bool ScalarReplacementPass::ReplaceAccessChain( Operand copy(chain->GetInOperand(i)); replacementChain->AddOperand(std::move(copy)); } + replacementChain->UpdateDebugInfoFrom(chain); auto iter = chainIter.InsertBefore(std::move(replacementChain)); get_def_use_mgr()->AnalyzeInstDefUse(&*iter); context()->set_instr_block(&*iter, context()->get_instr_block(chain)); @@ -427,6 +502,9 @@ void ScalarReplacementPass::CreateVariable( } } + // Update the OpenCL.DebugInfo.100 debug information. + inst->UpdateDebugInfoFrom(varInst); + replacements->push_back(inst); } @@ -711,6 +789,14 @@ bool ScalarReplacementPass::CheckUses(const Instruction* inst, get_def_use_mgr()->ForEachUse(inst, [this, max_legal_index, stats, &ok]( const Instruction* user, uint32_t index) { + if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare || + user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) { + // TODO: include num_partial_accesses if it uses Fragment operation or + // DebugValue has Indexes operand. + stats->num_full_accesses++; + return; + } + // Annotations are check as a group separately. if (!IsAnnotationInst(user->opcode())) { switch (user->opcode()) { diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h index e20f1f1a..1f6c9281 100644 --- a/source/opt/scalar_replacement_pass.h +++ b/source/opt/scalar_replacement_pass.h @@ -199,6 +199,21 @@ class ScalarReplacementPass : public Pass { bool ReplaceWholeStore(Instruction* store, const std::vector<Instruction*>& replacements); + // Replaces the DebugDeclare to the entire composite. + // + // Generates a DebugValue with Deref operation for each element in the + // scalarized variable from the original DebugDeclare. Returns true if + // successful. + bool ReplaceWholeDebugDeclare(Instruction* dbg_decl, + const std::vector<Instruction*>& replacements); + + // Replaces the DebugValue to the entire composite. + // + // Generates a DebugValue for each element in the scalarized variable from + // the original DebugValue. Returns true if successful. + bool ReplaceWholeDebugValue(Instruction* dbg_value, + const std::vector<Instruction*>& replacements); + // Replaces an access chain to the composite variable with either a direct use // of the appropriate replacement variable or another access chain with the // replacement variable as the base and one fewer indexes. Returns true if diff --git a/source/opt/simplification_pass.cpp b/source/opt/simplification_pass.cpp index 001f3543..319ceecf 100644 --- a/source/opt/simplification_pass.cpp +++ b/source/opt/simplification_pass.cpp @@ -90,7 +90,7 @@ bool SimplificationPass::SimplifyFunction(Function* function) { if (inst->opcode() == SpvOpCopyObject) { context()->ReplaceAllUsesWithPredicate( inst->result_id(), inst->GetSingleWordInOperand(0), - [](Instruction* user, uint32_t) { + [](Instruction* user) { const auto opcode = user->opcode(); if (!spvOpcodeIsDebug(opcode) && !spvOpcodeIsDecoration(opcode)) { @@ -137,7 +137,7 @@ bool SimplificationPass::SimplifyFunction(Function* function) { if (inst->opcode() == SpvOpCopyObject) { context()->ReplaceAllUsesWithPredicate( inst->result_id(), inst->GetSingleWordInOperand(0), - [](Instruction* user, uint32_t) { + [](Instruction* user) { const auto opcode = user->opcode(); if (!spvOpcodeIsDebug(opcode) && !spvOpcodeIsDecoration(opcode)) { return true; diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp index 1477db44..76f1781a 100644 --- a/source/opt/ssa_rewrite_pass.cpp +++ b/source/opt/ssa_rewrite_pass.cpp @@ -307,8 +307,8 @@ void SSARewriter::ProcessStore(Instruction* inst, BasicBlock* bb) { } if (pass_->IsTargetVar(var_id)) { WriteVariable(var_id, bb, val_id); - pass_->context()->get_debug_info_mgr()->AddDebugValue(inst, var_id, val_id, - inst); + pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible( + inst, var_id, val_id, inst); #if SSA_REWRITE_DEBUGGING_LEVEL > 1 std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': " @@ -491,7 +491,7 @@ bool SSARewriter::ApplyReplacements() { // Add DebugValue for the new OpPhi instruction. insert_it->SetDebugScope(local_var->GetDebugScope()); - pass_->context()->get_debug_info_mgr()->AddDebugValue( + pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible( &*insert_it, phi_candidate->var_id(), phi_candidate->result_id(), &*insert_it); @@ -615,8 +615,6 @@ Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) { << fp->PrettyPrint(0) << "\n"; #endif - if (modified) pass_->context()->KillDebugDeclareInsts(fp); - return modified ? Pass::Status::SuccessWithChange : Pass::Status::SuccessWithoutChange; } @@ -626,6 +624,10 @@ Pass::Status SSARewritePass::Process() { for (auto& fn : *get_module()) { status = CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn)); + // Kill DebugDeclares for target variables. + for (auto var_id : seen_target_vars_) { + context()->get_debug_info_mgr()->KillDebugDeclares(var_id); + } if (status == Status::Failure) { break; } diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp index 57fc49c1..203db87d 100644 --- a/source/opt/struct_cfg_analysis.cpp +++ b/source/opt/struct_cfg_analysis.cpp @@ -128,6 +128,19 @@ uint32_t StructuredCFGAnalysis::MergeBlock(uint32_t bb_id) { return merge_inst->GetSingleWordInOperand(kMergeNodeIndex); } +uint32_t StructuredCFGAnalysis::NestingDepth(uint32_t bb_id) { + uint32_t result = 0; + + // Find the merge block of the current merge construct as long as the block is + // inside a merge construct, exiting one for each iteration. + for (uint32_t merge_block_id = MergeBlock(bb_id); merge_block_id != 0; + merge_block_id = MergeBlock(merge_block_id)) { + result++; + } + + return result; +} + uint32_t StructuredCFGAnalysis::LoopMergeBlock(uint32_t bb_id) { uint32_t header_id = ContainingLoop(bb_id); if (header_id == 0) { @@ -150,6 +163,19 @@ uint32_t StructuredCFGAnalysis::LoopContinueBlock(uint32_t bb_id) { return merge_inst->GetSingleWordInOperand(kContinueNodeIndex); } +uint32_t StructuredCFGAnalysis::LoopNestingDepth(uint32_t bb_id) { + uint32_t result = 0; + + // Find the merge block of the current loop as long as the block is inside a + // loop, exiting a loop for each iteration. + for (uint32_t merge_block_id = LoopMergeBlock(bb_id); merge_block_id != 0; + merge_block_id = LoopMergeBlock(merge_block_id)) { + result++; + } + + return result; +} + uint32_t StructuredCFGAnalysis::SwitchMergeBlock(uint32_t bb_id) { uint32_t header_id = ContainingSwitch(bb_id); if (header_id == 0) { diff --git a/source/opt/struct_cfg_analysis.h b/source/opt/struct_cfg_analysis.h index dfae6d4f..9436b4fb 100644 --- a/source/opt/struct_cfg_analysis.h +++ b/source/opt/struct_cfg_analysis.h @@ -53,6 +53,11 @@ class StructuredCFGAnalysis { // merge construct. uint32_t MergeBlock(uint32_t bb_id); + // Returns the nesting depth of the given block, i.e. the number of merge + // constructs containing it. Headers and merge blocks are not considered part + // of the corresponding merge constructs. + uint32_t NestingDepth(uint32_t block_id); + // Returns the id of the header of the innermost loop construct // that contains |bb_id|. Return |0| if |bb_id| is not contained in any loop // construct. @@ -74,6 +79,13 @@ class StructuredCFGAnalysis { // construct. uint32_t LoopContinueBlock(uint32_t bb_id); + // Returns the loop nesting depth of |bb_id| within its function, i.e. the + // number of loop constructs in which |bb_id| is contained. As per other + // functions in StructuredCFGAnalysis, a loop header is not regarded as being + // part of the loop that it heads, so that e.g. the nesting depth of an + // outer-most loop header is 0. + uint32_t LoopNestingDepth(uint32_t bb_id); + // Returns the id of the header of the innermost switch construct // that contains |bb_id| as long as there is no intervening loop. Returns |0| // if no such construct exists. diff --git a/source/opt/wrap_opkill.cpp b/source/opt/wrap_opkill.cpp index 4d708405..ae1000c7 100644 --- a/source/opt/wrap_opkill.cpp +++ b/source/opt/wrap_opkill.cpp @@ -71,7 +71,7 @@ bool WrapOpKill::ReplaceWithFunctionCall(Instruction* inst) { if (call_inst == nullptr) { return false; } - call_inst->UpdateDebugInfo(inst); + call_inst->UpdateDebugInfoFrom(inst); Instruction* return_inst = nullptr; uint32_t return_type_id = GetOwningFunctionsReturnType(inst); diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt index d945bd20..e113ca25 100644 --- a/source/reduce/CMakeLists.txt +++ b/source/reduce/CMakeLists.txt @@ -50,6 +50,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES operand_to_dominating_id_reduction_opportunity_finder.cpp reducer.cpp reduction_opportunity.cpp + reduction_opportunity_finder.cpp reduction_pass.cpp reduction_util.cpp remove_block_reduction_opportunity.cpp @@ -70,14 +71,14 @@ set(SPIRV_TOOLS_REDUCE_SOURCES simple_conditional_branch_to_branch_reduction_opportunity.cpp ) -if(MSVC) +if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) # Enable parallel builds across four cores for this lib add_definitions(/MP4) endif() spvtools_pch(SPIRV_TOOLS_REDUCE_SOURCES pch_source_reduce) -add_library(SPIRV-Tools-reduce ${SPIRV_TOOLS_REDUCE_SOURCES}) +add_library(SPIRV-Tools-reduce STATIC ${SPIRV_TOOLS_REDUCE_SOURCES}) spvtools_default_compile_options(SPIRV-Tools-reduce) target_include_directories(SPIRV-Tools-reduce @@ -89,7 +90,7 @@ target_include_directories(SPIRV-Tools-reduce ) # The reducer reuses a lot of functionality from the SPIRV-Tools library. target_link_libraries(SPIRV-Tools-reduce - PUBLIC ${SPIRV_TOOLS} + PUBLIC ${SPIRV_TOOLS}-static PUBLIC SPIRV-Tools-opt) set_property(TARGET SPIRV-Tools-reduce PROPERTY FOLDER "SPIRV-Tools libraries") 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 index 0bd93b9b..2cd779a2 100644 --- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp +++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp @@ -20,12 +20,10 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - std::vector<std::unique_ptr<ReductionOpportunity>> ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: - GetAvailableOpportunities(IRContext* context) const { + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; // Find the opportunities for redirecting all false targets before the @@ -34,12 +32,12 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder:: // 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 relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. - Instruction* terminator = block.terminator(); + opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { continue; } 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 index c582a889..17af9b00 100644 --- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h +++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class ConditionalBranchToSimpleConditionalBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; 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 index d744773b..8304c30c 100644 --- a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp +++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp @@ -19,13 +19,10 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - ConditionalBranchToSimpleConditionalBranchReductionOpportunity:: ConditionalBranchToSimpleConditionalBranchReductionOpportunity( - IRContext* context, Instruction* conditional_branch_instruction, - bool redirect_to_true) + opt::IRContext* context, + opt::Instruction* conditional_branch_instruction, bool redirect_to_true) : context_(context), conditional_branch_instruction_(conditional_branch_instruction), redirect_to_true_(redirect_to_true) {} @@ -63,7 +60,8 @@ void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() { context_->cfg()->block(old_successor_block_id)); // We have changed the CFG. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); } } // namespace reduce diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp index 42c78439..a2c3b40a 100644 --- a/source/reduce/merge_blocks_reduction_opportunity.cpp +++ b/source/reduce/merge_blocks_reduction_opportunity.cpp @@ -20,12 +20,8 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::Function; -using opt::IRContext; - MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity( - IRContext* context, Function* function, BasicBlock* block) { + opt::IRContext* context, opt::Function* function, opt::BasicBlock* block) { // Precondition: the terminator has to be OpBranch. assert(block->terminator()->opcode() == SpvOpBranch); context_ = context; @@ -49,7 +45,8 @@ bool MergeBlocksReductionOpportunity::PreconditionHolds() { "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); + opt::BasicBlock* predecessor_block = + context_->get_instr_block(predecessor_id); return opt::blockmergeutil::CanMergeWithSuccessor(context_, predecessor_block); } @@ -70,7 +67,8 @@ void MergeBlocksReductionOpportunity::Apply() { 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); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); return; } } diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp index 89d62632..ea5e9dac 100644 --- a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp +++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp @@ -19,25 +19,23 @@ 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 { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; // Consider every block in every function. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + 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)); + context, function, &block)); } } } diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h index dbf82fec..df7a8bf1 100644 --- a/source/reduce/merge_blocks_reduction_opportunity_finder.h +++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class MergeBlocksReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp index 3e0a2248..eb7498ad 100644 --- a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp @@ -20,11 +20,9 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - std::vector<std::unique_ptr<ReductionOpportunity>> OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; assert(result.empty()); @@ -37,8 +35,8 @@ OperandToConstReductionOpportunityFinder::GetAvailableOpportunities( // contiguous blocks of opportunities early on, and we want to avoid having a // large block of incompatible opportunities if possible. for (const auto& constant : context->GetConstants()) { - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // We iterate through the operands using an explicit index (rather // than using a lambda) so that we use said index in the construction diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h index 93c0dcd3..67267468 100644 --- a/source/reduce/operand_to_const_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class OperandToConstReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp index 13beb890..ca3a99e9 100644 --- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp @@ -20,13 +20,9 @@ namespace spvtools { namespace reduce { -using opt::Function; -using opt::IRContext; -using opt::Instruction; - std::vector<std::unique_ptr<ReductionOpportunity>> OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; // Go through every instruction in every block, considering it as a potential @@ -42,15 +38,15 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( // to prioritise replacing e with its smallest sub-expressions; generalising // this idea to dominating ids this roughly corresponds to more distant // dominators. - for (auto& function : *context->module()) { - for (auto dominating_block = function.begin(); - dominating_block != function.end(); ++dominating_block) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto dominating_block = function->begin(); + dominating_block != function->end(); ++dominating_block) { for (auto& dominating_inst : *dominating_block) { if (dominating_inst.HasResultId() && dominating_inst.type_id()) { // Consider replacing any operand with matching type in a dominated // instruction with the id generated by this instruction. GetOpportunitiesForDominatingInst( - &result, &dominating_inst, dominating_block, &function, context); + &result, &dominating_inst, dominating_block, function, context); } } } @@ -61,9 +57,9 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities( void OperandToDominatingIdReductionOpportunityFinder:: GetOpportunitiesForDominatingInst( std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities, - Instruction* candidate_dominator, - Function::iterator candidate_dominator_block, Function* function, - IRContext* context) const { + opt::Instruction* candidate_dominator, + opt::Function::iterator candidate_dominator_block, + opt::Function* function, opt::IRContext* context) const { assert(candidate_dominator->HasResultId()); assert(candidate_dominator->type_id()); auto dominator_analysis = context->GetDominatorAnalysis(function); @@ -91,8 +87,8 @@ void OperandToDominatingIdReductionOpportunityFinder:: // constant. It is thus not relevant to this pass. continue; } - // Sanity check that we don't get here if the argument is a constant. - assert(!context->get_constant_mgr()->GetConstantFromInst(def)); + assert(!context->get_constant_mgr()->GetConstantFromInst(def) && + "We should not get here if the argument is a constant."); if (def->type_id() != candidate_dominator->type_id()) { // The types need to match. continue; diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h index 7745ff70..5f333705 100644 --- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h @@ -40,7 +40,7 @@ class OperandToDominatingIdReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: void GetOpportunitiesForDominatingInst( diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp index 579b7df6..06bf9550 100644 --- a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp +++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp @@ -20,15 +20,13 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - std::vector<std::unique_ptr<ReductionOpportunity>> OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { // Skip instructions that result in a pointer type. auto type_id = inst.type_id(); diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h index 9cdd8cd5..a5c759e9 100644 --- a/source/reduce/operand_to_undef_reduction_opportunity_finder.h +++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class OperandToUndefReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp index 092d4090..18eeaeb6 100644 --- a/source/reduce/reducer.cpp +++ b/source/reduce/reducer.cpp @@ -183,7 +183,8 @@ Reducer::ReductionResultStatus Reducer::RunPasses( consumer_(SPV_MSG_INFO, nullptr, {}, ("Trying pass " + pass->GetName() + ".").c_str()); do { - auto maybe_result = pass->TryApplyReduction(*current_binary); + auto maybe_result = + pass->TryApplyReduction(*current_binary, options->target_function); if (maybe_result.empty()) { // For this round, the pass has no more opportunities (chunks) to // apply, so move on to the next pass. diff --git a/source/reduce/reduction_opportunity_finder.cpp b/source/reduce/reduction_opportunity_finder.cpp new file mode 100644 index 00000000..0bd253b8 --- /dev/null +++ b/source/reduce/reduction_opportunity_finder.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020 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 "reduction_opportunity_finder.h" + +namespace spvtools { +namespace reduce { + +std::vector<opt::Function*> ReductionOpportunityFinder::GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function) { + std::vector<opt::Function*> result; + for (auto& function : *ir_context->module()) { + if (!target_function || function.result_id() == target_function) { + result.push_back(&function); + } + } + assert((!target_function || !result.empty()) && + "Requested target function must exist."); + return result; +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h index 1837484d..d95c832b 100644 --- a/source/reduce/reduction_opportunity_finder.h +++ b/source/reduce/reduction_opportunity_finder.h @@ -15,6 +15,8 @@ #ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ #define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_ +#include <vector> + #include "source/opt/ir_context.h" #include "source/reduce/reduction_opportunity.h" @@ -29,12 +31,25 @@ class ReductionOpportunityFinder { virtual ~ReductionOpportunityFinder() = default; // Finds and returns the reduction opportunities relevant to this pass that - // could be applied to the given SPIR-V module. + // could be applied to SPIR-V module |context|. + // + // If |target_function| is non-zero then the available opportunities will be + // restricted to only those opportunities that modify the function with result + // id |target_function|. virtual std::vector<std::unique_ptr<ReductionOpportunity>> - GetAvailableOpportunities(opt::IRContext* context) const = 0; + GetAvailableOpportunities(opt::IRContext* context, + uint32_t target_function) const = 0; // Provides a name for the finder. virtual std::string GetName() const = 0; + + protected: + // Requires that |target_function| is zero or the id of a function in + // |ir_context|. If |target_function| is zero, returns all the functions in + // |ir_context|. Otherwise, returns the function with id |target_function|. + // This allows fuzzer passes to restrict attention to a single function. + static std::vector<opt::Function*> GetTargetFunctions( + opt::IRContext* ir_context, uint32_t target_function); }; } // namespace reduce diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp index 2cb986de..c6d1ebfd 100644 --- a/source/reduce/reduction_pass.cpp +++ b/source/reduce/reduction_pass.cpp @@ -22,7 +22,7 @@ namespace spvtools { namespace reduce { std::vector<uint32_t> ReductionPass::TryApplyReduction( - const std::vector<uint32_t>& binary) { + const std::vector<uint32_t>& binary, uint32_t target_function) { // We represent modules as binaries because (a) attempts at reduction need to // end up in binary form to be passed on to SPIR-V-consuming tools, and (b) // when we apply a reduction step we need to do it on a fresh version of the @@ -34,7 +34,7 @@ std::vector<uint32_t> ReductionPass::TryApplyReduction( assert(context); std::vector<std::unique_ptr<ReductionOpportunity>> opportunities = - finder_->GetAvailableOpportunities(context.get()); + finder_->GetAvailableOpportunities(context.get(), target_function); // There is no point in having a granularity larger than the number of // opportunities, so reduce the granularity in this case. diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h index f2d937ba..18361824 100644 --- a/source/reduce/reduction_pass.h +++ b/source/reduce/reduction_pass.h @@ -49,7 +49,12 @@ class ReductionPass { // 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); + // + // If |target_function| is non-zero, only reduction opportunities that + // simplify the internals of the function with result id |target_function| + // will be applied. + std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary, + uint32_t target_function); // Notifies the reduction pass whether the binary returned from // TryApplyReduction is interesting, so that the next call to diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp index 6f128dcb..511f4323 100644 --- a/source/reduce/reduction_util.cpp +++ b/source/reduce/reduction_util.cpp @@ -15,17 +15,73 @@ #include "source/reduce/reduction_util.h" #include "source/opt/ir_context.h" +#include "source/util/make_unique.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) { +uint32_t FindOrCreateGlobalVariable(opt::IRContext* context, + uint32_t pointer_type_id) { + for (auto& inst : context->module()->types_values()) { + if (inst.opcode() != SpvOpVariable) { + continue; + } + if (inst.type_id() == pointer_type_id) { + return inst.result_id(); + } + } + const uint32_t variable_id = context->TakeNextId(); + auto variable_inst = MakeUnique<opt::Instruction>( + context, SpvOpVariable, pointer_type_id, variable_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, + {static_cast<uint32_t>(context->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class())}}})); + context->module()->AddGlobalValue(std::move(variable_inst)); + return variable_id; +} + +uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, + opt::Function* function, + uint32_t pointer_type_id) { + // The pointer type of a function variable must have Function storage class. + assert(context->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class() == SpvStorageClassFunction); + + // Go through the instructions in the function's first block until we find a + // suitable variable, or go past all the variables. + opt::BasicBlock::iterator iter = function->begin()->begin(); + for (;; ++iter) { + // We will either find a suitable variable, or find a non-variable + // instruction; we won't exhaust all instructions. + assert(iter != function->begin()->end()); + if (iter->opcode() != SpvOpVariable) { + // If we see a non-variable, we have gone through all the variables. + break; + } + if (iter->type_id() == pointer_type_id) { + return iter->result_id(); + } + } + // At this point, iter refers to the first non-function instruction of the + // function's entry block. + const uint32_t variable_id = context->TakeNextId(); + auto variable_inst = MakeUnique<opt::Instruction>( + context, SpvOpVariable, pointer_type_id, variable_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); + iter->InsertBefore(std::move(variable_inst)); + return variable_id; +} + +uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id) { for (auto& inst : context->module()->types_values()) { if (inst.opcode() != SpvOpUndef) { continue; @@ -34,11 +90,9 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t 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, {})); + auto undef_inst = MakeUnique<opt::Instruction>( + context, SpvOpUndef, type_id, undef_id, opt::Instruction::OperandList()); assert(undef_id == undef_inst->result_id()); context->module()->AddGlobalValue(std::move(undef_inst)); return undef_id; @@ -46,8 +100,8 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) { void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, opt::BasicBlock* to_block) { - to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) { - Instruction::OperandList new_in_operands; + to_block->ForEachPhiInst([&from_id](opt::Instruction* phi_inst) { + opt::Instruction::OperandList new_in_operands; // Go through the OpPhi's input operands in (variable, parent) pairs. for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) { // Keep all pairs where the parent is not the block from which the edge diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h index 7e7e153a..bcdb77cd 100644 --- a/source/reduce/reduction_util.h +++ b/source/reduce/reduction_util.h @@ -26,6 +26,16 @@ namespace reduce { extern const uint32_t kTrueBranchOperandIndex; extern const uint32_t kFalseBranchOperandIndex; +// Returns a global OpVariable of type |pointer_type_id|, adding one if none +// exist. +uint32_t FindOrCreateGlobalVariable(opt::IRContext* context, + uint32_t pointer_type_id); + +// Returns an OpVariable of type |pointer_type_id| declared in |function|, +// adding one if none exist. +uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, opt::Function*, + uint32_t pointer_type_id); + // 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); diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp index 3ad7f72c..aa481059 100644 --- a/source/reduce/remove_block_reduction_opportunity.cpp +++ b/source/reduce/remove_block_reduction_opportunity.cpp @@ -19,11 +19,8 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::Function; - RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity( - Function* function, BasicBlock* block) + opt::Function* function, opt::BasicBlock* block) : function_(function), block_(block) { // precondition: assert(block_->begin() != block_->end() && diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp index a3f873f3..27a4570c 100644 --- a/source/reduce/remove_block_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp @@ -19,25 +19,21 @@ 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 { + opt::IRContext* context, uint32_t target_function) 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)); + // Consider every block in every relevant function. + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto bi = function->begin(); bi != function->end(); ++bi) { + if (IsBlockValidOpportunity(context, function, &bi)) { + result.push_back( + MakeUnique<RemoveBlockReductionOpportunity>(function, &*bi)); } } } @@ -45,21 +41,22 @@ RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities( } bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( - IRContext* context, Function& function, Function::iterator& bi) { - assert(bi != function.end() && "Block iterator was out of bounds"); + opt::IRContext* context, opt::Function* function, + opt::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()) { + if (*bi == function->begin()) { return false; } // Don't remove blocks with references. - if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) { + 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)) { + if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) { return false; } @@ -67,19 +64,19 @@ bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity( } bool RemoveBlockReductionOpportunityFinder:: - BlockInstructionsHaveNoOutsideReferences(IRContext* context, - const Function::iterator& bi) { + BlockInstructionsHaveNoOutsideReferences( + opt::IRContext* context, const opt::Function::iterator& bi) { // Get all instructions in block. std::unordered_set<uint32_t> instructions_in_block; - for (const Instruction& instruction : *bi) { + for (const opt::Instruction& instruction : *bi) { instructions_in_block.insert(instruction.unique_id()); } // For each instruction... - for (const Instruction& instruction : *bi) { + for (const opt::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 { + &instruction, [&instructions_in_block](opt::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()) != diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h index 83cd04b5..d347bf91 100644 --- a/source/reduce/remove_block_reduction_opportunity_finder.h +++ b/source/reduce/remove_block_reduction_opportunity_finder.h @@ -34,14 +34,14 @@ class RemoveBlockReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) 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); + 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. diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp index 1edb9733..1d8d9726 100644 --- a/source/reduce/remove_function_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp @@ -21,7 +21,14 @@ namespace reduce { std::vector<std::unique_ptr<ReductionOpportunity>> RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // If we are targeting a specific function then we are only interested in + // opportunities that simplify the internals of that function; removing + // whole functions does not fit the bill. + return {}; + } + std::vector<std::unique_ptr<ReductionOpportunity>> result; // Consider each function. for (auto& function : *context->module()) { diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h index 7952a229..6fcfb779 100644 --- a/source/reduce/remove_function_reduction_opportunity_finder.h +++ b/source/reduce/remove_function_reduction_opportunity_finder.h @@ -31,7 +31,7 @@ class RemoveFunctionReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp index 45821e2a..74df1b8d 100644 --- a/source/reduce/remove_selection_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp @@ -19,10 +19,6 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::IRContext; -using opt::Instruction; - namespace { const uint32_t kMergeNodeIndex = 0; const uint32_t kContinueNodeIndex = 1; @@ -34,11 +30,11 @@ std::string RemoveSelectionReductionOpportunityFinder::GetName() const { std::vector<std::unique_ptr<ReductionOpportunity>> RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) 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) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { if (auto merge_instruction = block.GetMergeInst()) { if (merge_instruction->opcode() == SpvOpLoopMerge) { uint32_t merge_block_id = @@ -73,8 +69,8 @@ RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities( } bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved( - IRContext* context, const BasicBlock& header_block, - Instruction* merge_instruction, + opt::IRContext* context, const opt::BasicBlock& header_block, + opt::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 " @@ -122,7 +118,7 @@ bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved( merge_instruction->GetSingleWordOperand(kMergeNodeIndex); for (uint32_t predecessor_block_id : context->cfg()->preds(merge_block_id)) { - const BasicBlock* predecessor_block = + const opt::BasicBlock* predecessor_block = context->cfg()->block(predecessor_block_id); assert(predecessor_block); bool found_divergent_successor = false; diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h index 848122b8..1a174933 100644 --- a/source/reduce/remove_selection_reduction_opportunity_finder.h +++ b/source/reduce/remove_selection_reduction_opportunity_finder.h @@ -33,7 +33,7 @@ class RemoveSelectionReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; // Returns true if the OpSelectionMerge instruction |merge_instruction| in // block |header_block| can be removed. diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp index 91ec542c..d7bb3a82 100644 --- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp @@ -28,61 +28,72 @@ RemoveUnusedInstructionReductionOpportunityFinder:: std::vector<std::unique_ptr<ReductionOpportunity>> RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; - for (auto& inst : context->module()->debugs1()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; - } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); - } + if (!target_function) { + // We are not restricting reduction to a specific function, so we consider + // unused instructions defined outside functions. - for (auto& inst : context->module()->debugs2()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; + for (auto& inst : context->module()->debugs1()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); - } - for (auto& inst : context->module()->debugs3()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; + for (auto& inst : context->module()->debugs2()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); - } - for (auto& inst : context->module()->ext_inst_debuginfo()) { - if (context->get_def_use_mgr()->NumUses(&inst) > 0) { - continue; + for (auto& inst : context->module()->debugs3()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); - } - for (auto& inst : context->module()->types_values()) { - if (!remove_constants_and_undefs_ && - spvOpcodeIsConstantOrUndef(inst.opcode())) { - continue; - } - if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, - inst)) { - continue; + for (auto& inst : context->module()->ext_inst_debuginfo()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); - } - for (auto& inst : context->module()->annotations()) { - if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { - continue; + for (auto& inst : context->module()->types_values()) { + if (!remove_constants_and_undefs_ && + spvOpcodeIsConstantOrUndef(inst.opcode())) { + continue; + } + if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context, + inst)) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - if (!IsIndependentlyRemovableDecoration(inst)) { - continue; + + for (auto& inst : context->module()->annotations()) { + if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { + continue; + } + if (!IsIndependentlyRemovableDecoration(inst)) { + continue; + } + result.push_back( + MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst)); } - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { for (auto& inst : block) { if (context->get_def_use_mgr()->NumUses(&inst) > 0) { continue; diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h index cbf6a5bd..03236400 100644 --- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h +++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h @@ -38,7 +38,7 @@ class RemoveUnusedInstructionReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // Returns true if and only if the only uses of |inst| are by decorations that diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp index 39ce47f3..e72be625 100644 --- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp +++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp @@ -24,7 +24,14 @@ namespace reduce { std::vector<std::unique_ptr<ReductionOpportunity>> RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities( - opt::IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { + if (target_function) { + // Removing an unused struct member is a global change, as struct types are + // global. We thus do not consider such opportunities if we are targeting + // a specific function. + return {}; + } + std::vector<std::unique_ptr<ReductionOpportunity>> result; // We track those struct members that are never accessed. We do this by diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h index 13f40172..98f9c019 100644 --- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h +++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h @@ -32,7 +32,7 @@ class RemoveUnusedStructMemberReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: // A helper method to update |unused_members_to_structs| by removing from it diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp index 17a5c7e4..d867c3ad 100644 --- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp +++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp @@ -20,20 +20,17 @@ namespace spvtools { namespace reduce { -using opt::IRContext; -using opt::Instruction; - std::vector<std::unique_ptr<ReductionOpportunity>> SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; // Consider every function. - for (auto& function : *context->module()) { + for (auto* function : GetTargetFunctions(context, target_function)) { // Consider every block in the function. - for (auto& block : function) { + for (auto& block : *function) { // The terminator must be SpvOpBranchConditional. - Instruction* terminator = block.terminator(); + opt::Instruction* terminator = block.terminator(); if (terminator->opcode() != SpvOpBranchConditional) { continue; } diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h index 10b9dce4..8869908b 100644 --- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h +++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h @@ -26,7 +26,7 @@ class SimpleConditionalBranchToBranchOpportunityFinder : public ReductionOpportunityFinder { public: std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const override; + opt::IRContext* context, uint32_t target_function) const override; std::string GetName() const override; }; diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp index 8968b962..ca17f9eb 100644 --- a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp +++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp @@ -19,11 +19,9 @@ namespace spvtools { namespace reduce { -using namespace opt; - SimpleConditionalBranchToBranchReductionOpportunity:: SimpleConditionalBranchToBranchReductionOpportunity( - Instruction* conditional_branch_instruction) + opt::Instruction* conditional_branch_instruction) : conditional_branch_instruction_(conditional_branch_instruction) {} bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() { diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp index 88ea38e7..0c004439 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp @@ -21,11 +21,6 @@ namespace spvtools { namespace reduce { -using opt::BasicBlock; -using opt::IRContext; -using opt::Instruction; -using opt::Operand; - namespace { const uint32_t kMergeNodeIndex = 0; } // namespace @@ -58,14 +53,16 @@ void StructuredLoopToSelectionReductionOpportunity::Apply() { // We have made control flow changes that do not preserve the analyses that // were performed. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); // (4) By changing CFG edges we may have created scenarios where ids are used // without being dominated; we fix instances of this. FixNonDominatedIdUses(); // Invalidate the analyses we just used. - context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + context_->InvalidateAnalysesExceptFor( + opt::IRContext::Analysis::kAnalysisNone); } void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock( @@ -168,13 +165,14 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge( } void StructuredLoopToSelectionReductionOpportunity:: - AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) { - to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) { + AdaptPhiInstructionsForAddedEdge(uint32_t from_id, + opt::BasicBlock* to_block) { + to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) { // Add to the phi operand an (undef, from_id) pair to reflect the added // edge. 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})); + phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id})); + phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id})); }); } @@ -227,7 +225,7 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { continue; } context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def]( - Instruction* use, + opt::Instruction* use, uint32_t index) { // Ignore uses outside of blocks, such as in OpDecorate. if (context_->get_instr_block(use) == nullptr) { @@ -245,17 +243,20 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { case SpvStorageClassFunction: use->SetOperand( index, {FindOrCreateFunctionVariable( + context_, enclosing_function_, context_->get_type_mgr()->GetId(pointer_type))}); break; default: // TODO(2183) Need to think carefully about whether it makes - // sense to add new variables for all storage classes; it's fine - // for Private but might not be OK for input/output storage - // classes for example. + // sense to add new variables for all storage classes; it's + // fine for Private but might not be OK for input/output + // storage classes for example. use->SetOperand( index, {FindOrCreateGlobalVariable( + context_, context_->get_type_mgr()->GetId(pointer_type))}); break; + break; } } else { use->SetOperand(index, @@ -268,9 +269,10 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { } bool StructuredLoopToSelectionReductionOpportunity:: - DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use, + DefinitionSufficientlyDominatesUse(opt::Instruction* def, + opt::Instruction* use, uint32_t use_index, - BasicBlock& def_block) { + opt::BasicBlock& def_block) { if (use->opcode() == SpvOpPhi) { // A use in a phi doesn't need to be dominated by its definition, but the // associated parent block does need to be dominated by the definition. @@ -282,62 +284,5 @@ bool StructuredLoopToSelectionReductionOpportunity:: ->Dominates(def, use); } -uint32_t -StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable( - uint32_t pointer_type_id) { - for (auto& inst : context_->module()->types_values()) { - if (inst.opcode() != SpvOpVariable) { - continue; - } - if (inst.type_id() == pointer_type_id) { - return inst.result_id(); - } - } - const uint32_t variable_id = context_->TakeNextId(); - std::unique_ptr<Instruction> variable_inst( - new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, - {(uint32_t)context_->get_type_mgr() - ->GetType(pointer_type_id) - ->AsPointer() - ->storage_class()}}})); - context_->module()->AddGlobalValue(std::move(variable_inst)); - return variable_id; -} - -uint32_t -StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable( - uint32_t pointer_type_id) { - // The pointer type of a function variable must have Function storage class. - assert(context_->get_type_mgr() - ->GetType(pointer_type_id) - ->AsPointer() - ->storage_class() == SpvStorageClassFunction); - - // Go through the instructions in the function's first block until we find a - // suitable variable, or go past all the variables. - BasicBlock::iterator iter = enclosing_function_->begin()->begin(); - for (;; ++iter) { - // We will either find a suitable variable, or find a non-variable - // instruction; we won't exhaust all instructions. - assert(iter != enclosing_function_->begin()->end()); - if (iter->opcode() != SpvOpVariable) { - // If we see a non-variable, we have gone through all the variables. - break; - } - if (iter->type_id() == pointer_type_id) { - return iter->result_id(); - } - } - // At this point, iter refers to the first non-function instruction of the - // function's entry block. - const uint32_t variable_id = context_->TakeNextId(); - std::unique_ptr<Instruction> variable_inst(new Instruction( - context_, SpvOpVariable, pointer_type_id, variable_id, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); - iter->InsertBefore(std::move(variable_inst)); - return variable_id; -} - } // namespace reduce } // namespace spvtools diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h index 564811f1..4c576196 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h @@ -72,8 +72,8 @@ class StructuredLoopToSelectionReductionOpportunity void ChangeLoopToSelection(); // Fixes any scenarios where, due to CFG changes, ids have uses not dominated - // by their definitions, by changing such uses to uses of OpUndef or of dummy - // variables. + // by their definitions, by changing such uses to uses of OpUndef or of + // placeholder variables. void FixNonDominatedIdUses(); // Returns true if and only if at least one of the following holds: @@ -86,20 +86,6 @@ class StructuredLoopToSelectionReductionOpportunity uint32_t use_index, 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. - // - // TODO(2184): This will likely be used by other reduction passes, so should - // be factored out in due course. - uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id); - - // Checks whether the enclosing function has an OpVariable of the given - // pointer type, adding one if not, and returns the id of such an OpVariable. - // - // TODO(2184): This will likely be used by other reduction passes, so should - // be factored out in due course. - uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id); - opt::IRContext* context_; opt::BasicBlock* loop_construct_header_; opt::Function* enclosing_function_; diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp index 085b2672..fdf3ab04 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp @@ -19,8 +19,6 @@ namespace spvtools { namespace reduce { -using opt::IRContext; - namespace { const uint32_t kMergeNodeIndex = 0; const uint32_t kContinueNodeIndex = 1; @@ -28,12 +26,12 @@ const uint32_t kContinueNodeIndex = 1; std::vector<std::unique_ptr<ReductionOpportunity>> StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( - IRContext* context) const { + opt::IRContext* context, uint32_t target_function) const { std::vector<std::unique_ptr<ReductionOpportunity>> result; std::set<uint32_t> merge_block_ids; - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto merge_block_id = block.MergeBlockIdIfAny(); if (merge_block_id) { merge_block_ids.insert(merge_block_id); @@ -42,8 +40,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( } // Consider each loop construct header in the module. - for (auto& function : *context->module()) { - for (auto& block : function) { + for (auto* function : GetTargetFunctions(context, target_function)) { + for (auto& block : *function) { auto loop_merge_inst = block.GetLoopMergeInst(); if (!loop_merge_inst) { // This is not a loop construct header. @@ -71,8 +69,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // so we cautiously do not consider applying a transformation. auto merge_block_id = loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex); - if (!context->GetDominatorAnalysis(&function)->Dominates( - block.id(), merge_block_id)) { + if (!context->GetDominatorAnalysis(function)->Dominates(block.id(), + merge_block_id)) { continue; } @@ -80,7 +78,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // construct header. If not (e.g. because the loop contains OpReturn, // OpKill or OpUnreachable), we cautiously do not consider applying // a transformation. - if (!context->GetPostDominatorAnalysis(&function)->Dominates( + if (!context->GetPostDominatorAnalysis(function)->Dominates( merge_block_id, block.id())) { continue; } @@ -89,7 +87,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities( // opportunity to do so. result.push_back( MakeUnique<StructuredLoopToSelectionReductionOpportunity>( - context, &block, &function)); + context, &block, function)); } } return result; diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h index d63d4340..6166af38 100644 --- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h @@ -46,7 +46,7 @@ class StructuredLoopToSelectionReductionOpportunityFinder std::string GetName() const final; std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - opt::IRContext* context) const final; + opt::IRContext* context, uint32_t target_function) const final; private: }; diff --git a/source/spirv_fuzzer_options.cpp b/source/spirv_fuzzer_options.cpp index 64fefbc2..3f62e0e0 100644 --- a/source/spirv_fuzzer_options.cpp +++ b/source/spirv_fuzzer_options.cpp @@ -25,7 +25,8 @@ spv_fuzzer_options_t::spv_fuzzer_options_t() replay_range(0), replay_validation_enabled(false), shrinker_step_limit(kDefaultStepLimit), - fuzzer_pass_validation_enabled(false) {} + fuzzer_pass_validation_enabled(false), + all_passes_enabled(false) {} SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() { return new spv_fuzzer_options_t(); @@ -60,3 +61,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( spv_fuzzer_options options) { options->fuzzer_pass_validation_enabled = true; } + +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses( + spv_fuzzer_options options) { + options->all_passes_enabled = true; +} diff --git a/source/spirv_fuzzer_options.h b/source/spirv_fuzzer_options.h index 0db16a30..bb8d9103 100644 --- a/source/spirv_fuzzer_options.h +++ b/source/spirv_fuzzer_options.h @@ -40,6 +40,9 @@ struct spv_fuzzer_options_t { // See spvFuzzerOptionsValidateAfterEveryPass. bool fuzzer_pass_validation_enabled; + + // See spvFuzzerOptionsEnableAllPasses. + bool all_passes_enabled; }; #endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_ diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp index e8078753..9086433e 100644 --- a/source/spirv_reducer_options.cpp +++ b/source/spirv_reducer_options.cpp @@ -23,7 +23,9 @@ const uint32_t kDefaultStepLimit = 2500; } // namespace spv_reducer_options_t::spv_reducer_options_t() - : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {} + : step_limit(kDefaultStepLimit), + fail_on_validation_error(false), + target_function(0) {} SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() { return new spv_reducer_options_t(); @@ -42,3 +44,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError( spv_reducer_options options, bool fail_on_validation_error) { options->fail_on_validation_error = fail_on_validation_error; } + +SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction( + spv_reducer_options options, uint32_t target_function) { + options->target_function = target_function; +} diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h index 1a431cce..911747dd 100644 --- a/source/spirv_reducer_options.h +++ b/source/spirv_reducer_options.h @@ -30,6 +30,9 @@ struct spv_reducer_options_t { // See spvReducerOptionsSetFailOnValidationError. bool fail_on_validation_error; + + // See spvReducerOptionsSetTargetFunction. + uint32_t target_function; }; #endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_ diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp index d86c91e4..1d7017d1 100644 --- a/source/val/validate_builtins.cpp +++ b/source/val/validate_builtins.cpp @@ -205,6 +205,14 @@ class BuiltInsValidator { const Decoration& decoration, const Instruction& inst); spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateBaseInstanceOrVertexAtDefinition( + const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateDrawIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateViewIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateDeviceIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition( const Decoration& decoration, const Instruction& inst); @@ -339,6 +347,26 @@ class BuiltInsValidator { const Instruction& referenced_inst, const Instruction& referenced_from_inst); + spv_result_t ValidateBaseInstanceOrVertexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateDrawIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateViewIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateDeviceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. spv_result_t ValidateComputeShaderI32Vec3InputAtReference( const Decoration& decoration, const Instruction& built_in_inst, @@ -365,7 +393,7 @@ class BuiltInsValidator { // |referenced_from_inst| - instruction which references id defined by // |referenced_inst| from within a function. spv_result_t ValidateNotCalledWithExecutionModel( - const char* comment, SpvExecutionModel execution_model, + std::string comment, SpvExecutionModel execution_model, const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst); @@ -864,7 +892,7 @@ spv_result_t BuiltInsValidator::ValidateF32ArrHelper( } spv_result_t BuiltInsValidator::ValidateNotCalledWithExecutionModel( - const char* comment, SpvExecutionModel execution_model, + std::string comment, SpvExecutionModel execution_model, const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst) { @@ -903,6 +931,7 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; if (spvIsVulkanEnv(_.context()->target_env)) { const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); if (storage_class != SpvStorageClassMax && @@ -911,7 +940,7 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be only used for variables with Input or Output storage " "class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -949,7 +978,12 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( decoration, built_in_inst, /* Any number of components */ 0, [this, &decoration, &referenced_from_inst]( const std::string& message) -> spv_result_t { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInClipDistance) + ? 4191 + : 4200; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "According to the Vulkan spec BuiltIn " << _.grammar().lookupOperandName( SPV_OPERAND_TYPE_BUILT_IN, @@ -971,8 +1005,13 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( decoration, built_in_inst, /* Any number of components */ 0, [this, &decoration, &referenced_from_inst]( const std::string& message) -> spv_result_t { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInClipDistance) + ? 4191 + : 4200; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "According to the Vulkan spec BuiltIn " << _.grammar().lookupOperandName( SPV_OPERAND_TYPE_BUILT_IN, @@ -987,8 +1026,13 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( decoration, built_in_inst, /* Any number of components */ 0, [this, &decoration, &referenced_from_inst]( const std::string& message) -> spv_result_t { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInClipDistance) + ? 4191 + : 4200; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "According to the Vulkan spec BuiltIn " << _.grammar().lookupOperandName( SPV_OPERAND_TYPE_BUILT_IN, @@ -1003,10 +1047,12 @@ spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( } default: { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInClipDistance) ? 4187 : 4196; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << "Vulkan spec allows BuiltIn " + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be used only with Fragment, Vertex, " "TessellationControl, TessellationEvaluation or Geometry " "execution models. " @@ -1035,7 +1081,7 @@ spv_result_t BuiltInsValidator::ValidateFragCoordAtDefinition( decoration, inst, 4, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4212) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn FragCoord " "variable needs to be a 4-component 32-bit float " @@ -1059,7 +1105,7 @@ spv_result_t BuiltInsValidator::ValidateFragCoordAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(4211) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FragCoord to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1070,6 +1116,7 @@ spv_result_t BuiltInsValidator::ValidateFragCoordAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4210) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FragCoord to be used only with " "Fragment execution model. " @@ -1096,7 +1143,7 @@ spv_result_t BuiltInsValidator::ValidateFragDepthAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4215) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn FragDepth " "variable needs to be a 32-bit float scalar. " @@ -1119,7 +1166,7 @@ spv_result_t BuiltInsValidator::ValidateFragDepthAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassOutput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(4214) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FragDepth to be only used for " "variables with Output storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1130,6 +1177,7 @@ spv_result_t BuiltInsValidator::ValidateFragDepthAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4213) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FragDepth to be used only with " "Fragment execution model. " @@ -1144,6 +1192,7 @@ spv_result_t BuiltInsValidator::ValidateFragDepthAtReference( const auto* modes = _.GetExecutionModes(entry_point); if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4216) << spvLogStringForEnv(_.context()->target_env) << " spec requires DepthReplacing execution mode to be " "declared when using BuiltIn FragDepth. " @@ -1170,7 +1219,7 @@ spv_result_t BuiltInsValidator::ValidateFrontFacingAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4231) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn FrontFacing " "variable needs to be a bool scalar. " @@ -1193,7 +1242,7 @@ spv_result_t BuiltInsValidator::ValidateFrontFacingAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(4230) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FrontFacing to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1204,6 +1253,7 @@ spv_result_t BuiltInsValidator::ValidateFrontFacingAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4229) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn FrontFacing to be used only with " "Fragment execution model. " @@ -1230,6 +1280,7 @@ spv_result_t BuiltInsValidator::ValidateHelperInvocationAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4241) << "According to the Vulkan spec BuiltIn HelperInvocation " "variable needs to be a bool scalar. " << message; @@ -1251,6 +1302,7 @@ spv_result_t BuiltInsValidator::ValidateHelperInvocationAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4240) << "Vulkan spec allows BuiltIn HelperInvocation to be only used " "for variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1261,6 +1313,7 @@ spv_result_t BuiltInsValidator::ValidateHelperInvocationAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4239) << "Vulkan spec allows BuiltIn HelperInvocation to be used only " "with Fragment execution model. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1287,6 +1340,7 @@ spv_result_t BuiltInsValidator::ValidateInvocationIdAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4259) << "According to the Vulkan spec BuiltIn InvocationId " "variable needs to be a 32-bit int scalar. " << message; @@ -1308,6 +1362,7 @@ spv_result_t BuiltInsValidator::ValidateInvocationIdAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4258) << "Vulkan spec allows BuiltIn InvocationId to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1319,6 +1374,7 @@ spv_result_t BuiltInsValidator::ValidateInvocationIdAtReference( if (execution_model != SpvExecutionModelTessellationControl && execution_model != SpvExecutionModelGeometry) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4257) << "Vulkan spec allows BuiltIn InvocationId to be used only " "with TessellationControl or Geometry execution models. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1344,7 +1400,7 @@ spv_result_t BuiltInsValidator::ValidateInstanceIndexAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4265) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn InstanceIndex " "variable needs to be a 32-bit int scalar. " @@ -1367,7 +1423,7 @@ spv_result_t BuiltInsValidator::ValidateInstanceIndexAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(4264) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn InstanceIndex to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1378,6 +1434,7 @@ spv_result_t BuiltInsValidator::ValidateInstanceIndexAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelVertex) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4263) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn InstanceIndex to be used only " "with Vertex execution model. " @@ -1404,6 +1461,7 @@ spv_result_t BuiltInsValidator::ValidatePatchVerticesAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4310) << "According to the Vulkan spec BuiltIn PatchVertices " "variable needs to be a 32-bit int scalar. " << message; @@ -1425,6 +1483,7 @@ spv_result_t BuiltInsValidator::ValidatePatchVerticesAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4309) << "Vulkan spec allows BuiltIn PatchVertices to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1436,6 +1495,7 @@ spv_result_t BuiltInsValidator::ValidatePatchVerticesAtReference( if (execution_model != SpvExecutionModelTessellationControl && execution_model != SpvExecutionModelTessellationEvaluation) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4308) << "Vulkan spec allows BuiltIn PatchVertices to be used only " "with TessellationControl or TessellationEvaluation " "execution models. " @@ -1462,6 +1522,7 @@ spv_result_t BuiltInsValidator::ValidatePointCoordAtDefinition( decoration, inst, 2, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4313) << "According to the Vulkan spec BuiltIn PointCoord " "variable needs to be a 2-component 32-bit float " "vector. " @@ -1484,6 +1545,7 @@ spv_result_t BuiltInsValidator::ValidatePointCoordAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4312) << "Vulkan spec allows BuiltIn PointCoord to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1494,6 +1556,7 @@ spv_result_t BuiltInsValidator::ValidatePointCoordAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4311) << "Vulkan spec allows BuiltIn PointCoord to be used only with " "Fragment execution model. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1528,6 +1591,7 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( storage_class != SpvStorageClassInput && storage_class != SpvStorageClassOutput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4316) << "Vulkan spec allows BuiltIn PointSize to be only used for " "variables with Input or Output storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1539,8 +1603,11 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( assert(function_id_ == 0); id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, - "Vulkan spec doesn't allow BuiltIn PointSize to be used for " - "variables with Input storage class if execution model is Vertex.", + std::string( + _.VkErrorID(4315) + + "Vulkan spec doesn't allow BuiltIn PointSize to be used for " + "variables with Input storage class if execution model is " + "Vertex."), SpvExecutionModelVertex, decoration, built_in_inst, referenced_from_inst, std::placeholders::_1)); } @@ -1553,6 +1620,7 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( [this, &referenced_from_inst]( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4317) << "According to the Vulkan spec BuiltIn PointSize " "variable needs to be a 32-bit float scalar. " << message; @@ -1576,6 +1644,7 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4317) << "According to the Vulkan spec BuiltIn " "PointSize variable needs to be a 32-bit " "float scalar. " @@ -1590,6 +1659,7 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4317) << "According to the Vulkan spec BuiltIn " "PointSize variable needs to be a 32-bit " "float scalar. " @@ -1603,6 +1673,7 @@ spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( default: { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4314) << "Vulkan spec allows BuiltIn PointSize to be used only with " "Vertex, TessellationControl, TessellationEvaluation or " "Geometry execution models. " @@ -1650,8 +1721,10 @@ spv_result_t BuiltInsValidator::ValidatePositionAtReference( assert(function_id_ == 0); id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, - "Vulkan spec doesn't allow BuiltIn Position to be used for variables " - "with Input storage class if execution model is Vertex.", + std::string(_.VkErrorID(4320) + + "Vulkan spec doesn't allow BuiltIn Position to be used " + "for variables " + "with Input storage class if execution model is Vertex."), SpvExecutionModelVertex, decoration, built_in_inst, referenced_from_inst, std::placeholders::_1)); } @@ -1664,6 +1737,7 @@ spv_result_t BuiltInsValidator::ValidatePositionAtReference( [this, &referenced_from_inst]( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4321) << "According to the Vulkan spec BuiltIn Position " "variable needs to be a 4-component 32-bit float " "vector. " @@ -1690,6 +1764,7 @@ spv_result_t BuiltInsValidator::ValidatePositionAtReference( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4321) << "According to the Vulkan spec BuiltIn Position " "variable needs to be a 4-component 32-bit " "float vector. " @@ -1704,6 +1779,7 @@ spv_result_t BuiltInsValidator::ValidatePositionAtReference( const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4321) << "According to the Vulkan spec BuiltIn Position " "variable needs to be a 4-component 32-bit " "float vector. " @@ -1717,6 +1793,7 @@ spv_result_t BuiltInsValidator::ValidatePositionAtReference( default: { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4318) << "Vulkan spec allows BuiltIn Position to be used only " "with Vertex, TessellationControl, TessellationEvaluation" " or Geometry execution models. " @@ -1788,6 +1865,7 @@ spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4337) << "According to the Vulkan spec BuiltIn PrimitiveId " "variable needs to be a 32-bit int scalar. " << message; @@ -1799,6 +1877,7 @@ spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4337) << "According to the Vulkan spec BuiltIn PrimitiveId " "variable needs to be a 32-bit int scalar. " << message; @@ -1833,23 +1912,29 @@ spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtReference( assert(function_id_ == 0); id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, - "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " - "variables with Output storage class if execution model is " - "TessellationControl.", + std::string( + _.VkErrorID(4334) + + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is " + "TessellationControl."), SpvExecutionModelTessellationControl, decoration, built_in_inst, referenced_from_inst, std::placeholders::_1)); id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, - "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " - "variables with Output storage class if execution model is " - "TessellationEvaluation.", + std::string( + _.VkErrorID(4334) + + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is " + "TessellationEvaluation."), SpvExecutionModelTessellationEvaluation, decoration, built_in_inst, referenced_from_inst, std::placeholders::_1)); id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, - "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " - "variables with Output storage class if execution model is " - "Fragment.", + std::string( + _.VkErrorID(4334) + + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is " + "Fragment."), SpvExecutionModelFragment, decoration, built_in_inst, referenced_from_inst, std::placeholders::_1)); } @@ -1873,6 +1958,7 @@ spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtReference( default: { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4330) << "Vulkan spec allows BuiltIn PrimitiveId to be used only " "with Fragment, TessellationControl, " "TessellationEvaluation or Geometry execution models. " @@ -1900,6 +1986,7 @@ spv_result_t BuiltInsValidator::ValidateSampleIdAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4356) << "According to the Vulkan spec BuiltIn SampleId " "variable needs to be a 32-bit int scalar. " << message; @@ -1921,6 +2008,7 @@ spv_result_t BuiltInsValidator::ValidateSampleIdAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4355) << "Vulkan spec allows BuiltIn SampleId to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1931,6 +2019,7 @@ spv_result_t BuiltInsValidator::ValidateSampleIdAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4354) << "Vulkan spec allows BuiltIn SampleId to be used only with " "Fragment execution model. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1956,6 +2045,7 @@ spv_result_t BuiltInsValidator::ValidateSampleMaskAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4359) << "According to the Vulkan spec BuiltIn SampleMask " "variable needs to be a 32-bit int array. " << message; @@ -1978,6 +2068,7 @@ spv_result_t BuiltInsValidator::ValidateSampleMaskAtReference( storage_class != SpvStorageClassInput && storage_class != SpvStorageClassOutput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4358) << "Vulkan spec allows BuiltIn SampleMask to be only used for " "variables with Input or Output storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -1988,6 +2079,7 @@ spv_result_t BuiltInsValidator::ValidateSampleMaskAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4357) << "Vulkan spec allows BuiltIn SampleMask to be used only " "with " "Fragment execution model. " @@ -2014,6 +2106,7 @@ spv_result_t BuiltInsValidator::ValidateSamplePositionAtDefinition( decoration, inst, 2, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4362) << "According to the Vulkan spec BuiltIn SamplePosition " "variable needs to be a 2-component 32-bit float " "vector. " @@ -2036,6 +2129,7 @@ spv_result_t BuiltInsValidator::ValidateSamplePositionAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4361) << "Vulkan spec allows BuiltIn SamplePosition to be only used " "for " "variables with Input storage class. " @@ -2047,6 +2141,7 @@ spv_result_t BuiltInsValidator::ValidateSamplePositionAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelFragment) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4360) << "Vulkan spec allows BuiltIn SamplePosition to be used only " "with " "Fragment execution model. " @@ -2073,6 +2168,7 @@ spv_result_t BuiltInsValidator::ValidateTessCoordAtDefinition( decoration, inst, 3, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4389) << "According to the Vulkan spec BuiltIn TessCoord " "variable needs to be a 3-component 32-bit float " "vector. " @@ -2095,6 +2191,7 @@ spv_result_t BuiltInsValidator::ValidateTessCoordAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4388) << "Vulkan spec allows BuiltIn TessCoord to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2105,6 +2202,7 @@ spv_result_t BuiltInsValidator::ValidateTessCoordAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelTessellationEvaluation) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4387) << "Vulkan spec allows BuiltIn TessCoord to be used only with " "TessellationEvaluation execution model. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2130,6 +2228,7 @@ spv_result_t BuiltInsValidator::ValidateTessLevelOuterAtDefinition( decoration, inst, 4, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4393) << "According to the Vulkan spec BuiltIn TessLevelOuter " "variable needs to be a 4-component 32-bit float " "array. " @@ -2150,6 +2249,7 @@ spv_result_t BuiltInsValidator::ValidateTessLevelInnerAtDefinition( decoration, inst, 2, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4397) << "According to the Vulkan spec BuiltIn TessLevelOuter " "variable needs to be a 2-component 32-bit float " "array. " @@ -2167,6 +2267,7 @@ spv_result_t BuiltInsValidator::ValidateTessLevelAtReference( const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; if (spvIsVulkanEnv(_.context()->target_env)) { const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); if (storage_class != SpvStorageClassMax && @@ -2175,7 +2276,7 @@ spv_result_t BuiltInsValidator::ValidateTessLevelAtReference( return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be only used for variables with Input or Output storage " "class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2216,10 +2317,11 @@ spv_result_t BuiltInsValidator::ValidateTessLevelAtReference( } default: { + uint32_t vuid = (operand == SpvBuiltInTessLevelOuter) ? 4390 : 4394; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << "Vulkan spec allows BuiltIn " + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be used only with TessellationControl or " "TessellationEvaluation execution models. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2246,7 +2348,7 @@ spv_result_t BuiltInsValidator::ValidateVertexIndexAtDefinition( decoration, inst, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4400) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn VertexIndex variable needs to be a " "32-bit int scalar. " @@ -2381,7 +2483,7 @@ spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference( if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(4399) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn VertexIndex to be only used for " "variables with Input storage class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2392,6 +2494,7 @@ spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelVertex) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4398) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn VertexIndex to be used only with " "Vertex execution model. " @@ -2422,7 +2525,10 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtDefinition( decoration, inst, [this, &decoration, &inst](const std::string& message) -> spv_result_t { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInLayer) ? 4276 : 4408; return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(vuid) << "According to the Vulkan spec BuiltIn " << _.grammar().lookupOperandName( SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0]) @@ -2436,7 +2542,10 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtDefinition( decoration, inst, [this, &decoration, &inst](const std::string& message) -> spv_result_t { + uint32_t vuid = + (decoration.params()[0] == SpvBuiltInLayer) ? 4276 : 4408; return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(vuid) << "According to the Vulkan spec BuiltIn " << _.grammar().lookupOperandName( SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0]) @@ -2456,6 +2565,7 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtReference( const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; if (spvIsVulkanEnv(_.context()->target_env)) { const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); if (storage_class != SpvStorageClassMax && @@ -2464,7 +2574,7 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtReference( return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be only used for variables with Input or Output storage " "class. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2516,17 +2626,18 @@ spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtReference( return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) << "Using BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " in Vertex or Tessellation execution model requires " "the ShaderViewportIndexLayerEXT capability."; } break; } default: { + uint32_t vuid = (operand == SpvBuiltInLayer) ? 4272 : 4404; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << "Vulkan spec allows BuiltIn " + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " to be used only with Vertex, TessellationEvaluation, " "Geometry, or Fragment execution models. " << GetReferenceDesc(decoration, built_in_inst, referenced_inst, @@ -2554,12 +2665,28 @@ spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtDefinition( decoration, inst, 3, [this, &decoration, &inst](const std::string& message) -> spv_result_t { + uint32_t operand = decoration.params()[0]; + uint32_t vuid = 0; + switch (operand) { + case SpvBuiltInGlobalInvocationId: + vuid = 4238; + break; + case SpvBuiltInLocalInvocationId: + vuid = 4283; + break; + case SpvBuiltInNumWorkgroups: + vuid = 4298; + break; + case SpvBuiltInWorkgroupId: + vuid = 4424; + break; + }; return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(vuid) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, - decoration.params()[0]) + operand) << " variable needs to be a 3-component 32-bit int " "vector. " << message; @@ -2577,12 +2704,28 @@ spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtReference( const Decoration& decoration, const Instruction& built_in_inst, const Instruction& referenced_inst, const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) { const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); if (storage_class != SpvStorageClassMax && storage_class != SpvStorageClassInput) { + uint32_t vuid = 0; + switch (operand) { + case SpvBuiltInGlobalInvocationId: + vuid = 4237; + break; + case SpvBuiltInLocalInvocationId: + vuid = 4282; + break; + case SpvBuiltInNumWorkgroups: + vuid = 4297; + break; + case SpvBuiltInWorkgroupId: + vuid = 4423; + break; + }; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) - << spvLogStringForEnv(_.context()->target_env) + << _.VkErrorID(vuid) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0]) @@ -2599,7 +2742,23 @@ spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtReference( bool has_webgpu_model = execution_model == SpvExecutionModelGLCompute; if ((spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) || (spvIsWebGPUEnv(_.context()->target_env) && !has_webgpu_model)) { + uint32_t vuid = 0; + switch (operand) { + case SpvBuiltInGlobalInvocationId: + vuid = 4236; + break; + case SpvBuiltInLocalInvocationId: + vuid = 4281; + break; + case SpvBuiltInNumWorkgroups: + vuid = 4296; + break; + case SpvBuiltInWorkgroupId: + vuid = 4422; + break; + }; return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, @@ -2793,6 +2952,7 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition( if (spvIsVulkanEnv(_.context()->target_env) && !spvOpcodeIsConstant(inst.opcode())) { return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4426) << "Vulkan spec requires BuiltIn WorkgroupSize to be a " "constant. " << GetIdDesc(inst) << " is not a constant."; @@ -2802,7 +2962,7 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition( decoration, inst, 3, [this, &inst](const std::string& message) -> spv_result_t { return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "According to the " + << _.VkErrorID(4427) << "According to the " << spvLogStringForEnv(_.context()->target_env) << " spec BuiltIn WorkgroupSize variable needs to be a " "3-component 32-bit int vector. " @@ -2824,6 +2984,7 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference( for (const SpvExecutionModel execution_model : execution_models_) { if (execution_model != SpvExecutionModelGLCompute) { return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4425) << spvLogStringForEnv(_.context()->target_env) << " spec allows BuiltIn " << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, @@ -2845,6 +3006,259 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference( return SPV_SUCCESS; } +spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + uint32_t vuid = (decoration.params()[0] == SpvBuiltInBaseInstance) + ? 4183 + : 4186; + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(vuid) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateBaseInstanceOrVertexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4182 : 4185; + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex) { + uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4181 : 4184; + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be used only with Vertex execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference, + this, decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateDrawIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4209) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateDrawIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateDrawIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4208) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex && + execution_model != SpvExecutionModelMeshNV && + execution_model != SpvExecutionModelTaskNV) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4207) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be used only with Vertex, MeshNV, or TaskNV execution " + "model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateDrawIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateViewIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4403) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateViewIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateViewIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4402) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model == SpvExecutionModelGLCompute) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4401) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be not be used with GLCompute execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateViewIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateDeviceIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &inst, + &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA, &inst) + << _.VkErrorID(4206) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + return ValidateDeviceIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateDeviceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + uint32_t operand = decoration.params()[0]; + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst) + << _.VkErrorID(4205) << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + operand) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateDeviceIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition( const Decoration& decoration, const Instruction& inst) { if (spvIsVulkanEnv(_.context()->target_env)) { @@ -3035,6 +3449,19 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( case SpvBuiltInSMIDNV: { return ValidateSMBuiltinsAtDefinition(decoration, inst); } + case SpvBuiltInBaseInstance: + case SpvBuiltInBaseVertex: { + return ValidateBaseInstanceOrVertexAtDefinition(decoration, inst); + } + case SpvBuiltInDrawIndex: { + return ValidateDrawIndexAtDefinition(decoration, inst); + } + case SpvBuiltInViewIndex: { + return ValidateViewIndexAtDefinition(decoration, inst); + } + case SpvBuiltInDeviceIndex: { + return ValidateDeviceIndexAtDefinition(decoration, inst); + } case SpvBuiltInWorkDim: case SpvBuiltInGlobalSize: case SpvBuiltInEnqueuedWorkgroupSize: @@ -3042,11 +3469,6 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( case SpvBuiltInGlobalLinearId: case SpvBuiltInSubgroupMaxSize: case SpvBuiltInNumEnqueuedSubgroups: - case SpvBuiltInBaseVertex: - case SpvBuiltInBaseInstance: - case SpvBuiltInDrawIndex: - case SpvBuiltInDeviceIndex: - case SpvBuiltInViewIndex: case SpvBuiltInBaryCoordNoPerspAMD: case SpvBuiltInBaryCoordNoPerspCentroidAMD: case SpvBuiltInBaryCoordNoPerspSampleAMD: diff --git a/source/val/validate_capability.cpp b/source/val/validate_capability.cpp index 8a356bf7..4b98bc19 100644 --- a/source/val/validate_capability.cpp +++ b/source/val/validate_capability.cpp @@ -14,8 +14,6 @@ // Validates OpCapability instruction. -#include "source/val/validate.h" - #include <cassert> #include <string> #include <unordered_set> @@ -23,6 +21,7 @@ #include "source/diagnostic.h" #include "source/opcode.h" #include "source/val/instruction.h" +#include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { @@ -166,7 +165,6 @@ bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { switch (capability) { case SpvCapabilityAddresses: case SpvCapabilityFloat16Buffer: - case SpvCapabilityGroups: case SpvCapabilityInt16: case SpvCapabilityInt8: case SpvCapabilityKernel: @@ -175,8 +173,6 @@ bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { return true; case SpvCapabilityInt64: return !embedded_profile; - case SpvCapabilityPipes: - return embedded_profile; } return false; } @@ -187,6 +183,7 @@ bool IsSupportGuaranteedOpenCL_2_0(uint32_t capability, bool embedded_profile) { switch (capability) { case SpvCapabilityDeviceEnqueue: case SpvCapabilityGenericPointer: + case SpvCapabilityGroups: case SpvCapabilityPipes: return true; } diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp index 7ce681c4..17b04460 100644 --- a/source/val/validate_extensions.cpp +++ b/source/val/validate_extensions.cpp @@ -13,11 +13,13 @@ // limitations under the License. // Validates correctness of extension SPIR-V instructions. - +#include <cstdlib> #include <sstream> #include <string> #include <vector> +#include "spirv/unified1/NonSemanticClspvReflection.h" + #include "OpenCLDebugInfo100.h" #include "source/diagnostic.h" #include "source/enum_string_mapping.h" @@ -79,6 +81,7 @@ bool DoesDebugInfoOperandMatchExpectation( const ValidationState_t& _, const std::function<bool(OpenCLDebugInfo100Instructions)>& expectation, const Instruction* inst, uint32_t word_index) { + if (inst->words().size() <= word_index) return false; auto* debug_inst = _.FindDef(inst->word(word_index)); if (debug_inst->opcode() != SpvOpExtInst || debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || @@ -165,11 +168,18 @@ spv_result_t ValidateOperandLexicalScope( spv_result_t ValidateOperandDebugType( ValidationState_t& _, const std::string& debug_inst_name, const Instruction* inst, uint32_t word_index, - const std::function<std::string()>& ext_inst_name) { + const std::function<std::string()>& ext_inst_name, + bool allow_template_param) { std::function<bool(OpenCLDebugInfo100Instructions)> expectation = - [](OpenCLDebugInfo100Instructions dbg_inst) { + [&allow_template_param](OpenCLDebugInfo100Instructions dbg_inst) { + if (allow_template_param && + (dbg_inst == OpenCLDebugInfo100DebugTypeTemplateParameter || + dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateTemplateParameter)) { + return true; + } return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst && - dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember; + dbg_inst <= OpenCLDebugInfo100DebugTypeTemplate; }; if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index)) return SPV_SUCCESS; @@ -180,6 +190,499 @@ spv_result_t ValidateOperandDebugType( << " is not a valid debug type"; } +bool IsUint32Constant(ValidationState_t& _, uint32_t id) { + auto inst = _.FindDef(id); + if (!inst || inst->opcode() != SpvOpConstant) { + return false; + } + + auto type = _.FindDef(inst->type_id()); + if (!type || type->opcode() != SpvOpTypeInt) { + return false; + } + + if (type->GetOperandAs<uint32_t>(1) != 32) { + return false; + } + + if (type->GetOperandAs<uint32_t>(2) != 0) { + return false; + } + + return true; +} + +spv_result_t ValidateClspvReflectionKernel(ValidationState_t& _, + const Instruction* inst) { + const auto kernel_id = inst->GetOperandAs<uint32_t>(4); + const auto kernel = _.FindDef(kernel_id); + if (kernel->opcode() != SpvOpFunction) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel does not reference a function"; + } + + bool found_kernel = false; + for (auto entry_point : _.entry_points()) { + if (entry_point == kernel_id) { + found_kernel = true; + break; + } + } + if (!found_kernel) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel does not reference an entry-point"; + } + + const auto* exec_models = _.GetExecutionModels(kernel_id); + if (!exec_models || exec_models->empty()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel does not reference an entry-point"; + } + for (auto exec_model : *exec_models) { + if (exec_model != SpvExecutionModelGLCompute) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel must refer only to GLCompute entry-points"; + } + } + + auto name = _.FindDef(inst->GetOperandAs<uint32_t>(5)); + if (!name || name->opcode() != SpvOpString) { + return _.diag(SPV_ERROR_INVALID_ID, inst) << "Name must be an OpString"; + } + + const std::string name_str = reinterpret_cast<const char*>( + name->words().data() + name->operands()[1].offset); + bool found = false; + for (auto& desc : _.entry_point_descriptions(kernel_id)) { + if (name_str == desc.name) { + found = true; + break; + } + } + if (!found) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Name must match an entry-point for Kernel"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionArgumentInfo(ValidationState_t& _, + const Instruction* inst) { + const auto num_operands = inst->operands().size(); + if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(4)) != SpvOpString) { + return _.diag(SPV_ERROR_INVALID_ID, inst) << "Name must be an OpString"; + } + if (num_operands > 5) { + if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(5)) != SpvOpString) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "TypeName must be an OpString"; + } + } + if (num_operands > 6) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "AddressQualifier must be a 32-bit unsigned integer " + "OpConstant"; + } + } + if (num_operands > 7) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "AccessQualifier must be a 32-bit unsigned integer " + "OpConstant"; + } + } + if (num_operands > 8) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(8))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "TypeQualifier must be a 32-bit unsigned integer " + "OpConstant"; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateKernelDecl(ValidationState_t& _, const Instruction* inst) { + const auto decl_id = inst->GetOperandAs<uint32_t>(4); + const auto decl = _.FindDef(decl_id); + if (!decl || decl->opcode() != SpvOpExtInst) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel must be a Kernel extended instruction"; + } + + if (decl->GetOperandAs<uint32_t>(2) != inst->GetOperandAs<uint32_t>(2)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel must be from the same extended instruction import"; + } + + const auto ext_inst = + decl->GetOperandAs<NonSemanticClspvReflectionInstructions>(3); + if (ext_inst != NonSemanticClspvReflectionKernel) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Kernel must be a Kernel extended instruction"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateArgInfo(ValidationState_t& _, const Instruction* inst, + uint32_t info_index) { + auto info = _.FindDef(inst->GetOperandAs<uint32_t>(info_index)); + if (!info || info->opcode() != SpvOpExtInst) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "ArgInfo must be an ArgumentInfo extended instruction"; + } + + if (info->GetOperandAs<uint32_t>(2) != inst->GetOperandAs<uint32_t>(2)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "ArgInfo must be from the same extended instruction import"; + } + + auto ext_inst = info->GetOperandAs<NonSemanticClspvReflectionInstructions>(3); + if (ext_inst != NonSemanticClspvReflectionArgumentInfo) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "ArgInfo must be an ArgumentInfo extended instruction"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionArgumentBuffer(ValidationState_t& _, + const Instruction* inst) { + const auto num_operands = inst->operands().size(); + if (auto error = ValidateKernelDecl(_, inst)) { + return error; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Ordinal must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "DescriptorSet must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Binding must be a 32-bit unsigned integer OpConstant"; + } + + if (num_operands == 9) { + if (auto error = ValidateArgInfo(_, inst, 8)) { + return error; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionArgumentPodBuffer(ValidationState_t& _, + const Instruction* inst) { + const auto num_operands = inst->operands().size(); + if (auto error = ValidateKernelDecl(_, inst)) { + return error; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Ordinal must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "DescriptorSet must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Binding must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(8))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Offset must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(9))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Size must be a 32-bit unsigned integer OpConstant"; + } + + if (num_operands == 11) { + if (auto error = ValidateArgInfo(_, inst, 10)) { + return error; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionArgumentPodPushConstant( + ValidationState_t& _, const Instruction* inst) { + const auto num_operands = inst->operands().size(); + if (auto error = ValidateKernelDecl(_, inst)) { + return error; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Ordinal must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Offset must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Size must be a 32-bit unsigned integer OpConstant"; + } + + if (num_operands == 9) { + if (auto error = ValidateArgInfo(_, inst, 8)) { + return error; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionArgumentWorkgroup(ValidationState_t& _, + const Instruction* inst) { + const auto num_operands = inst->operands().size(); + if (auto error = ValidateKernelDecl(_, inst)) { + return error; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Ordinal must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "SpecId must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "ElemSize must be a 32-bit unsigned integer OpConstant"; + } + + if (num_operands == 9) { + if (auto error = ValidateArgInfo(_, inst, 8)) { + return error; + } + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionSpecConstantTriple( + ValidationState_t& _, const Instruction* inst) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "X must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Y must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Z must be a 32-bit unsigned integer OpConstant"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionSpecConstantWorkDim( + ValidationState_t& _, const Instruction* inst) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Dim must be a 32-bit unsigned integer OpConstant"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionPushConstant(ValidationState_t& _, + const Instruction* inst) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Offset must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Size must be a 32-bit unsigned integer OpConstant"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionConstantData(ValidationState_t& _, + const Instruction* inst) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "DescriptorSet must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Binding must be a 32-bit unsigned integer OpConstant"; + } + + if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(6)) != SpvOpString) { + return _.diag(SPV_ERROR_INVALID_ID, inst) << "Data must be an OpString"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionSampler(ValidationState_t& _, + const Instruction* inst) { + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "DescriptorSet must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Binding must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Mask must be a 32-bit unsigned integer OpConstant"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionPropertyRequiredWorkgroupSize( + ValidationState_t& _, const Instruction* inst) { + if (auto error = ValidateKernelDecl(_, inst)) { + return error; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "X must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Y must be a 32-bit unsigned integer OpConstant"; + } + + if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Z must be a 32-bit unsigned integer OpConstant"; + } + + return SPV_SUCCESS; +} + +spv_result_t ValidateClspvReflectionInstruction(ValidationState_t& _, + const Instruction* inst, + uint32_t /*version*/) { + if (!_.IsVoidType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Return Type must be OpTypeVoid"; + } + + auto ext_inst = inst->GetOperandAs<NonSemanticClspvReflectionInstructions>(3); + switch (ext_inst) { + case NonSemanticClspvReflectionKernel: + return ValidateClspvReflectionKernel(_, inst); + case NonSemanticClspvReflectionArgumentInfo: + return ValidateClspvReflectionArgumentInfo(_, inst); + case NonSemanticClspvReflectionArgumentStorageBuffer: + case NonSemanticClspvReflectionArgumentUniform: + case NonSemanticClspvReflectionArgumentSampledImage: + case NonSemanticClspvReflectionArgumentStorageImage: + case NonSemanticClspvReflectionArgumentSampler: + return ValidateClspvReflectionArgumentBuffer(_, inst); + case NonSemanticClspvReflectionArgumentPodStorageBuffer: + case NonSemanticClspvReflectionArgumentPodUniform: + return ValidateClspvReflectionArgumentPodBuffer(_, inst); + case NonSemanticClspvReflectionArgumentPodPushConstant: + return ValidateClspvReflectionArgumentPodPushConstant(_, inst); + case NonSemanticClspvReflectionArgumentWorkgroup: + return ValidateClspvReflectionArgumentWorkgroup(_, inst); + case NonSemanticClspvReflectionSpecConstantWorkgroupSize: + case NonSemanticClspvReflectionSpecConstantGlobalOffset: + return ValidateClspvReflectionSpecConstantTriple(_, inst); + case NonSemanticClspvReflectionSpecConstantWorkDim: + return ValidateClspvReflectionSpecConstantWorkDim(_, inst); + case NonSemanticClspvReflectionPushConstantGlobalOffset: + case NonSemanticClspvReflectionPushConstantEnqueuedLocalSize: + case NonSemanticClspvReflectionPushConstantGlobalSize: + case NonSemanticClspvReflectionPushConstantRegionOffset: + case NonSemanticClspvReflectionPushConstantNumWorkgroups: + case NonSemanticClspvReflectionPushConstantRegionGroupOffset: + return ValidateClspvReflectionPushConstant(_, inst); + case NonSemanticClspvReflectionConstantDataStorageBuffer: + case NonSemanticClspvReflectionConstantDataUniform: + return ValidateClspvReflectionConstantData(_, inst); + case NonSemanticClspvReflectionLiteralSampler: + return ValidateClspvReflectionSampler(_, inst); + case NonSemanticClspvReflectionPropertyRequiredWorkgroupSize: + return ValidateClspvReflectionPropertyRequiredWorkgroupSize(_, inst); + default: + break; + } + + return SPV_SUCCESS; +} + +bool IsConstIntScalarTypeWith32Or64Bits(ValidationState_t& _, + Instruction* instr) { + if (instr->opcode() != SpvOpConstant) return false; + if (!_.IsIntScalarType(instr->type_id())) return false; + uint32_t size_in_bits = _.GetBitWidth(instr->type_id()); + return size_in_bits == 32 || size_in_bits == 64; +} + +bool IsConstWithIntScalarType(ValidationState_t& _, const Instruction* inst, + uint32_t word_index) { + auto* int_scalar_const = _.FindDef(inst->word(word_index)); + if (int_scalar_const->opcode() == SpvOpConstant && + _.IsIntScalarType(int_scalar_const->type_id())) { + return true; + } + return false; +} + +bool IsDebugVariableWithIntScalarType(ValidationState_t& _, + const Instruction* inst, + uint32_t word_index) { + auto* dbg_int_scalar_var = _.FindDef(inst->word(word_index)); + if (OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) == + OpenCLDebugInfo100DebugLocalVariable || + OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) == + OpenCLDebugInfo100DebugGlobalVariable) { + auto* dbg_type = _.FindDef(dbg_int_scalar_var->word(6)); + if (OpenCLDebugInfo100Instructions(dbg_type->word(4)) == + OpenCLDebugInfo100DebugTypeBasic && + (OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) == + OpenCLDebugInfo100Signed || + OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) == + OpenCLDebugInfo100Unsigned)) { + return true; + } + } + return false; +} + } // anonymous namespace spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) { @@ -2222,17 +2725,53 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { break; } case OpenCLDebugInfo100DebugTypeArray: { - auto validate_base_type = - ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name); + auto validate_base_type = ValidateOperandDebugType( + _, "Base Type", inst, 5, ext_inst_name, false); if (validate_base_type != SPV_SUCCESS) return validate_base_type; for (uint32_t i = 6; i < num_words; ++i) { - CHECK_OPERAND("Component Count", SpvOpConstant, i); + bool invalid = false; auto* component_count = _.FindDef(inst->word(i)); - if (!_.IsIntScalarType(component_count->type_id()) || - !component_count->word(3)) { + if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) { + // TODO: We need a spec discussion for the bindless array. + if (!component_count->word(3)) { + invalid = true; + } + } else if (component_count->words().size() > 6 && + (OpenCLDebugInfo100Instructions(component_count->word( + 4)) == OpenCLDebugInfo100DebugLocalVariable || + OpenCLDebugInfo100Instructions(component_count->word( + 4)) == OpenCLDebugInfo100DebugGlobalVariable)) { + auto* component_count_type = _.FindDef(component_count->word(6)); + if (component_count_type->words().size() > 7) { + if (OpenCLDebugInfo100Instructions(component_count_type->word( + 4)) != OpenCLDebugInfo100DebugTypeBasic || + OpenCLDebugInfo100DebugBaseTypeAttributeEncoding( + component_count_type->word(7)) != + OpenCLDebugInfo100Unsigned) { + invalid = true; + } else { + // DebugTypeBasic for DebugLocalVariable/DebugGlobalVariable + // must have Unsigned encoding and 32 or 64 as its size in bits. + Instruction* size_in_bits = + _.FindDef(component_count_type->word(6)); + if (!_.IsIntScalarType(size_in_bits->type_id()) || + (size_in_bits->word(3) != 32 && + size_in_bits->word(3) != 64)) { + invalid = true; + } + } + } else { + invalid = true; + } + } else { + invalid = true; + } + if (invalid) { return _.diag(SPV_ERROR_INVALID_DATA, inst) - << ext_inst_name() << ": Component Count must be positive " - << "integer"; + << ext_inst_name() << ": Component Count must be " + << "OpConstant with a 32- or 64-bits integer scalar type or " + << "DebugGlobalVariable or DebugLocalVariable with a 32- or " + << "64-bits unsigned integer scalar type"; } } break; @@ -2250,14 +2789,16 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugTypeFunction: { auto* return_type = _.FindDef(inst->word(6)); + // TODO: We need a spec discussion that we have to allow return and + // parameter types of a DebugTypeFunction to have template parameter. if (return_type->opcode() != SpvOpTypeVoid) { auto validate_return = ValidateOperandDebugType( - _, "Return Type", inst, 6, ext_inst_name); + _, "Return Type", inst, 6, ext_inst_name, true); if (validate_return != SPV_SUCCESS) return validate_return; } for (uint32_t word_index = 7; word_index < num_words; ++word_index) { auto validate_param = ValidateOperandDebugType( - _, "Parameter Types", inst, word_index, ext_inst_name); + _, "Parameter Types", inst, word_index, ext_inst_name, true); if (validate_param != SPV_SUCCESS) return validate_param; } break; @@ -2271,7 +2812,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { }, inst, 6)) { auto validate_underlying_type = ValidateOperandDebugType( - _, "Underlying Types", inst, 6, ext_inst_name); + _, "Underlying Types", inst, 6, ext_inst_name, false); if (validate_underlying_type != SPV_SUCCESS) return validate_underlying_type; } @@ -2328,8 +2869,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugTypeMember: { CHECK_OPERAND("Name", SpvOpString, 5); + // TODO: We need a spec discussion that we have to allow member types + // to have template parameter. auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10); @@ -2367,18 +2910,13 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { case OpenCLDebugInfo100DebugFunction: { CHECK_OPERAND("Name", SpvOpString, 5); auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); auto validate_parent = ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name); if (validate_parent != SPV_SUCCESS) return validate_parent; CHECK_OPERAND("Linkage Name", SpvOpString, 11); - // TODO: The current OpenCL.100.DebugInfo spec says "Function - // is an OpFunction which is described by this instruction.". - // However, the function definition can be opted-out e.g., - // inlining. We assume that Function operand can be a - // DebugInfoNone, but we must discuss it and update the spec. if (!DoesDebugInfoOperandMatchExpectation( _, [](OpenCLDebugInfo100Instructions dbg_inst) { @@ -2396,7 +2934,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { case OpenCLDebugInfo100DebugFunctionDeclaration: { CHECK_OPERAND("Name", SpvOpString, 5); auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); auto validate_parent = @@ -2414,9 +2952,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { break; } case OpenCLDebugInfo100DebugScope: { - // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We are - // still in spec discussion about what must be "Scope" operand of - // DebugScope. Update this code if the conclusion is different. auto validate_scope = ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name); if (validate_scope != SPV_SUCCESS) return validate_scope; @@ -2428,8 +2963,10 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } case OpenCLDebugInfo100DebugLocalVariable: { CHECK_OPERAND("Name", SpvOpString, 5); + // TODO: We need a spec discussion that we have to allow local variable + // types to have template parameter. auto validate_type = - ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name); + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true); if (validate_type != SPV_SUCCESS) return validate_type; CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); auto validate_parent = @@ -2440,11 +2977,6 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { case OpenCLDebugInfo100DebugDeclare: { CHECK_DEBUG_OPERAND("Local Variable", OpenCLDebugInfo100DebugLocalVariable, 5); - - // TODO: We must discuss DebugDeclare.Variable of OpenCL.100.DebugInfo. - // Currently, it says "Variable must be an id of OpVariable instruction - // which defines the local variable.", but we want to allow - // OpFunctionParameter as well. auto* operand = _.FindDef(inst->word(6)); if (operand->opcode() != SpvOpVariable && operand->opcode() != SpvOpFunctionParameter) { @@ -2464,18 +2996,120 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } break; } + case OpenCLDebugInfo100DebugTypeTemplate: { + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugTypeComposite || + dbg_inst == OpenCLDebugInfo100DebugFunction; + }, + inst, 5)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Target must be DebugTypeComposite " + << "or DebugFunction"; + } + for (uint32_t word_index = 6; word_index < num_words; ++word_index) { + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateParameter || + dbg_inst == + OpenCLDebugInfo100DebugTypeTemplateTemplateParameter; + }, + inst, word_index)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Parameters must be " + << "DebugTypeTemplateParameter or " + << "DebugTypeTemplateTemplateParameter"; + } + } + break; + } + case OpenCLDebugInfo100DebugTypeTemplateParameter: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_actual_type = ValidateOperandDebugType( + _, "Actual Type", inst, 6, ext_inst_name, false); + if (validate_actual_type != SPV_SUCCESS) return validate_actual_type; + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 7)) { + CHECK_OPERAND("Value", SpvOpConstant, 7); + } + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 8); + break; + } + case OpenCLDebugInfo100DebugGlobalVariable: { + CHECK_OPERAND("Name", SpvOpString, 5); + auto validate_type = + ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false); + if (validate_type != SPV_SUCCESS) return validate_type; + CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7); + auto validate_scope = + ValidateOperandLexicalScope(_, "Scope", inst, 10, ext_inst_name); + if (validate_scope != SPV_SUCCESS) return validate_scope; + CHECK_OPERAND("Linkage Name", SpvOpString, 11); + if (!DoesDebugInfoOperandMatchExpectation( + _, + [](OpenCLDebugInfo100Instructions dbg_inst) { + return dbg_inst == OpenCLDebugInfo100DebugInfoNone; + }, + inst, 12)) { + auto* operand = _.FindDef(inst->word(12)); + if (operand->opcode() != SpvOpVariable && + operand->opcode() != SpvOpConstant) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": " + << "expected operand Variable must be a result id of " + "OpVariable or OpConstant or DebugInfoNone"; + } + } + if (num_words == 15) { + CHECK_DEBUG_OPERAND("Static Member Declaration", + OpenCLDebugInfo100DebugTypeMember, 14); + } + break; + } + case OpenCLDebugInfo100DebugInlinedAt: { + auto validate_scope = + ValidateOperandLexicalScope(_, "Scope", inst, 6, ext_inst_name); + if (validate_scope != SPV_SUCCESS) return validate_scope; + if (num_words == 8) { + CHECK_DEBUG_OPERAND("Inlined", OpenCLDebugInfo100DebugInlinedAt, 7); + } + break; + } + case OpenCLDebugInfo100DebugValue: { + CHECK_DEBUG_OPERAND("Local Variable", + OpenCLDebugInfo100DebugLocalVariable, 5); + CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7); + + for (uint32_t word_index = 8; word_index < num_words; ++word_index) { + // TODO: The following code simply checks if it is a const int scalar + // or a DebugLocalVariable or DebugGlobalVariable, but we have to + // check it using the same validation for Indexes of OpAccessChain. + if (!IsConstWithIntScalarType(_, inst, word_index) && + !IsDebugVariableWithIntScalarType(_, inst, word_index)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << ext_inst_name() << ": expected operand Indexes is " + << "OpConstant, DebugGlobalVariable, or " + << "type is OpConstant with an integer scalar type"; + } + } + break; + } // TODO: Add validation rules for remaining cases as well. case OpenCLDebugInfo100DebugTypePtrToMember: - case OpenCLDebugInfo100DebugTypeTemplate: - case OpenCLDebugInfo100DebugTypeTemplateParameter: case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter: case OpenCLDebugInfo100DebugTypeTemplateParameterPack: - case OpenCLDebugInfo100DebugGlobalVariable: case OpenCLDebugInfo100DebugLexicalBlockDiscriminator: - case OpenCLDebugInfo100DebugInlinedAt: case OpenCLDebugInfo100DebugInlinedVariable: - case OpenCLDebugInfo100DebugValue: case OpenCLDebugInfo100DebugMacroDef: case OpenCLDebugInfo100DebugMacroUndef: case OpenCLDebugInfo100DebugImportedEntity: @@ -2484,6 +3118,30 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { assert(0); break; } + } else if (ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) { + auto import_inst = _.FindDef(inst->GetOperandAs<uint32_t>(2)); + const std::string name(reinterpret_cast<const char*>( + import_inst->words().data() + import_inst->operands()[1].offset)); + const std::string reflection = "NonSemantic.ClspvReflection."; + char* end_ptr; + auto version_string = name.substr(reflection.size()); + if (version_string.empty()) { + return _.diag(SPV_ERROR_INVALID_DATA, import_inst) + << "Missing NonSemantic.ClspvReflection import version"; + } + uint32_t version = static_cast<uint32_t>( + std::strtoul(version_string.c_str(), &end_ptr, 10)); + if (end_ptr && *end_ptr != '\0') { + return _.diag(SPV_ERROR_INVALID_DATA, import_inst) + << "NonSemantic.ClspvReflection import does not encode the " + "version correctly"; + } + if (version == 0 || version > NonSemanticClspvReflectionRevision) { + return _.diag(SPV_ERROR_INVALID_DATA, import_inst) + << "Unknown NonSemantic.ClspvReflection import version"; + } + + return ValidateClspvReflectionInstruction(_, inst, version); } return SPV_SUCCESS; diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp index 9ce74a3d..8a2bdf13 100644 --- a/source/val/validate_image.cpp +++ b/source/val/validate_image.cpp @@ -33,7 +33,7 @@ namespace { // Performs compile time check that all SpvImageOperandsXXX cases are handled in // this module. If SpvImageOperandsXXX list changes, this function will fail the // build. -// For all other purposes this is a dummy function. +// For all other purposes this is a placeholder function. bool CheckAllImageOperandsHandled() { SpvImageOperandsMask enum_val = SpvImageOperandsBiasMask; diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp index 833734f4..d16d48e2 100644 --- a/source/val/validate_interfaces.cpp +++ b/source/val/validate_interfaces.cpp @@ -437,6 +437,19 @@ spv_result_t GetLocationsForVariable( spv_result_t ValidateLocations(ValidationState_t& _, const Instruction* entry_point) { + // According to Vulkan 14.1 only the following execution models have + // locations assigned. + switch (entry_point->GetOperandAs<SpvExecutionModel>(0)) { + case SpvExecutionModelVertex: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: + case SpvExecutionModelFragment: + break; + default: + return SPV_SUCCESS; + } + // Locations are stored as a combined location and component values. std::unordered_set<uint32_t> input_locations; std::unordered_set<uint32_t> output_locations_index0; diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index 0739148e..72793ecf 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -1319,5 +1319,209 @@ bool ValidationState_t::IsValidStorageClass( return true; } +#define VUID_WRAP(vuid) "[" #vuid "] " + +// Currently no 2 VUID share the same id, so no need for |reference| +std::string ValidationState_t::VkErrorID(uint32_t id, + const char* /*reference*/) const { + if (!spvIsVulkanEnv(context_->target_env)) { + return ""; + } + + // This large switch case is only searched when an error has occured. + // If an id is changed, the old case must be modified or removed. Each string + // here is interpreted as being "implemented" + + // Clang format adds spaces between hyphens + // clang-format off + switch (id) { + case 4181: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181); + case 4182: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182); + case 4183: + return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183); + case 4184: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184); + case 4185: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185); + case 4186: + return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186); + case 4187: + return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187); + case 4191: + return VUID_WRAP(VUID-ClipDistance-ClipDistance-04191); + case 4196: + return VUID_WRAP(VUID-CullDistance-CullDistance-04196); + case 4200: + return VUID_WRAP(VUID-CullDistance-CullDistance-04200); + case 4205: + return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205); + case 4206: + return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206); + case 4207: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207); + case 4208: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208); + case 4209: + return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209); + case 4210: + return VUID_WRAP(VUID-FragCoord-FragCoord-04210); + case 4211: + return VUID_WRAP(VUID-FragCoord-FragCoord-04211); + case 4212: + return VUID_WRAP(VUID-FragCoord-FragCoord-04212); + case 4213: + return VUID_WRAP(VUID-FragDepth-FragDepth-04213); + case 4214: + return VUID_WRAP(VUID-FragDepth-FragDepth-04214); + case 4215: + return VUID_WRAP(VUID-FragDepth-FragDepth-04215); + case 4216: + return VUID_WRAP(VUID-FragDepth-FragDepth-04216); + case 4229: + return VUID_WRAP(VUID-FrontFacing-FrontFacing-04229); + case 4230: + return VUID_WRAP(VUID-FrontFacing-FrontFacing-04230); + case 4231: + return VUID_WRAP(VUID-FrontFacing-FrontFacing-04231); + case 4236: + return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04236); + case 4237: + return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04237); + case 4238: + return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04238); + case 4239: + return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04239); + case 4240: + return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04240); + case 4241: + return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04241); + case 4257: + return VUID_WRAP(VUID-InvocationId-InvocationId-04257); + case 4258: + return VUID_WRAP(VUID-InvocationId-InvocationId-04258); + case 4259: + return VUID_WRAP(VUID-InvocationId-InvocationId-04259); + case 4263: + return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04263); + case 4264: + return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04264); + case 4265: + return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04265); + case 4272: + return VUID_WRAP(VUID-Layer-Layer-04272); + case 4276: + return VUID_WRAP(VUID-Layer-Layer-04276); + case 4281: + return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04281); + case 4282: + return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04282); + case 4283: + return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04283); + case 4296: + return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04296); + case 4297: + return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04297); + case 4298: + return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04298); + case 4308: + return VUID_WRAP(VUID-PatchVertices-PatchVertices-04308); + case 4309: + return VUID_WRAP(VUID-PatchVertices-PatchVertices-04309); + case 4310: + return VUID_WRAP(VUID-PatchVertices-PatchVertices-04310); + case 4311: + return VUID_WRAP(VUID-PointCoord-PointCoord-04311); + case 4312: + return VUID_WRAP(VUID-PointCoord-PointCoord-04312); + case 4313: + return VUID_WRAP(VUID-PointCoord-PointCoord-04313); + case 4314: + return VUID_WRAP(VUID-PointSize-PointSize-04314); + case 4315: + return VUID_WRAP(VUID-PointSize-PointSize-04315); + case 4316: + return VUID_WRAP(VUID-PointSize-PointSize-04316); + case 4317: + return VUID_WRAP(VUID-PointSize-PointSize-04317); + case 4318: + return VUID_WRAP(VUID-Position-Position-04318); + case 4320: + return VUID_WRAP(VUID-Position-Position-04320); + case 4321: + return VUID_WRAP(VUID-Position-Position-04321); + case 4330: + return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04330); + case 4334: + return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04334); + case 4337: + return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04337); + case 4354: + return VUID_WRAP(VUID-SampleId-SampleId-04354); + case 4355: + return VUID_WRAP(VUID-SampleId-SampleId-04355); + case 4356: + return VUID_WRAP(VUID-SampleId-SampleId-04356); + case 4357: + return VUID_WRAP(VUID-SampleMask-SampleMask-04357); + case 4358: + return VUID_WRAP(VUID-SampleMask-SampleMask-04358); + case 4359: + return VUID_WRAP(VUID-SampleMask-SampleMask-04359); + case 4360: + return VUID_WRAP(VUID-SamplePosition-SamplePosition-04360); + case 4361: + return VUID_WRAP(VUID-SamplePosition-SamplePosition-04361); + case 4362: + return VUID_WRAP(VUID-SamplePosition-SamplePosition-04362); + case 4387: + return VUID_WRAP(VUID-TessCoord-TessCoord-04387); + case 4388: + return VUID_WRAP(VUID-TessCoord-TessCoord-04388); + case 4389: + return VUID_WRAP(VUID-TessCoord-TessCoord-04389); + case 4390: + return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04390); + case 4393: + return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04393); + case 4394: + return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04394); + case 4397: + return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04397); + case 4398: + return VUID_WRAP(VUID-VertexIndex-VertexIndex-04398); + case 4399: + return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399); + case 4400: + return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400); + case 4401: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401); + case 4402: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402); + case 4403: + return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403); + case 4404: + return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404); + case 4408: + return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04408); + case 4422: + return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04422); + case 4423: + return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04423); + case 4424: + return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04424); + case 4425: + return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04425); + case 4426: + return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04426); + case 4427: + return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04427); + default: + return ""; // unknown id + }; + // clang-format on +} + } // namespace val } // namespace spvtools diff --git a/source/val/validation_state.h b/source/val/validation_state.h index e5d31acf..e852c524 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -707,6 +707,17 @@ class ValidationState_t { // Validates the storage class for the target environment. bool IsValidStorageClass(SpvStorageClass storage_class) const; + // Takes a Vulkan Valid Usage ID (VUID) as |id| and optional |reference| and + // will return a non-empty string only if ID is known and targeting Vulkan. + // VUIDs are found in the Vulkan-Docs repo in the form "[[VUID-ref-ref-id]]" + // where "id" is always an 5 char long number (with zeros padding) and matches + // to |id|. |reference| is used if there is a "common validity" and the VUID + // shares the same |id| value. + // + // More details about Vulkan validation can be found in Vulkan Guide: + // https://github.com/KhronosGroup/Vulkan-Guide/blob/master/chapters/validation_overview.md + std::string VkErrorID(uint32_t id, const char* reference = nullptr) const; + private: ValidationState_t(const ValidationState_t&); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 70999f99..5dd4036c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -159,12 +159,12 @@ spvtools_pch(TEST_SOURCES pch_test) add_spvtools_unittest( TARGET spirv_unit_tests SRCS ${TEST_SOURCES} - LIBS ${SPIRV_TOOLS}) + LIBS ${SPIRV_TOOLS}-static) add_spvtools_unittest( TARGET c_interface SRCS c_interface_test.cpp - LIBS ${SPIRV_TOOLS}) + LIBS ${SPIRV_TOOLS}-static) add_spvtools_unittest( TARGET c_interface_shared @@ -181,7 +181,7 @@ if (${SPIRV_TIMER_ENABLED}) add_spvtools_unittest( TARGET timer SRCS timer_test.cpp - LIBS ${SPIRV_TOOLS}) + LIBS ${SPIRV_TOOLS}-static) endif() diff --git a/test/binary_header_get_test.cpp b/test/binary_header_get_test.cpp index f8f6bdbd..3ce0b63a 100644 --- a/test/binary_header_get_test.cpp +++ b/test/binary_header_get_test.cpp @@ -81,5 +81,37 @@ TEST_F(BinaryHeaderGet, TruncatedHeader) { } } +TEST_F(BinaryHeaderGet, VersionNonZeroHighByte) { + spv_header_t header; + code[1] = 0xFF010300; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header)); +} + +TEST_F(BinaryHeaderGet, VersionNonZeroLowByte) { + spv_header_t header; + code[1] = 0x000103F0; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header)); +} + +TEST_F(BinaryHeaderGet, VersionTooLow) { + spv_header_t header; + code[1] = 0x00000300; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header)); +} + +TEST_F(BinaryHeaderGet, VersionTooHigh) { + spv_header_t header; + code[1] = 0x000F0300; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header)); +} + } // namespace } // namespace spvtools diff --git a/test/binary_parse_test.cpp b/test/binary_parse_test.cpp index 54664fce..93e87bdd 100644 --- a/test/binary_parse_test.cpp +++ b/test/binary_parse_test.cpp @@ -92,7 +92,7 @@ std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) { return os; } -// Sanity check for the equality operator on ParsedInstruction. +// Basic check for the equality operator on ParsedInstruction. TEST(ParsedInstruction, ZeroInitializedAreEqual) { spv_parsed_instruction_t pi = {}; ParsedInstruction a(pi); diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index afd23a1f..a918b24f 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -17,15 +17,21 @@ if (${SPIRV_BUILD_FUZZER}) set(SOURCES fuzz_test_util.h + call_graph_test.cpp + comparator_deep_blocks_first_test.cpp data_synonym_transformation_test.cpp equivalence_relation_test.cpp fact_manager_test.cpp fuzz_test_util.cpp + fuzzer_pass_add_opphi_synonyms_test.cpp fuzzer_pass_construct_composites_test.cpp fuzzer_pass_donate_modules_test.cpp + fuzzer_pass_outline_functions_test.cpp instruction_descriptor_test.cpp + fuzzer_pass_test.cpp replayer_test.cpp transformation_access_chain_test.cpp + transformation_add_bit_instruction_synonym_test.cpp transformation_add_constant_boolean_test.cpp transformation_add_constant_composite_test.cpp transformation_add_constant_null_test.cpp @@ -39,7 +45,10 @@ if (${SPIRV_BUILD_FUZZER}) transformation_add_global_variable_test.cpp transformation_add_image_sample_unused_components_test.cpp transformation_add_local_variable_test.cpp + transformation_add_loop_preheader_test.cpp + transformation_add_loop_to_create_int_constant_synonym_test.cpp transformation_add_no_contraction_decoration_test.cpp + transformation_add_opphi_synonym_test.cpp transformation_add_parameter_test.cpp transformation_add_relaxed_decoration_test.cpp transformation_add_synonym_test.cpp @@ -55,22 +64,37 @@ if (${SPIRV_BUILD_FUZZER}) transformation_adjust_branch_weights_test.cpp transformation_composite_construct_test.cpp transformation_composite_extract_test.cpp + transformation_composite_insert_test.cpp transformation_compute_data_synonym_fact_closure_test.cpp + transformation_duplicate_region_with_selection_test.cpp transformation_equation_instruction_test.cpp + transformation_flatten_conditional_branch_test.cpp transformation_function_call_test.cpp + transformation_inline_function_test.cpp transformation_invert_comparison_operator_test.cpp transformation_load_test.cpp + transformation_make_vector_operation_dynamic_test.cpp transformation_merge_blocks_test.cpp transformation_move_block_down_test.cpp + transformation_move_instruction_down_test.cpp + transformation_mutate_pointer_test.cpp transformation_outline_function_test.cpp transformation_permute_function_parameters_test.cpp transformation_permute_phi_operands_test.cpp + transformation_propagate_instruction_up_test.cpp transformation_push_id_through_variable_test.cpp - transformation_replace_parameter_with_global_test.cpp + transformation_replace_add_sub_mul_with_carrying_extended_test.cpp transformation_replace_boolean_constant_with_constant_binary_test.cpp + transformation_replace_copy_object_with_store_load_test.cpp transformation_replace_constant_with_uniform_test.cpp + transformation_replace_copy_memory_with_load_store_test.cpp transformation_replace_id_with_synonym_test.cpp + transformation_replace_irrelevant_id_test.cpp transformation_replace_linear_algebra_instruction_test.cpp + transformation_replace_load_store_with_copy_memory_test.cpp + transformation_replace_opphi_id_from_dead_predecessor_test.cpp + transformation_replace_opselect_with_conditional_branch_test.cpp + transformation_replace_parameter_with_global_test.cpp transformation_replace_params_with_struct_test.cpp transformation_set_function_control_test.cpp transformation_set_loop_control_test.cpp diff --git a/test/fuzz/call_graph_test.cpp b/test/fuzz/call_graph_test.cpp new file mode 100644 index 00000000..d8b31349 --- /dev/null +++ b/test/fuzz/call_graph_test.cpp @@ -0,0 +1,365 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/call_graph.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +// The SPIR-V came from this GLSL, slightly modified +// (main is %2, A is %35, B is %48, C is %50, D is %61): +// +// #version 310 es +// +// int A (int x) { +// return x + 1; +// } +// +// void D() { +// } +// +// void C() { +// int x = 0; +// int y = 0; +// +// while (x < 10) { +// while (y < 10) { +// y = A(y); +// } +// x = A(x); +// } +// } +// +// void B () { +// int x = 0; +// int y = 0; +// +// while (x < 10) { +// D(); +// while (y < 10) { +// y = A(y); +// C(); +// } +// x++; +// } +// +// } +// +// void main() +// { +// int x = 0; +// int y = 0; +// int z = 0; +// +// while (x < 10) { +// while(y < 10) { +// y = A(x); +// while (z < 10) { +// z = A(z); +// } +// } +// x += 2; +// } +// +// B(); +// C(); +// } +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpTypeFunction %5 %6 + %8 = OpConstant %5 1 + %9 = OpConstant %5 0 + %10 = OpConstant %5 10 + %11 = OpTypeBool + %12 = OpConstant %5 2 + %2 = OpFunction %3 None %4 + %13 = OpLabel + %14 = OpVariable %6 Function + %15 = OpVariable %6 Function + %16 = OpVariable %6 Function + %17 = OpVariable %6 Function + %18 = OpVariable %6 Function + OpStore %14 %9 + OpStore %15 %9 + OpStore %16 %9 + OpBranch %19 + %19 = OpLabel + OpLoopMerge %20 %21 None + OpBranch %22 + %22 = OpLabel + %23 = OpLoad %5 %14 + %24 = OpSLessThan %11 %23 %10 + OpBranchConditional %24 %25 %20 + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpLoopMerge %27 %28 None + OpBranch %29 + %29 = OpLabel + %30 = OpLoad %5 %15 + %31 = OpSLessThan %11 %30 %10 + OpBranchConditional %31 %32 %27 + %32 = OpLabel + %33 = OpLoad %5 %14 + OpStore %17 %33 + %34 = OpFunctionCall %5 %35 %17 + OpStore %15 %34 + OpBranch %36 + %36 = OpLabel + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %40 = OpLoad %5 %16 + %41 = OpSLessThan %11 %40 %10 + OpBranchConditional %41 %42 %37 + %42 = OpLabel + %43 = OpLoad %5 %16 + OpStore %18 %43 + %44 = OpFunctionCall %5 %35 %18 + OpStore %16 %44 + OpBranch %38 + %38 = OpLabel + OpBranch %36 + %37 = OpLabel + OpBranch %28 + %28 = OpLabel + OpBranch %26 + %27 = OpLabel + %45 = OpLoad %5 %14 + %46 = OpIAdd %5 %45 %12 + OpStore %14 %46 + OpBranch %21 + %21 = OpLabel + OpBranch %19 + %20 = OpLabel + %47 = OpFunctionCall %3 %48 + %49 = OpFunctionCall %3 %50 + OpReturn + OpFunctionEnd + %35 = OpFunction %5 None %7 + %51 = OpFunctionParameter %6 + %52 = OpLabel + %53 = OpLoad %5 %51 + %54 = OpIAdd %5 %53 %8 + OpReturnValue %54 + OpFunctionEnd + %48 = OpFunction %3 None %4 + %55 = OpLabel + %56 = OpVariable %6 Function + %57 = OpVariable %6 Function + %58 = OpVariable %6 Function + OpStore %56 %9 + OpStore %57 %9 + OpBranch %59 + %59 = OpLabel + %60 = OpFunctionCall %3 %61 + OpLoopMerge %62 %63 None + OpBranch %64 + %64 = OpLabel + OpLoopMerge %65 %66 None + OpBranch %67 + %67 = OpLabel + %68 = OpLoad %5 %57 + %69 = OpSLessThan %11 %68 %10 + OpBranchConditional %69 %70 %65 + %70 = OpLabel + %71 = OpLoad %5 %57 + OpStore %58 %71 + %72 = OpFunctionCall %5 %35 %58 + OpStore %57 %72 + %73 = OpFunctionCall %3 %50 + OpBranch %66 + %66 = OpLabel + OpBranch %64 + %65 = OpLabel + %74 = OpLoad %5 %56 + %75 = OpIAdd %5 %74 %8 + OpStore %56 %75 + OpBranch %63 + %63 = OpLabel + %76 = OpLoad %5 %56 + %77 = OpSLessThan %11 %76 %10 + OpBranchConditional %77 %59 %62 + %62 = OpLabel + OpReturn + OpFunctionEnd + %50 = OpFunction %3 None %4 + %78 = OpLabel + %79 = OpVariable %6 Function + %80 = OpVariable %6 Function + %81 = OpVariable %6 Function + %82 = OpVariable %6 Function + OpStore %79 %9 + OpStore %80 %9 + OpBranch %83 + %83 = OpLabel + OpLoopMerge %84 %85 None + OpBranch %86 + %86 = OpLabel + %87 = OpLoad %5 %79 + %88 = OpSLessThan %11 %87 %10 + OpBranchConditional %88 %89 %84 + %89 = OpLabel + OpBranch %90 + %90 = OpLabel + OpLoopMerge %91 %92 None + OpBranch %93 + %93 = OpLabel + %94 = OpLoad %5 %80 + %95 = OpSLessThan %11 %94 %10 + OpBranchConditional %95 %96 %91 + %96 = OpLabel + %97 = OpLoad %5 %80 + OpStore %81 %97 + %98 = OpFunctionCall %5 %35 %81 + OpStore %80 %98 + OpBranch %92 + %92 = OpLabel + OpBranch %90 + %91 = OpLabel + %99 = OpLoad %5 %79 + OpStore %82 %99 + %100 = OpFunctionCall %5 %35 %82 + OpStore %79 %100 + OpBranch %85 + %85 = OpLabel + OpBranch %83 + %84 = OpLabel + OpReturn + OpFunctionEnd + %61 = OpFunction %3 None %4 + %101 = OpLabel + OpReturn + OpFunctionEnd +)"; + +// We have that: +// main calls: +// - A (maximum loop nesting depth of function call: 3) +// - B (0) +// - C (0) +// A calls nothing. +// B calls: +// - A (2) +// - C (2) +// - D (1) +// C calls: +// - A (2) +// D calls nothing. + +TEST(CallGraphTest, FunctionInDegree) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + const auto graph = CallGraph(context.get()); + + const auto& function_in_degree = graph.GetFunctionInDegree(); + // Check the in-degrees of, in order: main, A, B, C, D. + ASSERT_EQ(function_in_degree.at(2), 0); + ASSERT_EQ(function_in_degree.at(35), 3); + ASSERT_EQ(function_in_degree.at(48), 1); + ASSERT_EQ(function_in_degree.at(50), 2); + ASSERT_EQ(function_in_degree.at(61), 1); +} + +TEST(CallGraphTest, DirectCallees) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + const auto graph = CallGraph(context.get()); + + // Check the callee sets of, in order: main, A, B, C, D. + ASSERT_EQ(graph.GetDirectCallees(2), std::set<uint32_t>({35, 48, 50})); + ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({})); + ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61})); + ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35})); + ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({})); +} + +TEST(CallGraphTest, IndirectCallees) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + const auto graph = CallGraph(context.get()); + + // Check the callee sets of, in order: main, A, B, C, D. + ASSERT_EQ(graph.GetIndirectCallees(2), std::set<uint32_t>({35, 48, 50, 61})); + ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({})); + ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61})); + ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35})); + ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({})); +} + +TEST(CallGraphTest, TopologicalOrder) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + const auto graph = CallGraph(context.get()); + + const auto& topological_ordering = graph.GetFunctionsInTopologicalOrder(); + + // The possible topological orderings are: + // - main, B, D, C, A + // - main, B, C, D, A + // - main, B, C, A, D + ASSERT_TRUE( + topological_ordering == std::vector<uint32_t>({2, 48, 61, 50, 35}) || + topological_ordering == std::vector<uint32_t>({2, 48, 50, 61, 35}) || + topological_ordering == std::vector<uint32_t>({2, 48, 50, 35, 61})); +} + +TEST(CallGraphTest, LoopNestingDepth) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + const auto graph = CallGraph(context.get()); + + // Check the maximum loop nesting depth for function calls to, in order: + // main, A, B, C, D + ASSERT_EQ(graph.GetMaxCallNestingDepth(2), 0); + ASSERT_EQ(graph.GetMaxCallNestingDepth(35), 4); + ASSERT_EQ(graph.GetMaxCallNestingDepth(48), 0); + ASSERT_EQ(graph.GetMaxCallNestingDepth(50), 2); + ASSERT_EQ(graph.GetMaxCallNestingDepth(61), 1); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/comparator_deep_blocks_first_test.cpp b/test/fuzz/comparator_deep_blocks_first_test.cpp new file mode 100644 index 00000000..497a1233 --- /dev/null +++ b/test/fuzz/comparator_deep_blocks_first_test.cpp @@ -0,0 +1,134 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/comparator_deep_blocks_first.h" +#include "source/fuzz/fact_manager/fact_manager.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/transformation_context.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 1 + %10 = OpConstant %7 10 + %11 = OpConstant %7 2 + %2 = OpFunction %3 None %4 + %12 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %6 %14 %15 + %14 = OpLabel + OpBranch %13 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + OpLoopMerge %17 %18 None + OpBranch %19 + %19 = OpLabel + OpBranchConditional %6 %20 %17 + %20 = OpLabel + OpSelectionMerge %21 None + OpBranchConditional %6 %22 %23 + %22 = OpLabel + OpBranch %21 + %23 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %18 + %18 = OpLabel + OpBranch %16 + %17 = OpLabel + OpBranch %13 + %13 = OpLabel + OpReturn + OpFunctionEnd +)"; + +TEST(ComparatorDeepBlocksFirstTest, Compare) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto is_deeper = ComparatorDeepBlocksFirst(context.get()); + + // The block ids and the corresponding depths are: + // 12, 13 -> depth 0 + // 14, 15, 16, 17 -> depth 1 + // 18, 19, 20, 21 -> depth 2 + // 22, 23 -> depth 3 + + // Perform some comparisons and check that they return true iff the first + // block is deeper than the second. + ASSERT_FALSE(is_deeper(12, 12)); + ASSERT_FALSE(is_deeper(12, 13)); + ASSERT_FALSE(is_deeper(12, 14)); + ASSERT_FALSE(is_deeper(12, 18)); + ASSERT_FALSE(is_deeper(12, 22)); + ASSERT_TRUE(is_deeper(14, 12)); + ASSERT_FALSE(is_deeper(14, 15)); + ASSERT_FALSE(is_deeper(15, 14)); + ASSERT_FALSE(is_deeper(14, 18)); + ASSERT_TRUE(is_deeper(18, 12)); + ASSERT_TRUE(is_deeper(18, 16)); +} + +TEST(ComparatorDeepBlocksFirstTest, Sort) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Check that, sorting using the comparator, the blocks are ordered from more + // deeply nested to less deeply nested. + // 17 has depth 1, 20 has depth 2, 13 has depth 0. + std::vector<opt::BasicBlock*> blocks = {context->get_instr_block(17), + context->get_instr_block(20), + context->get_instr_block(13)}; + + std::sort(blocks.begin(), blocks.end(), + ComparatorDeepBlocksFirst(context.get())); + + // Check that the blocks are in the correct order. + ASSERT_EQ(blocks[0]->id(), 20); + ASSERT_EQ(blocks[1]->id(), 17); + ASSERT_EQ(blocks[2]->id(), 13); +} +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/data_synonym_transformation_test.cpp b/test/fuzz/data_synonym_transformation_test.cpp index 66ce769e..3bbe2c59 100644 --- a/test/fuzz/data_synonym_transformation_test.cpp +++ b/test/fuzz/data_synonym_transformation_test.cpp @@ -28,9 +28,9 @@ namespace { // number of transformations that relate to data synonyms. protobufs::Fact MakeSynonymFact(uint32_t first_id, - std::vector<uint32_t>&& first_indices, + std::vector<uint32_t> first_indices, uint32_t second_id, - std::vector<uint32_t>&& second_indices) { + std::vector<uint32_t> second_indices) { protobufs::FactDataSynonym data_synonym_fact; *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first_id, std::move(first_indices)); @@ -122,25 +122,25 @@ TEST(DataSynonymTransformationTest, ArrayCompositeSynonyms) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(12, {}, 100, {0}), context.get()); + MakeSynonymFact(12, {}, 100, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(13, {}, 100, {1}), context.get()); + MakeSynonymFact(13, {}, 100, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(22, {}, 100, {2}), context.get()); + MakeSynonymFact(22, {}, 100, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(28, {}, 101, {0}), context.get()); + MakeSynonymFact(28, {}, 101, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(23, {}, 101, {1}), context.get()); + MakeSynonymFact(23, {}, 101, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(32, {}, 101, {2}), context.get()); + MakeSynonymFact(32, {}, 101, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(23, {}, 101, {3}), context.get()); + MakeSynonymFact(23, {}, 101, {3})); // Replace %12 with %100[0] in '%25 = OpAccessChain %24 %20 %12' auto instruction_descriptor_1 = @@ -410,17 +410,17 @@ TEST(DataSynonymTransformationTest, MatrixCompositeSynonyms) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(23, {}, 100, {0}), context.get()); + MakeSynonymFact(23, {}, 100, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(25, {}, 100, {1}), context.get()); + MakeSynonymFact(25, {}, 100, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(50, {}, 100, {2}), context.get()); + MakeSynonymFact(50, {}, 100, {2})); // Replace %23 with %100[0] in '%26 = OpFAdd %7 %23 %25' auto instruction_descriptor_1 = MakeInstructionDescriptor(26, SpvOpFAdd, 0); @@ -580,25 +580,25 @@ TEST(DataSynonymTransformationTest, StructCompositeSynonyms) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(16, {}, 100, {0}), context.get()); + MakeSynonymFact(16, {}, 100, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(45, {}, 100, {1}), context.get()); + MakeSynonymFact(45, {}, 100, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(27, {}, 101, {0}), context.get()); + MakeSynonymFact(27, {}, 101, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(36, {}, 101, {1}), context.get()); + MakeSynonymFact(36, {}, 101, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(27, {}, 101, {2}), context.get()); + MakeSynonymFact(27, {}, 101, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(22, {}, 102, {0}), context.get()); + MakeSynonymFact(22, {}, 102, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(15, {}, 102, {1}), context.get()); + MakeSynonymFact(15, {}, 102, {1})); // Replace %45 with %100[1] in '%46 = OpCompositeConstruct %32 %35 %45' auto instruction_descriptor_1 = @@ -870,51 +870,51 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(20, {0}, 100, {0}), context.get()); + MakeSynonymFact(20, {0}, 100, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(20, {1}, 100, {1}), context.get()); + MakeSynonymFact(20, {1}, 100, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(20, {2}, 100, {2}), context.get()); + MakeSynonymFact(20, {2}, 100, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(54, {}, 100, {3}), context.get()); + MakeSynonymFact(54, {}, 100, {3})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(15, {0}, 101, {0}), context.get()); + MakeSynonymFact(15, {0}, 101, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(15, {1}, 101, {1}), context.get()); + MakeSynonymFact(15, {1}, 101, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(19, {0}, 101, {2}), context.get()); + MakeSynonymFact(19, {0}, 101, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(19, {1}, 101, {3}), context.get()); + MakeSynonymFact(19, {1}, 101, {3})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(27, {}, 102, {0}), context.get()); + MakeSynonymFact(27, {}, 102, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(15, {0}, 102, {1}), context.get()); + MakeSynonymFact(15, {0}, 102, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(15, {1}, 102, {2}), context.get()); + MakeSynonymFact(15, {1}, 102, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(33, {}, 103, {0}), context.get()); + MakeSynonymFact(33, {}, 103, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(47, {0}, 103, {1}), context.get()); + MakeSynonymFact(47, {0}, 103, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(47, {1}, 103, {2}), context.get()); + MakeSynonymFact(47, {1}, 103, {2})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(47, {2}, 103, {3}), context.get()); + MakeSynonymFact(47, {2}, 103, {3})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(42, {}, 104, {0}), context.get()); + MakeSynonymFact(42, {}, 104, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(45, {}, 104, {1}), context.get()); + MakeSynonymFact(45, {}, 104, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(38, {0}, 105, {0}), context.get()); + MakeSynonymFact(38, {0}, 105, {0})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(38, {1}, 105, {1}), context.get()); + MakeSynonymFact(38, {1}, 105, {1})); transformation_context.GetFactManager()->AddFact( - MakeSynonymFact(46, {}, 105, {2}), context.get()); + MakeSynonymFact(46, {}, 105, {2})); // Replace %20 with %100[0:2] in '%80 = OpCopyObject %16 %20' auto instruction_descriptor_1 = @@ -923,7 +923,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 100, 100, {0, 1, 2}); ASSERT_TRUE(shuffle_1.IsApplicable(context.get(), transformation_context)); shuffle_1.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_1 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(20, instruction_descriptor_1, 0), 200); @@ -953,7 +953,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 101, 101, {0, 1}); ASSERT_TRUE(shuffle_3.IsApplicable(context.get(), transformation_context)); shuffle_3.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_3 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(15, instruction_descriptor_3, 1), 202); @@ -969,7 +969,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 101, 101, {2, 3}); ASSERT_TRUE(shuffle_4.IsApplicable(context.get(), transformation_context)); shuffle_4.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_4 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(19, instruction_descriptor_4, 0), 203); @@ -1001,7 +1001,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 102, 102, {1, 2}); ASSERT_TRUE(shuffle_6.IsApplicable(context.get(), transformation_context)); shuffle_6.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_6 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(15, instruction_descriptor_6, 0), 205); @@ -1031,7 +1031,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 103, 103, {1, 2, 3}); ASSERT_TRUE(shuffle_8.IsApplicable(context.get(), transformation_context)); shuffle_8.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_8 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(47, instruction_descriptor_8, 0), 207); @@ -1074,7 +1074,7 @@ TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) { 105, 105, {0, 1}); ASSERT_TRUE(shuffle_11.IsApplicable(context.get(), transformation_context)); shuffle_11.Apply(context.get(), &transformation_context); - fact_manager.ComputeClosureOfFacts(context.get(), 100); + fact_manager.ComputeClosureOfFacts(100); auto replacement_11 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(38, instruction_descriptor_11, 1), 210); diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp index bce10b97..38777ada 100644 --- a/test/fuzz/fact_manager_test.cpp +++ b/test/fuzz/fact_manager_test.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/fact_manager/fact_manager.h" + #include <limits> -#include "source/fuzz/fact_manager.h" +#include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/uniform_buffer_element_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -33,8 +35,7 @@ using opt::analysis::Integer; using opt::analysis::Type; bool AddFactHelper( - FactManager* fact_manager, opt::IRContext* context, - std::vector<uint32_t>&& words, + FactManager* fact_manager, const std::vector<uint32_t>& words, const protobufs::UniformBufferElementDescriptor& descriptor) { protobufs::FactConstantUniform constant_uniform_fact; for (auto word : words) { @@ -44,7 +45,7 @@ bool AddFactHelper( descriptor; protobufs::Fact fact; *fact.mutable_constant_uniform_fact() = constant_uniform_fact; - return fact_manager->AddFact(fact, context); + return fact_manager->AddFact(fact); } TEST(FactManagerTest, ConstantsAvailableViaUniforms) { @@ -254,7 +255,7 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { std::memcpy(&buffer_double_20, &temp, sizeof(temp)); } - FactManager fact_manager; + FactManager fact_manager(context.get()); uint32_t type_int32_id = 11; uint32_t type_int64_id = 13; @@ -264,103 +265,100 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { uint32_t type_double_id = 16; // Initially there should be no facts about uniforms. - ASSERT_TRUE(fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_uint32_id) - .empty()); + ASSERT_TRUE( + fact_manager.GetConstantsAvailableFromUniformsForType(type_uint32_id) + .empty()); // In the comments that follow we write v[...][...] to refer to uniform // variable v indexed with some given indices, when in practice v is // identified via a (descriptor set, binding) pair. // 100[2][3] == int(1) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {1}, MakeUniformBufferElementDescriptor(0, 0, {2, 3}))); // 200[1][2][3] == int(1) - ASSERT_TRUE( - AddFactHelper(&fact_manager, context.get(), {1}, - MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3}))); + ASSERT_TRUE(AddFactHelper( + &fact_manager, {1}, MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3}))); // 300[1][0][2][3] == int(1) ASSERT_TRUE( - AddFactHelper(&fact_manager, context.get(), {1}, + AddFactHelper(&fact_manager, {1}, MakeUniformBufferElementDescriptor(0, 2, {1, 0, 2, 3}))); // 400[2][3] = int32_min - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_int32_min[0]}, MakeUniformBufferElementDescriptor(0, 3, {2, 3}))); // 500[1][2][3] = int32_min ASSERT_TRUE( - AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]}, + AddFactHelper(&fact_manager, {buffer_int32_min[0]}, MakeUniformBufferElementDescriptor(0, 4, {1, 2, 3}))); // 600[1][2][3] = int64_max - ASSERT_TRUE(AddFactHelper( - &fact_manager, context.get(), {buffer_int64_max[0], buffer_int64_max[1]}, - MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3}))); + ASSERT_TRUE( + AddFactHelper(&fact_manager, {buffer_int64_max[0], buffer_int64_max[1]}, + MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3}))); // 700[1][1] = int64_max - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_int64_max[0], buffer_int64_max[1]}, MakeUniformBufferElementDescriptor(0, 6, {1, 1}))); // 800[2][3] = uint(1) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {1}, MakeUniformBufferElementDescriptor(1, 0, {2, 3}))); // 900[1][2][3] = uint(1) - ASSERT_TRUE( - AddFactHelper(&fact_manager, context.get(), {1}, - MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3}))); + ASSERT_TRUE(AddFactHelper( + &fact_manager, {1}, MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3}))); // 1000[1][0][2][3] = uint(1) ASSERT_TRUE( - AddFactHelper(&fact_manager, context.get(), {1}, + AddFactHelper(&fact_manager, {1}, MakeUniformBufferElementDescriptor(1, 2, {1, 0, 2, 3}))); // 1100[0] = uint64(1) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_uint64_1[0], buffer_uint64_1[1]}, MakeUniformBufferElementDescriptor(1, 3, {0}))); // 1200[0][0] = uint64_max - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_uint64_max[0], buffer_uint64_max[1]}, MakeUniformBufferElementDescriptor(1, 4, {0, 0}))); // 1300[1][0] = uint64_max - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_uint64_max[0], buffer_uint64_max[1]}, MakeUniformBufferElementDescriptor(1, 5, {1, 0}))); // 1400[6] = float(10.0) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]}, MakeUniformBufferElementDescriptor(1, 6, {6}))); // 1500[7] = float(10.0) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]}, MakeUniformBufferElementDescriptor(2, 0, {7}))); // 1600[9][9] = float(10.0) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]}, MakeUniformBufferElementDescriptor(2, 1, {9, 9}))); // 1700[9][9][1] = double(10.0) - ASSERT_TRUE(AddFactHelper( - &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]}, - MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1}))); + ASSERT_TRUE( + AddFactHelper(&fact_manager, {buffer_double_10[0], buffer_double_10[1]}, + MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1}))); // 1800[9][9][2] = double(10.0) - ASSERT_TRUE(AddFactHelper( - &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]}, - MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}))); + ASSERT_TRUE( + AddFactHelper(&fact_manager, {buffer_double_10[0], buffer_double_10[1]}, + MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}))); // 1900[0][0][0][0][0] = double(20.0) - ASSERT_TRUE(AddFactHelper( - &fact_manager, context.get(), {buffer_double_20[0], buffer_double_20[1]}, - MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}))); + ASSERT_TRUE( + AddFactHelper(&fact_manager, {buffer_double_20[0], buffer_double_20[1]}, + MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}))); opt::Instruction::OperandList operands = { {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}}; @@ -404,59 +402,52 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); // Constants 1 and int32_min are available. - ASSERT_EQ(2, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_int32_id) - .size()); + ASSERT_EQ(2, + fact_manager.GetConstantsAvailableFromUniformsForType(type_int32_id) + .size()); // Constant int64_max is available. - ASSERT_EQ(1, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_int64_id) - .size()); + ASSERT_EQ(1, + fact_manager.GetConstantsAvailableFromUniformsForType(type_int64_id) + .size()); // Constant 1u is available. - ASSERT_EQ(1, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_uint32_id) - .size()); + ASSERT_EQ( + 1, fact_manager.GetConstantsAvailableFromUniformsForType(type_uint32_id) + .size()); // Constants 1u and uint64_max are available. - ASSERT_EQ(2, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_uint64_id) - .size()); + ASSERT_EQ( + 2, fact_manager.GetConstantsAvailableFromUniformsForType(type_uint64_id) + .size()); // Constant 10.0 is available. - ASSERT_EQ(1, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_float_id) - .size()); + ASSERT_EQ(1, + fact_manager.GetConstantsAvailableFromUniformsForType(type_float_id) + .size()); // Constants 10.0 and 20.0 are available. - ASSERT_EQ(2, fact_manager - .GetConstantsAvailableFromUniformsForType(context.get(), - type_double_id) - .size()); + ASSERT_EQ( + 2, fact_manager.GetConstantsAvailableFromUniformsForType(type_double_id) + .size()); ASSERT_EQ(std::numeric_limits<int64_t>::max(), context->get_constant_mgr() ->FindDeclaredConstant( fact_manager.GetConstantsAvailableFromUniformsForType( - context.get(), type_int64_id)[0]) + type_int64_id)[0]) ->AsIntConstant() ->GetS64()); ASSERT_EQ(1, context->get_constant_mgr() ->FindDeclaredConstant( fact_manager.GetConstantsAvailableFromUniformsForType( - context.get(), type_uint32_id)[0]) + type_uint32_id)[0]) ->AsIntConstant() ->GetU32()); ASSERT_EQ(10.0f, context->get_constant_mgr() ->FindDeclaredConstant( fact_manager.GetConstantsAvailableFromUniformsForType( - context.get(), type_float_id)[0]) + type_float_id)[0]) ->AsFloatConstant() ->GetFloat()); const std::vector<uint32_t>& double_constant_ids = - fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), - type_double_id); + fact_manager.GetConstantsAvailableFromUniformsForType(type_double_id); ASSERT_EQ(10.0, context->get_constant_mgr() ->FindDeclaredConstant(double_constant_ids[0]) ->AsFloatConstant() @@ -467,8 +458,8 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { ->GetDouble()); const std::vector<protobufs::UniformBufferElementDescriptor> - descriptors_for_double_10 = fact_manager.GetUniformDescriptorsForConstant( - context.get(), double_constant_ids[0]); + descriptors_for_double_10 = + fact_manager.GetUniformDescriptorsForConstant(double_constant_ids[0]); ASSERT_EQ(2, descriptors_for_double_10.size()); { auto temp = MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1}); @@ -481,8 +472,8 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { &temp, &descriptors_for_double_10[1])); } const std::vector<protobufs::UniformBufferElementDescriptor> - descriptors_for_double_20 = fact_manager.GetUniformDescriptorsForConstant( - context.get(), double_constant_ids[1]); + descriptors_for_double_20 = + fact_manager.GetUniformDescriptorsForConstant(double_constant_ids[1]); ASSERT_EQ(1, descriptors_for_double_20.size()); { auto temp = MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}); @@ -491,11 +482,11 @@ TEST(FactManagerTest, ConstantsAvailableViaUniforms) { } auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor( - context.get(), MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})); + MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})); ASSERT_TRUE(constant_1_id); auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor( - context.get(), MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})); + MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})); ASSERT_TRUE(constant_2_id); ASSERT_EQ(double_constant_ids[0], constant_1_id); @@ -544,29 +535,28 @@ TEST(FactManagerTest, TwoConstantsWithSameValue) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); auto uniform_buffer_element_descriptor = MakeUniformBufferElementDescriptor(0, 0, {0}); // (0, 0, [0]) = int(1) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1}, - uniform_buffer_element_descriptor)); - auto constants = - fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6); + ASSERT_TRUE( + AddFactHelper(&fact_manager, {1}, uniform_buffer_element_descriptor)); + auto constants = fact_manager.GetConstantsAvailableFromUniformsForType(6); ASSERT_EQ(1, constants.size()); ASSERT_TRUE(constants[0] == 9 || constants[0] == 20); auto constant = fact_manager.GetConstantFromUniformDescriptor( - context.get(), uniform_buffer_element_descriptor); + uniform_buffer_element_descriptor); ASSERT_TRUE(constant == 9 || constant == 20); // Because the constants with ids 9 and 20 are equal, we should get the same // single uniform buffer element descriptor when we look up the descriptors // for either one of them. for (auto constant_id : {9u, 20u}) { - auto descriptors = fact_manager.GetUniformDescriptorsForConstant( - context.get(), constant_id); + auto descriptors = + fact_manager.GetUniformDescriptorsForConstant(constant_id); ASSERT_EQ(1, descriptors.size()); ASSERT_TRUE(UniformBufferElementDescriptorEquals()( &uniform_buffer_element_descriptor, &descriptors[0])); @@ -610,7 +600,7 @@ TEST(FactManagerTest, NonFiniteFactsAreNotValid) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); auto uniform_buffer_element_descriptor_f = MakeUniformBufferElementDescriptor(0, 0, {0}); @@ -622,12 +612,12 @@ TEST(FactManagerTest, NonFiniteFactsAreNotValid) { float positive_infinity_float = std::numeric_limits<float>::infinity(); uint32_t words[1]; memcpy(words, &positive_infinity_float, sizeof(float)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]}, uniform_buffer_element_descriptor_f)); // f == -inf float negative_infinity_float = std::numeric_limits<float>::infinity(); memcpy(words, &negative_infinity_float, sizeof(float)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]}, uniform_buffer_element_descriptor_f)); } @@ -636,7 +626,7 @@ TEST(FactManagerTest, NonFiniteFactsAreNotValid) { float quiet_nan_float = std::numeric_limits<float>::quiet_NaN(); uint32_t words[1]; memcpy(words, &quiet_nan_float, sizeof(float)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]}, uniform_buffer_element_descriptor_f)); } @@ -645,14 +635,12 @@ TEST(FactManagerTest, NonFiniteFactsAreNotValid) { double positive_infinity_double = std::numeric_limits<double>::infinity(); uint32_t words[2]; memcpy(words, &positive_infinity_double, sizeof(double)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), - {words[0], words[1]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]}, uniform_buffer_element_descriptor_d)); // d == -inf double negative_infinity_double = -std::numeric_limits<double>::infinity(); memcpy(words, &negative_infinity_double, sizeof(double)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), - {words[0], words[1]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]}, uniform_buffer_element_descriptor_d)); } @@ -661,8 +649,7 @@ TEST(FactManagerTest, NonFiniteFactsAreNotValid) { double quiet_nan_double = std::numeric_limits<double>::quiet_NaN(); uint32_t words[2]; memcpy(words, &quiet_nan_double, sizeof(double)); - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), - {words[0], words[1]}, + ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]}, uniform_buffer_element_descriptor_d)); } } @@ -728,14 +715,14 @@ TEST(FactManagerTest, AmbiguousFact) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); auto uniform_buffer_element_descriptor = MakeUniformBufferElementDescriptor(0, 0, {0}); // The fact cannot be added because it is ambiguous: there are two uniforms // with descriptor set 0 and binding 0. - ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {1}, - uniform_buffer_element_descriptor)); + ASSERT_FALSE( + AddFactHelper(&fact_manager, {1}, uniform_buffer_element_descriptor)); } TEST(FactManagerTest, RecursiveAdditionOfFacts) { @@ -765,10 +752,10 @@ TEST(FactManagerTest, RecursiveAdditionOfFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}), - MakeDataDescriptor(11, {2}), context.get()); + MakeDataDescriptor(11, {2})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}), MakeDataDescriptor(11, {2}))); @@ -827,49 +814,121 @@ TEST(FactManagerTest, CorollaryConversionFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); // Add equation facts - fact_manager.AddFactIdEquation(24, SpvOpConvertSToF, {15}, context.get()); - fact_manager.AddFactIdEquation(25, SpvOpConvertSToF, {16}, context.get()); - fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}, context.get()); - fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {18}, context.get()); - fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {19}, context.get()); - fact_manager.AddFactIdEquation(29, SpvOpConvertUToF, {20}, context.get()); - fact_manager.AddFactIdEquation(30, SpvOpConvertSToF, {21}, context.get()); - fact_manager.AddFactIdEquation(31, SpvOpConvertUToF, {22}, context.get()); - fact_manager.AddFactIdEquation(32, SpvOpConvertUToF, {23}, context.get()); + fact_manager.AddFactIdEquation(24, SpvOpConvertSToF, {15}); + fact_manager.AddFactIdEquation(25, SpvOpConvertSToF, {16}); + fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}); + fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {18}); + fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {19}); + fact_manager.AddFactIdEquation(29, SpvOpConvertUToF, {20}); + fact_manager.AddFactIdEquation(30, SpvOpConvertSToF, {21}); + fact_manager.AddFactIdEquation(31, SpvOpConvertUToF, {22}); + fact_manager.AddFactIdEquation(32, SpvOpConvertUToF, {23}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}), - MakeDataDescriptor(16, {}), context.get()); + MakeDataDescriptor(16, {})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}), MakeDataDescriptor(25, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}), - MakeDataDescriptor(18, {}), context.get()); + MakeDataDescriptor(18, {})); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(19, {}), - MakeDataDescriptor(20, {}), context.get()); + MakeDataDescriptor(20, {})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}), MakeDataDescriptor(29, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}), - MakeDataDescriptor(22, {}), context.get()); + MakeDataDescriptor(22, {})); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}), MakeDataDescriptor(31, {}))); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}), MakeDataDescriptor(28, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}), - MakeDataDescriptor(19, {}), context.get()); + MakeDataDescriptor(19, {})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}), MakeDataDescriptor(28, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}), MakeDataDescriptor(29, {}))); } +TEST(FactManagerTest, HandlesCorollariesWithInvalidIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %12 "main" + OpExecutionMode %12 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %8 = OpTypeInt 32 1 + %9 = OpConstant %8 3 + %12 = OpFunction %2 None %3 + %13 = OpLabel + %14 = OpConvertSToF %6 %9 + OpBranch %16 + %16 = OpLabel + %17 = OpPhi %6 %14 %13 + %15 = OpConvertSToF %6 %9 + %18 = OpConvertSToF %6 %9 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + + // Add required facts. + fact_manager.AddFactIdEquation(14, SpvOpConvertSToF, {9}); + fact_manager.AddFactDataSynonym(MakeDataDescriptor(14, {}), + MakeDataDescriptor(17, {})); + + // Apply TransformationMergeBlocks which will remove %17 from the module. + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + TransformationMergeBlocks transformation(16); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + ASSERT_EQ(context->get_def_use_mgr()->GetDef(17), nullptr); + + // Add another equation. + fact_manager.AddFactIdEquation(15, SpvOpConvertSToF, {9}); + + // Check that two ids are synonymous even though one of them doesn't exist in + // the module (%17). + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}), + MakeDataDescriptor(17, {}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}), + MakeDataDescriptor(14, {}))); + + // Remove some instructions from the module. At this point, the equivalence + // class of %14 has no valid members. + ASSERT_TRUE(context->KillDef(14)); + ASSERT_TRUE(context->KillDef(15)); + + fact_manager.AddFactIdEquation(18, SpvOpConvertSToF, {9}); + + // We don't create synonyms if at least one of the equivalence classes has no + // valid members. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(14, {}), + MakeDataDescriptor(18, {}))); +} + TEST(FactManagerTest, LogicalNotEquationFacts) { std::string shader = R"( OpCapability Shader @@ -897,14 +956,14 @@ TEST(FactManagerTest, LogicalNotEquationFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}), - MakeDataDescriptor(7, {}), context.get()); + MakeDataDescriptor(7, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}), - MakeDataDescriptor(14, {}), context.get()); - fact_manager.AddFactIdEquation(14, SpvOpLogicalNot, {7}, context.get()); - fact_manager.AddFactIdEquation(17, SpvOpLogicalNot, {16}, context.get()); + MakeDataDescriptor(14, {})); + fact_manager.AddFactIdEquation(14, SpvOpLogicalNot, {7}); + fact_manager.AddFactIdEquation(17, SpvOpLogicalNot, {16}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {}))); @@ -941,10 +1000,10 @@ TEST(FactManagerTest, SignedNegateEquationFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); - fact_manager.AddFactIdEquation(14, SpvOpSNegate, {7}, context.get()); - fact_manager.AddFactIdEquation(15, SpvOpSNegate, {14}, context.get()); + fact_manager.AddFactIdEquation(14, SpvOpSNegate, {7}); + fact_manager.AddFactIdEquation(15, SpvOpSNegate, {14}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}), MakeDataDescriptor(15, {}))); @@ -983,21 +1042,21 @@ TEST(FactManagerTest, AddSubNegateFacts1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); - fact_manager.AddFactIdEquation(14, SpvOpIAdd, {15, 16}, context.get()); + fact_manager.AddFactIdEquation(14, SpvOpIAdd, {15, 16}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}), - MakeDataDescriptor(15, {}), context.get()); + MakeDataDescriptor(15, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}), - MakeDataDescriptor(16, {}), context.get()); - fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 18}, context.get()); - fact_manager.AddFactIdEquation(20, SpvOpISub, {14, 17}, context.get()); + MakeDataDescriptor(16, {})); + fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 18}); + fact_manager.AddFactIdEquation(20, SpvOpISub, {14, 17}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}), - MakeDataDescriptor(14, {}), context.get()); - fact_manager.AddFactIdEquation(22, SpvOpISub, {16, 21}, context.get()); + MakeDataDescriptor(14, {})); + fact_manager.AddFactIdEquation(22, SpvOpISub, {16, 21}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}), - MakeDataDescriptor(22, {}), context.get()); - fact_manager.AddFactIdEquation(24, SpvOpSNegate, {23}, context.get()); + MakeDataDescriptor(22, {})); + fact_manager.AddFactIdEquation(24, SpvOpSNegate, {23}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(19, {}), MakeDataDescriptor(15, {}))); @@ -1039,33 +1098,33 @@ TEST(FactManagerTest, AddSubNegateFacts2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); - fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}, context.get()); - fact_manager.AddFactIdEquation(17, SpvOpIAdd, {14, 16}, context.get()); + fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}); + fact_manager.AddFactIdEquation(17, SpvOpIAdd, {14, 16}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}), MakeDataDescriptor(15, {}))); - fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 14}, context.get()); + fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 14}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {}))); - fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}, context.get()); - fact_manager.AddFactIdEquation(20, SpvOpSNegate, {19}, context.get()); + fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}); + fact_manager.AddFactIdEquation(20, SpvOpSNegate, {19}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {}))); - fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get()); + fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}))); - fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}, context.get()); - fact_manager.AddFactIdEquation(23, SpvOpSNegate, {22}, context.get()); + fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}); + fact_manager.AddFactIdEquation(23, SpvOpSNegate, {22}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}), MakeDataDescriptor(16, {}))); } @@ -1115,46 +1174,102 @@ TEST(FactManagerTest, ConversionEquations) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}), - MakeDataDescriptor(17, {}), context.get()); + MakeDataDescriptor(17, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}), - MakeDataDescriptor(19, {}), context.get()); + MakeDataDescriptor(19, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(20, {}), - MakeDataDescriptor(21, {}), context.get()); + MakeDataDescriptor(21, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}), - MakeDataDescriptor(23, {}), context.get()); + MakeDataDescriptor(23, {})); - fact_manager.AddFactIdEquation(25, SpvOpConvertUToF, {16}, context.get()); - fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}, context.get()); + fact_manager.AddFactIdEquation(25, SpvOpConvertUToF, {16}); + fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}), MakeDataDescriptor(26, {}))); - fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {20}, context.get()); - fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {21}, context.get()); + fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {20}); + fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {21}); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}), MakeDataDescriptor(28, {}))); - fact_manager.AddFactIdEquation(29, SpvOpConvertSToF, {18}, context.get()); - fact_manager.AddFactIdEquation(30, SpvOpConvertUToF, {19}, context.get()); + fact_manager.AddFactIdEquation(29, SpvOpConvertSToF, {18}); + fact_manager.AddFactIdEquation(30, SpvOpConvertUToF, {19}); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(29, {}), MakeDataDescriptor(30, {}))); - fact_manager.AddFactIdEquation(31, SpvOpConvertSToF, {22}, context.get()); - fact_manager.AddFactIdEquation(32, SpvOpConvertSToF, {23}, context.get()); + fact_manager.AddFactIdEquation(31, SpvOpConvertSToF, {22}); + fact_manager.AddFactIdEquation(32, SpvOpConvertSToF, {23}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(31, {}), MakeDataDescriptor(32, {}))); - fact_manager.AddFactIdEquation(33, SpvOpConvertUToF, {17}, context.get()); + fact_manager.AddFactIdEquation(33, SpvOpConvertUToF, {17}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}), MakeDataDescriptor(26, {}))); - fact_manager.AddFactIdEquation(34, SpvOpConvertSToF, {23}, context.get()); + fact_manager.AddFactIdEquation(34, SpvOpConvertSToF, {23}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}), MakeDataDescriptor(34, {}))); } +TEST(FactManagerTest, BitcastEquationFacts) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %12 "main" + OpExecutionMode %12 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeInt 32 1 + %5 = OpTypeInt 32 0 + %8 = OpTypeFloat 32 + %9 = OpTypeVector %4 2 + %10 = OpTypeVector %5 2 + %11 = OpTypeVector %8 2 + %6 = OpConstant %4 23 + %7 = OpConstant %5 23 + %19 = OpConstant %8 23 + %20 = OpConstantComposite %9 %6 %6 + %21 = OpConstantComposite %10 %7 %7 + %22 = OpConstantComposite %11 %19 %19 + %12 = OpFunction %2 None %3 + %13 = OpLabel + %30 = OpBitcast %8 %6 + %31 = OpBitcast %5 %6 + %32 = OpBitcast %8 %7 + %33 = OpBitcast %4 %7 + %34 = OpBitcast %4 %19 + %35 = OpBitcast %5 %19 + %36 = OpBitcast %10 %20 + %37 = OpBitcast %11 %20 + %38 = OpBitcast %9 %21 + %39 = OpBitcast %11 %21 + %40 = OpBitcast %9 %22 + %41 = OpBitcast %10 %22 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + + uint32_t lhs_id = 30; + for (uint32_t rhs_id : {6, 6, 7, 7, 19, 19, 20, 20, 21, 21, 22, 22}) { + fact_manager.AddFactIdEquation(lhs_id, SpvOpBitcast, {rhs_id}); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(lhs_id, {}), + MakeDataDescriptor(rhs_id, {}))); + ++lhs_id; + } +} + TEST(FactManagerTest, EquationAndEquivalenceFacts) { std::string shader = R"( OpCapability Shader @@ -1190,39 +1305,39 @@ TEST(FactManagerTest, EquationAndEquivalenceFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); - fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}, context.get()); + fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(114, {}), - MakeDataDescriptor(14, {}), context.get()); - fact_manager.AddFactIdEquation(17, SpvOpIAdd, {114, 16}, context.get()); + MakeDataDescriptor(14, {})); + fact_manager.AddFactIdEquation(17, SpvOpIAdd, {114, 16}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}), MakeDataDescriptor(15, {}))); - fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 114}, context.get()); + fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 114}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {}))); - fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}, context.get()); + fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(119, {}), - MakeDataDescriptor(19, {}), context.get()); - fact_manager.AddFactIdEquation(20, SpvOpSNegate, {119}, context.get()); + MakeDataDescriptor(19, {})); + fact_manager.AddFactIdEquation(20, SpvOpSNegate, {119}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {}))); - fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get()); + fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}))); - fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}, context.get()); + fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}); fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}), - MakeDataDescriptor(220, {}), context.get()); - fact_manager.AddFactIdEquation(23, SpvOpSNegate, {220}, context.get()); + MakeDataDescriptor(220, {})); + fact_manager.AddFactIdEquation(23, SpvOpSNegate, {220}); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}), MakeDataDescriptor(16, {}))); } @@ -1263,10 +1378,10 @@ TEST(FactManagerTest, CheckingFactsDoesNotAddConstants) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); // 8[0] == int(1) - ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1}, + ASSERT_TRUE(AddFactHelper(&fact_manager, {1}, MakeUniformBufferElementDescriptor(0, 0, {0}))); // Although 8[0] has the value 1, we do not have the constant 1 in the module. @@ -1277,7 +1392,7 @@ TEST(FactManagerTest, CheckingFactsDoesNotAddConstants) { opt::analysis::IntConstant constant_one(int_type, {1}); ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one)); auto available_constants = - fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6); + fact_manager.GetConstantsAvailableFromUniformsForType(6); ASSERT_EQ(0, available_constants.size()); ASSERT_TRUE(IsEqual(env, shader, context.get())); ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one)); @@ -1307,7 +1422,7 @@ TEST(FactManagerTest, IdIsIrrelevant) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); ASSERT_FALSE(fact_manager.IdIsIrrelevant(12)); ASSERT_FALSE(fact_manager.IdIsIrrelevant(13)); @@ -1318,6 +1433,161 @@ TEST(FactManagerTest, IdIsIrrelevant) { ASSERT_FALSE(fact_manager.IdIsIrrelevant(13)); } +TEST(FactManagerTest, GetIrrelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %12 = OpConstant %6 0 + %13 = OpConstant %6 1 + %14 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + + ASSERT_EQ(fact_manager.GetIrrelevantIds(), std::unordered_set<uint32_t>({})); + + fact_manager.AddFactIdIsIrrelevant(12); + + ASSERT_EQ(fact_manager.GetIrrelevantIds(), + std::unordered_set<uint32_t>({12})); + + fact_manager.AddFactIdIsIrrelevant(13); + + ASSERT_EQ(fact_manager.GetIrrelevantIds(), + std::unordered_set<uint32_t>({12, 13})); +} + +TEST(FactManagerTest, BlockIsDead) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + OpSelectionMerge %10 None + OpBranchConditional %6 %11 %12 + %11 = OpLabel + OpBranch %10 + %12 = OpLabel + OpBranch %10 + %10 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + + ASSERT_FALSE(fact_manager.BlockIsDead(9)); + ASSERT_FALSE(fact_manager.BlockIsDead(11)); + ASSERT_FALSE(fact_manager.BlockIsDead(12)); + + fact_manager.AddFactBlockIsDead(12); + + ASSERT_FALSE(fact_manager.BlockIsDead(9)); + ASSERT_FALSE(fact_manager.BlockIsDead(11)); + ASSERT_TRUE(fact_manager.BlockIsDead(12)); +} + +TEST(FactManagerTest, IdsFromDeadBlocksAreIrrelevant) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 1 + %2 = OpFunction %3 None %4 + %10 = OpLabel + %11 = OpVariable %8 Function + OpSelectionMerge %12 None + OpBranchConditional %6 %13 %14 + %13 = OpLabel + OpBranch %12 + %14 = OpLabel + %15 = OpCopyObject %8 %11 + %16 = OpCopyObject %7 %9 + %17 = OpFunctionCall %3 %18 + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd + %18 = OpFunction %3 None %4 + %19 = OpLabel + %20 = OpVariable %8 Function + %21 = OpCopyObject %7 %9 + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + + ASSERT_FALSE(fact_manager.BlockIsDead(14)); + ASSERT_FALSE(fact_manager.BlockIsDead(19)); + + // Initially no id is irrelevant. + ASSERT_FALSE(fact_manager.IdIsIrrelevant(16)); + ASSERT_FALSE(fact_manager.IdIsIrrelevant(17)); + ASSERT_EQ(fact_manager.GetIrrelevantIds(), std::unordered_set<uint32_t>({})); + + fact_manager.AddFactBlockIsDead(14); + + // %16 and %17 should now be considered irrelevant. + ASSERT_TRUE(fact_manager.IdIsIrrelevant(16)); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(17)); + ASSERT_EQ(fact_manager.GetIrrelevantIds(), + std::unordered_set<uint32_t>({16, 17})); + + // Similarly for %21. + ASSERT_FALSE(fact_manager.IdIsIrrelevant(21)); + + fact_manager.AddFactBlockIsDead(19); + + ASSERT_TRUE(fact_manager.IdIsIrrelevant(21)); + ASSERT_EQ(fact_manager.GetIrrelevantIds(), + std::unordered_set<uint32_t>({16, 17, 21})); +} } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h index 9e08bf68..19d19182 100644 --- a/test/fuzz/fuzz_test_util.h +++ b/test/fuzz/fuzz_test_util.h @@ -15,10 +15,9 @@ #ifndef TEST_FUZZ_FUZZ_TEST_UTIL_H_ #define TEST_FUZZ_FUZZ_TEST_UTIL_H_ -#include "gtest/gtest.h" - #include <vector> +#include "gtest/gtest.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/opt/build_module.h" #include "source/opt/ir_context.h" diff --git a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp new file mode 100644 index 00000000..ca06b288 --- /dev/null +++ b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp @@ -0,0 +1,175 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) { + protobufs::FactDataSynonym data_synonym_fact; + *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {}); + *data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {}); + protobufs::Fact result; + *result.mutable_data_synonym_fact() = data_synonym_fact; + return result; +} + +// Adds synonym facts to the fact manager. +void SetUpIdSynonyms(FactManager* fact_manager) { + // Synonyms {9, 11, 15, 16, 21, 22} + fact_manager->AddFact(MakeSynonymFact(11, 9)); + fact_manager->AddFact(MakeSynonymFact(15, 9)); + fact_manager->AddFact(MakeSynonymFact(16, 9)); + fact_manager->AddFact(MakeSynonymFact(21, 9)); + fact_manager->AddFact(MakeSynonymFact(22, 9)); + + // Synonyms {10, 23} + fact_manager->AddFact(MakeSynonymFact(10, 23)); + + // Synonyms {14, 27} + fact_manager->AddFact(MakeSynonymFact(14, 27)); + + // Synonyms {24, 26, 30} + fact_manager->AddFact(MakeSynonymFact(26, 24)); + fact_manager->AddFact(MakeSynonymFact(30, 24)); +} + +// Returns true if the given lists have the same elements, regardless of their +// order. +template <typename T> +bool ListsHaveTheSameElements(const std::vector<T>& list1, + const std::vector<T>& list2) { + auto sorted1 = list1; + std::sort(sorted1.begin(), sorted1.end()); + + auto sorted2 = list2; + std::sort(sorted2.begin(), sorted2.end()); + + return sorted1 == sorted2; +} + +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %31 = OpTypeFunction %7 + %8 = OpTypeInt 32 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %8 1 + %12 = OpTypePointer Function %7 + %2 = OpFunction %3 None %4 + %13 = OpLabel + %14 = OpVariable %12 Function + %15 = OpCopyObject %7 %9 + %16 = OpCopyObject %8 %11 + OpBranch %17 + %17 = OpLabel + OpSelectionMerge %18 None + OpBranchConditional %6 %19 %20 + %19 = OpLabel + %21 = OpCopyObject %7 %15 + %22 = OpCopyObject %8 %16 + %23 = OpCopyObject %7 %10 + %24 = OpIAdd %7 %9 %10 + OpBranch %18 + %20 = OpLabel + OpBranch %18 + %18 = OpLabel + %26 = OpIAdd %7 %15 %10 + %27 = OpCopyObject %12 %14 + OpSelectionMerge %28 None + OpBranchConditional %6 %29 %28 + %29 = OpLabel + %30 = OpCopyObject %7 %26 + OpBranch %28 + %28 = OpLabel + OpReturn + OpFunctionEnd + %32 = OpFunction %7 None %31 + %33 = OpLabel + OpReturnValue %9 + OpFunctionEnd +)"; + +TEST(FuzzerPassAddOpPhiSynonymsTest, HelperFunctions) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context, + &fuzzer_context, + &transformation_sequence); + + SetUpIdSynonyms(&fact_manager); + + std::vector<std::set<uint32_t>> expected_equivalence_classes = { + {9, 15, 21}, {11, 16, 22}, {10, 23}, {6}, {24, 26, 30}}; + + ASSERT_TRUE(ListsHaveTheSameElements<std::set<uint32_t>>( + fuzzer_pass.GetIdEquivalenceClasses(), expected_equivalence_classes)); + + // The set {24, 26, 30} is not suitable for 18 (none if the ids is available + // for predecessor 20). + ASSERT_FALSE( + fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 18, 1)); + + // The set {6} is not suitable for 18 if we require at least 2 distinct + // available ids. + ASSERT_FALSE(fuzzer_pass.EquivalenceClassIsSuitableForBlock({6}, 18, 2)); + + // Only id 26 from the set {24, 26, 30} is available to use for the + // transformation at block 29, so the set is not suitable if we want at least + // 2 available ids. + ASSERT_FALSE( + fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 2)); + + ASSERT_TRUE( + fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 1)); + + // %21 is not available at the end of block 20. + ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>( + fuzzer_pass.GetSuitableIds({9, 15, 21}, 20), {9, 15})); + + // %24 and %30 are not available at the end of block 18. + ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>( + fuzzer_pass.GetSuitableIds({24, 26, 30}, 18), {26})); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/fuzzer_pass_construct_composites_test.cpp b/test/fuzz/fuzzer_pass_construct_composites_test.cpp index cc21f74d..80e81173 100644 --- a/test/fuzz/fuzzer_pass_construct_composites_test.cpp +++ b/test/fuzz/fuzzer_pass_construct_composites_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/fuzzer_pass_construct_composites.h" + #include "source/fuzz/pseudo_random_generator.h" #include "test/fuzz/fuzz_test_util.h" @@ -81,7 +82,7 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicStructs) { BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -163,7 +164,7 @@ TEST(FuzzerPassConstructCompositesTest, IsomorphicArrays) { BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp index 0be3d5a5..b6dbd96f 100644 --- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp +++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/fuzz/fuzzer_pass_donate_modules.h" + #include <algorithm> -#include "source/fuzz/fuzzer_pass_donate_modules.h" #include "source/fuzz/pseudo_random_generator.h" #include "test/fuzz/fuzz_test_util.h" @@ -195,7 +196,7 @@ TEST(FuzzerPassDonateModulesTest, BasicDonation) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -273,7 +274,7 @@ TEST(FuzzerPassDonateModulesTest, DonationWithUniforms) { env, consumer, recipient_and_donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -401,7 +402,7 @@ TEST(FuzzerPassDonateModulesTest, DonationWithInputAndOutputVariables) { env, consumer, recipient_and_donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -493,7 +494,7 @@ TEST(FuzzerPassDonateModulesTest, DonateFunctionTypeWithDifferentPointers) { env, consumer, recipient_and_donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -560,7 +561,7 @@ TEST(FuzzerPassDonateModulesTest, DonateOpConstantNull) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -685,7 +686,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImages) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -778,7 +779,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesSampler) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -907,7 +908,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageStructField) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1040,7 +1041,7 @@ TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageFunctionParameter) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1119,7 +1120,7 @@ TEST(FuzzerPassDonateModulesTest, DonateShaderWithImageStorageClass) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1203,7 +1204,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArray) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1304,7 +1305,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArrayLivesafe) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1373,7 +1374,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithWorkgroupVariables) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1480,7 +1481,7 @@ TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithAtomics) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1661,7 +1662,7 @@ TEST(FuzzerPassDonateModulesTest, Miscellaneous1) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1730,7 +1731,7 @@ TEST(FuzzerPassDonateModulesTest, OpSpecConstantInstructions) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1884,7 +1885,7 @@ TEST(FuzzerPassDonateModulesTest, DonationSupportsOpTypeRuntimeArray) { BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, donor_context.get())); - FactManager fact_manager; + FactManager fact_manager(recipient_context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1902,6 +1903,308 @@ TEST(FuzzerPassDonateModulesTest, DonationSupportsOpTypeRuntimeArray) { ASSERT_TRUE(IsValid(env, recipient_context.get())); } +TEST(FuzzerPassDonateModulesTest, HandlesCapabilities) { + std::string donor_shader = R"( + OpCapability VariablePointersStorageBuffer + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %7 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %9 + + %9 = OpLabel + %10 = OpPhi %7 %8 %5 + OpStore %10 %11 + OpReturn + + OpFunctionEnd + )"; + + std::string recipient_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto recipient_context = + BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, recipient_context.get())); + + const auto donor_context = + BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, donor_context.get())); + + FactManager fact_manager(recipient_context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator rng(0); + FuzzerContext fuzzer_context(&rng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), + &transformation_context, &fuzzer_context, + &transformation_sequence, {}); + + ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer)); + ASSERT_FALSE(recipient_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer)); + + fuzzer_pass.DonateSingleModule(donor_context.get(), false); + + // Check that recipient module hasn't changed. + ASSERT_TRUE(IsEqual(env, recipient_shader, recipient_context.get())); + + // Add the missing capability. + // + // We are adding VariablePointers to test the case when donor and recipient + // have different OpCapability instructions but the same capabilities. In our + // example, VariablePointers implicitly declares + // VariablePointersStorageBuffer. Thus, two modules must be compatible. + recipient_context->AddCapability(SpvCapabilityVariablePointers); + + ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer)); + ASSERT_TRUE(recipient_context->get_feature_mgr()->HasCapability( + SpvCapabilityVariablePointersStorageBuffer)); + + fuzzer_pass.DonateSingleModule(donor_context.get(), false); + + // Check that donation was successful. + ASSERT_TRUE(IsValid(env, recipient_context.get())); + + std::string after_transformation = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %100 = OpTypeFloat 32 + %101 = OpConstant %100 23 + %102 = OpTypePointer Function %100 + %105 = OpConstant %100 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %103 = OpFunction %2 None %3 + %104 = OpLabel + %106 = OpVariable %102 Function %105 + OpBranch %107 + %107 = OpLabel + %108 = OpPhi %102 %106 %104 + OpStore %108 %101 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get())); +} + +TEST(FuzzerPassDonateModulesTest, HandlesOpPhisInMergeBlock) { + std::string donor_shader = R"( + ; OpPhis don't support pointers without this capability + ; and we need pointers to test some of the functionality + OpCapability VariablePointers + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %14 = OpTypeBool + %15 = OpConstantTrue %14 + %42 = OpTypePointer Function %14 + + ; back-edge block is unreachable in the CFG + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %7 None + OpBranch %8 + %7 = OpLabel + OpBranch %6 + %8 = OpLabel + OpReturn + OpFunctionEnd + + ; back-edge block already has an edge to the merge block + %9 = OpFunction %2 None %3 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpLoopMerge %13 %12 None + OpBranch %12 + %12 = OpLabel + OpBranchConditional %15 %11 %13 + %13 = OpLabel + OpReturn + OpFunctionEnd + + ; merge block has no OpPhis + %16 = OpFunction %2 None %3 + %17 = OpLabel + OpBranch %18 + %18 = OpLabel + OpLoopMerge %20 %19 None + OpBranchConditional %15 %19 %20 + %19 = OpLabel + OpBranch %18 + %20 = OpLabel + OpReturn + OpFunctionEnd + + ; merge block has OpPhis and some of their operands are available at + ; the back-edge block + %21 = OpFunction %2 None %3 + %22 = OpLabel + OpBranch %23 + %23 = OpLabel + %24 = OpCopyObject %14 %15 + OpLoopMerge %28 %27 None + OpBranchConditional %15 %25 %28 + %25 = OpLabel + %26 = OpCopyObject %14 %15 + OpBranchConditional %15 %28 %27 + %27 = OpLabel + OpBranch %23 + %28 = OpLabel + %29 = OpPhi %14 %24 %23 %26 %25 + OpReturn + OpFunctionEnd + + ; none of the OpPhis' operands dominate the back-edge block but some of + ; them have basic type + %30 = OpFunction %2 None %3 + %31 = OpLabel + OpBranch %32 + %32 = OpLabel + OpLoopMerge %40 %39 None + OpBranch %33 + %33 = OpLabel + OpSelectionMerge %38 None + OpBranchConditional %15 %34 %36 + %34 = OpLabel + %35 = OpCopyObject %14 %15 + OpBranchConditional %35 %38 %40 + %36 = OpLabel + %37 = OpCopyObject %14 %15 + OpBranchConditional %37 %38 %40 + %38 = OpLabel + OpBranch %39 + %39 = OpLabel + OpBranch %32 + %40 = OpLabel + %41 = OpPhi %14 %35 %34 %37 %36 + OpReturn + OpFunctionEnd + + ; none of the OpPhis' operands dominate the back-edge block and none of + ; them have basic type + %43 = OpFunction %2 None %3 + %44 = OpLabel + %45 = OpVariable %42 Function + OpBranch %46 + %46 = OpLabel + OpLoopMerge %54 %53 None + OpBranch %47 + %47 = OpLabel + OpSelectionMerge %52 None + OpBranchConditional %15 %48 %50 + %48 = OpLabel + %49 = OpCopyObject %42 %45 + OpBranchConditional %15 %52 %54 + %50 = OpLabel + %51 = OpCopyObject %42 %45 + OpBranchConditional %15 %52 %54 + %52 = OpLabel + OpBranch %53 + %53 = OpLabel + OpBranch %46 + %54 = OpLabel + %55 = OpPhi %42 %49 %48 %51 %50 + OpReturn + OpFunctionEnd + )"; + + std::string recipient_shader = R"( + ; OpPhis don't support pointers without this capability + ; and we need pointers to test some of the functionality + OpCapability VariablePointers + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto recipient_context = + BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, recipient_context.get())); + + const auto donor_context = + BuildModule(env, consumer, donor_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, donor_context.get())); + + FactManager fact_manager(recipient_context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassDonateModules fuzzer_pass(recipient_context.get(), + &transformation_context, &fuzzer_context, + &transformation_sequence, {}); + + fuzzer_pass.DonateSingleModule(donor_context.get(), true); + + // We just check that the result is valid. Checking to what it should be + // exactly equal to would be very fragile. + ASSERT_TRUE(IsValid(env, recipient_context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/fuzzer_pass_outline_functions_test.cpp b/test/fuzz/fuzzer_pass_outline_functions_test.cpp new file mode 100644 index 00000000..59f47e2f --- /dev/null +++ b/test/fuzz/fuzzer_pass_outline_functions_test.cpp @@ -0,0 +1,603 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_outline_functions.h" + +#include "source/fuzz/pseudo_random_generator.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + OpDecorate %5 RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + %10 = OpTypeVoid + %11 = OpTypeFunction %10 + %12 = OpTypeInt 32 1 + %13 = OpTypePointer Function %12 + %14 = OpConstant %12 8 + %15 = OpConstant %12 23 + %16 = OpTypeBool + %17 = OpConstantTrue %16 + %18 = OpConstant %12 0 + %19 = OpConstant %12 1 + %2 = OpFunction %10 None %11 + %20 = OpLabel + %3 = OpVariable %13 Function + %4 = OpVariable %13 Function + OpStore %3 %14 + OpStore %4 %15 + OpBranch %21 + %21 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %12 %19 %21 %18 %26 + OpLoopMerge %27 %26 None + OpBranch %28 + %28 = OpLabel + %5 = OpLoad %12 %3 + %29 = OpSGreaterThan %16 %5 %18 + OpBranchConditional %29 %30 %27 + %30 = OpLabel + %6 = OpLoad %12 %4 + %7 = OpISub %12 %6 %19 + OpStore %4 %7 + OpBranch %26 + %26 = OpLabel + %8 = OpLoad %12 %3 + %9 = OpISub %12 %8 %19 + OpStore %3 %9 + OpBranch %24 + %27 = OpLabel + OpBranch %23 + %23 = OpLabel + OpBranch %21 + %22 = OpLabel + OpBranch %31 + %31 = OpLabel + OpLoopMerge %32 %31 None + OpBranchConditional %17 %31 %32 + %32 = OpLabel + OpSelectionMerge %33 None + OpBranchConditional %17 %34 %35 + %34 = OpLabel + OpBranch %33 + %35 = OpLabel + OpBranch %33 + %33 = OpLabel + %42 = OpPhi %12 %19 %33 %18 %34 %18 %35 + OpLoopMerge %36 %33 None + OpBranchConditional %17 %36 %33 + %36 = OpLabel + %43 = OpPhi %12 %18 %33 %18 %41 + OpReturn + %37 = OpLabel + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + OpBranchConditional %17 %41 %38 + %41 = OpLabel + OpBranchConditional %17 %36 %39 + %39 = OpLabel + OpBranch %37 + %38 = OpLabel + OpReturn + OpFunctionEnd +)"; + +TEST(FuzzerPassOutlineFunctionsTest, EntryIsAlreadySuitable) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, + &fuzzer_context, + &transformation_sequence); + + // Block 28 + auto suitable_entry_block = + fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(28)); + + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 28); + + // Block 32 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(32)); + + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 32); + + // Block 41 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(41)); + + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 41); + + // The module should not have been changed. + ASSERT_TRUE(IsEqual(env, shader, context.get())); +} + +TEST(FuzzerPassOutlineFunctionsTest, EntryHasOpVariable) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, + &fuzzer_context, + &transformation_sequence); + + // Block 20 + auto suitable_entry_block = + fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(20)); + + // The block should have been split, the new entry block being the block + // generated by the splitting. + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 100); + + std::string after_adjustment = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + OpDecorate %5 RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + %10 = OpTypeVoid + %11 = OpTypeFunction %10 + %12 = OpTypeInt 32 1 + %13 = OpTypePointer Function %12 + %14 = OpConstant %12 8 + %15 = OpConstant %12 23 + %16 = OpTypeBool + %17 = OpConstantTrue %16 + %18 = OpConstant %12 0 + %19 = OpConstant %12 1 + %2 = OpFunction %10 None %11 + %20 = OpLabel + %3 = OpVariable %13 Function + %4 = OpVariable %13 Function + OpBranch %100 + %100 = OpLabel + OpStore %3 %14 + OpStore %4 %15 + OpBranch %21 + %21 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %12 %19 %21 %18 %26 + OpLoopMerge %27 %26 None + OpBranch %28 + %28 = OpLabel + %5 = OpLoad %12 %3 + %29 = OpSGreaterThan %16 %5 %18 + OpBranchConditional %29 %30 %27 + %30 = OpLabel + %6 = OpLoad %12 %4 + %7 = OpISub %12 %6 %19 + OpStore %4 %7 + OpBranch %26 + %26 = OpLabel + %8 = OpLoad %12 %3 + %9 = OpISub %12 %8 %19 + OpStore %3 %9 + OpBranch %24 + %27 = OpLabel + OpBranch %23 + %23 = OpLabel + OpBranch %21 + %22 = OpLabel + OpBranch %31 + %31 = OpLabel + OpLoopMerge %32 %31 None + OpBranchConditional %17 %31 %32 + %32 = OpLabel + OpSelectionMerge %33 None + OpBranchConditional %17 %34 %35 + %34 = OpLabel + OpBranch %33 + %35 = OpLabel + OpBranch %33 + %33 = OpLabel + %42 = OpPhi %12 %19 %33 %18 %34 %18 %35 + OpLoopMerge %36 %33 None + OpBranchConditional %17 %36 %33 + %36 = OpLabel + %43 = OpPhi %12 %18 %33 %18 %41 + OpReturn + %37 = OpLabel + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + OpBranchConditional %17 %41 %38 + %41 = OpLabel + OpBranchConditional %17 %36 %39 + %39 = OpLabel + OpBranch %37 + %38 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_adjustment, context.get())); +} + +TEST(FuzzerPassOutlineFunctionsTest, EntryBlockIsHeader) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, + &fuzzer_context, + &transformation_sequence); + + // Block 21 + auto suitable_entry_block = + fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(21)); + + // A suitable entry block should have been found by finding the preheader + // (%20) and then splitting it. + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 100); + + // Block 24 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(24)); + + // A preheader should have been created, because the current one is a loop + // header. + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 101); + + // Block 31 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(31)); + + // An existing suitable entry block should have been found by finding the + // preheader (%22), which is already suitable. + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 22); + + // Block 33 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(33)); + + // An existing suitable entry block should have been found by creating a new + // preheader (there is not one already), and then splitting it (as it contains + // OpPhi). + ASSERT_TRUE(suitable_entry_block); + ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 104); + + // Block 37 + suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining( + context->get_instr_block(37)); + + // No suitable entry block can be found for block 37, since it is a loop + // header with only one predecessor (the back-edge block). + ASSERT_FALSE(suitable_entry_block); + + std::string after_adjustments = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + OpDecorate %5 RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + %10 = OpTypeVoid + %11 = OpTypeFunction %10 + %12 = OpTypeInt 32 1 + %13 = OpTypePointer Function %12 + %14 = OpConstant %12 8 + %15 = OpConstant %12 23 + %16 = OpTypeBool + %17 = OpConstantTrue %16 + %18 = OpConstant %12 0 + %19 = OpConstant %12 1 + %2 = OpFunction %10 None %11 + %20 = OpLabel + %3 = OpVariable %13 Function + %4 = OpVariable %13 Function + OpBranch %100 + %100 = OpLabel + OpStore %3 %14 + OpStore %4 %15 + OpBranch %21 + %21 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %101 + %101 = OpLabel + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %12 %19 %101 %18 %26 + OpLoopMerge %27 %26 None + OpBranch %28 + %28 = OpLabel + %5 = OpLoad %12 %3 + %29 = OpSGreaterThan %16 %5 %18 + OpBranchConditional %29 %30 %27 + %30 = OpLabel + %6 = OpLoad %12 %4 + %7 = OpISub %12 %6 %19 + OpStore %4 %7 + OpBranch %26 + %26 = OpLabel + %8 = OpLoad %12 %3 + %9 = OpISub %12 %8 %19 + OpStore %3 %9 + OpBranch %24 + %27 = OpLabel + OpBranch %23 + %23 = OpLabel + OpBranch %21 + %22 = OpLabel + OpBranch %31 + %31 = OpLabel + OpLoopMerge %32 %31 None + OpBranchConditional %17 %31 %32 + %32 = OpLabel + OpSelectionMerge %102 None + OpBranchConditional %17 %34 %35 + %34 = OpLabel + OpBranch %102 + %35 = OpLabel + OpBranch %102 + %102 = OpLabel + %103 = OpPhi %12 %18 %34 %18 %35 + OpBranch %104 + %104 = OpLabel + OpBranch %33 + %33 = OpLabel + %42 = OpPhi %12 %103 %104 %19 %33 + OpLoopMerge %36 %33 None + OpBranchConditional %17 %36 %33 + %36 = OpLabel + %43 = OpPhi %12 %18 %33 %18 %41 + OpReturn + %37 = OpLabel + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + OpBranchConditional %17 %41 %38 + %41 = OpLabel + OpBranchConditional %17 %36 %39 + %39 = OpLabel + OpBranch %37 + %38 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_adjustments, context.get())); +} + +TEST(FuzzerPassOutlineFunctionsTest, ExitBlock) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformation_sequence; + + FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context, + &fuzzer_context, + &transformation_sequence); + + // Block 39 is not a merge block, so it is already suitable. + auto suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining( + context->get_instr_block(39)); + ASSERT_TRUE(suitable_exit_block); + ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 39); + + // The following are merge blocks and, thus, they will need to be split. + + // Block 22 + suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining( + context->get_instr_block(22)); + ASSERT_TRUE(suitable_exit_block); + ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 100); + + // Block 27 + suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining( + context->get_instr_block(27)); + ASSERT_TRUE(suitable_exit_block); + ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 101); + + // Block 36 + suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining( + context->get_instr_block(36)); + ASSERT_TRUE(suitable_exit_block); + ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 102); + + std::string after_adjustments = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + OpDecorate %5 RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + OpDecorate %9 RelaxedPrecision + %10 = OpTypeVoid + %11 = OpTypeFunction %10 + %12 = OpTypeInt 32 1 + %13 = OpTypePointer Function %12 + %14 = OpConstant %12 8 + %15 = OpConstant %12 23 + %16 = OpTypeBool + %17 = OpConstantTrue %16 + %18 = OpConstant %12 0 + %19 = OpConstant %12 1 + %2 = OpFunction %10 None %11 + %20 = OpLabel + %3 = OpVariable %13 Function + %4 = OpVariable %13 Function + OpStore %3 %14 + OpStore %4 %15 + OpBranch %21 + %21 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %12 %19 %21 %18 %26 + OpLoopMerge %27 %26 None + OpBranch %28 + %28 = OpLabel + %5 = OpLoad %12 %3 + %29 = OpSGreaterThan %16 %5 %18 + OpBranchConditional %29 %30 %27 + %30 = OpLabel + %6 = OpLoad %12 %4 + %7 = OpISub %12 %6 %19 + OpStore %4 %7 + OpBranch %26 + %26 = OpLabel + %8 = OpLoad %12 %3 + %9 = OpISub %12 %8 %19 + OpStore %3 %9 + OpBranch %24 + %27 = OpLabel + OpBranch %101 + %101 = OpLabel + OpBranch %23 + %23 = OpLabel + OpBranch %21 + %22 = OpLabel + OpBranch %100 + %100 = OpLabel + OpBranch %31 + %31 = OpLabel + OpLoopMerge %32 %31 None + OpBranchConditional %17 %31 %32 + %32 = OpLabel + OpSelectionMerge %33 None + OpBranchConditional %17 %34 %35 + %34 = OpLabel + OpBranch %33 + %35 = OpLabel + OpBranch %33 + %33 = OpLabel + %42 = OpPhi %12 %19 %33 %18 %34 %18 %35 + OpLoopMerge %36 %33 None + OpBranchConditional %17 %36 %33 + %36 = OpLabel + %43 = OpPhi %12 %18 %33 %18 %41 + OpBranch %102 + %102 = OpLabel + OpReturn + %37 = OpLabel + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + OpBranchConditional %17 %41 %38 + %41 = OpLabel + OpBranchConditional %17 %36 %39 + %39 = OpLabel + OpBranch %37 + %38 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_adjustments, context.get())); +} +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/fuzzer_pass_test.cpp b/test/fuzz/fuzzer_pass_test.cpp new file mode 100644 index 00000000..ce48010a --- /dev/null +++ b/test/fuzz/fuzzer_pass_test.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +class FuzzerPassMock : public FuzzerPass { + public: + FuzzerPassMock(opt::IRContext* ir_context, + TransformationContext* transformation_context, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, transformation_context, fuzzer_context, + transformations) {} + + ~FuzzerPassMock() override = default; + + const std::unordered_set<uint32_t>& GetReachedInstructions() const { + return reached_ids_; + } + + void Apply() override { + ForEachInstructionWithInstructionDescriptor( + [this](opt::Function* /*unused*/, opt::BasicBlock* /*unused*/, + opt::BasicBlock::iterator inst_it, + const protobufs::InstructionDescriptor& /*unused*/) { + if (inst_it->result_id()) { + reached_ids_.insert(inst_it->result_id()); + } + }); + } + + private: + std::unordered_set<uint32_t> reached_ids_; +}; + +TEST(FuzzerPassTest, ForEachInstructionWithInstructionDescriptor) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %7 = OpUndef %6 + OpReturn + %8 = OpLabel + %9 = OpUndef %6 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Check that %5 is reachable and %8 is unreachable as expected. + const auto* dominator_analysis = + context->GetDominatorAnalysis(context->GetFunction(4)); + ASSERT_TRUE(dominator_analysis->IsReachable(5)); + ASSERT_FALSE(dominator_analysis->IsReachable(8)); + + PseudoRandomGenerator prng(0); + FuzzerContext fuzzer_context(&prng, 100); + protobufs::TransformationSequence transformations; + FuzzerPassMock fuzzer_pass_mock(context.get(), &transformation_context, + &fuzzer_context, &transformations); + fuzzer_pass_mock.Apply(); + + ASSERT_TRUE(fuzzer_pass_mock.GetReachedInstructions().count(7)); + ASSERT_FALSE(fuzzer_pass_mock.GetReachedInstructions().count(9)); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp index 8a23574a..bfcf4ea9 100644 --- a/test/fuzz/fuzzer_replayer_test.cpp +++ b/test/fuzz/fuzzer_replayer_test.cpp @@ -14,6 +14,7 @@ #include "source/fuzz/fuzzer.h" #include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/replayer.h" #include "source/fuzz/uniform_buffer_element_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -1639,43 +1640,52 @@ void RunFuzzerAndReplayer(const std::string& shader, }); } + std::vector<Fuzzer::RepeatedPassStrategy> strategies{ + Fuzzer::RepeatedPassStrategy::kSimple, + Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations, + Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations}; + uint32_t strategy_index = 0; for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) { - std::vector<uint32_t> fuzzer_binary_out; - protobufs::TransformationSequence fuzzer_transformation_sequence_out; - spvtools::ValidatorOptions validator_options; - Fuzzer fuzzer(env, seed, true, validator_options); - fuzzer.SetMessageConsumer(kConsoleMessageConsumer); - auto fuzzer_result_status = - fuzzer.Run(binary_in, initial_facts, donor_suppliers, - &fuzzer_binary_out, &fuzzer_transformation_sequence_out); - ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status); - ASSERT_TRUE(t.Validate(fuzzer_binary_out)); + // Every 4th time we run the fuzzer, enable all fuzzer passes. + bool enable_all_passes = (seed % 4) == 0; + auto fuzzer_result = + Fuzzer(env, kSilentConsumer, binary_in, initial_facts, donor_suppliers, + MakeUnique<PseudoRandomGenerator>(seed), enable_all_passes, + strategies[strategy_index], true, validator_options) + .Run(); + + // Cycle the repeated pass strategy so that we try a different one next time + // we run the fuzzer. + strategy_index = + (strategy_index + 1) % static_cast<uint32_t>(strategies.size()); - std::vector<uint32_t> replayer_binary_out; - protobufs::TransformationSequence replayer_transformation_sequence_out; + ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status); + ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary)); - Replayer replayer(env, false, validator_options); - replayer.SetMessageConsumer(kConsoleMessageConsumer); - auto replayer_result_status = replayer.Run( - binary_in, initial_facts, fuzzer_transformation_sequence_out, - static_cast<uint32_t>( - fuzzer_transformation_sequence_out.transformation_size()), - &replayer_binary_out, &replayer_transformation_sequence_out); + auto replayer_result = + Replayer( + env, kConsoleMessageConsumer, binary_in, initial_facts, + fuzzer_result.applied_transformations, + static_cast<uint32_t>( + fuzzer_result.applied_transformations.transformation_size()), + 0, false, validator_options) + .Run(); ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, - replayer_result_status); + replayer_result.status); // After replaying the transformations applied by the fuzzer, exactly those // transformations should have been applied, and the binary resulting from // replay should be identical to that which resulted from fuzzing. std::string fuzzer_transformations_string; std::string replayer_transformations_string; - fuzzer_transformation_sequence_out.SerializeToString( + fuzzer_result.applied_transformations.SerializeToString( &fuzzer_transformations_string); - replayer_transformation_sequence_out.SerializeToString( + replayer_result.applied_transformations.SerializeToString( &replayer_transformations_string); ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string); - ASSERT_EQ(fuzzer_binary_out, replayer_binary_out); + ASSERT_EQ(fuzzer_result.transformed_binary, + replayer_result.transformed_binary); } } diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp index 24b44602..361f5e81 100644 --- a/test/fuzz/fuzzer_shrinker_test.cpp +++ b/test/fuzz/fuzzer_shrinker_test.cpp @@ -991,25 +991,25 @@ void RunAndCheckShrinker( uint32_t expected_transformations_out_size, uint32_t step_limit, spv_validator_options validator_options) { // Run the shrinker. - Shrinker shrinker(target_env, step_limit, false, validator_options); - shrinker.SetMessageConsumer(kSilentConsumer); + auto shrinker_result = + Shrinker(target_env, kSilentConsumer, binary_in, initial_facts, + transformation_sequence_in, interestingness_function, step_limit, + false, validator_options) + .Run(); - std::vector<uint32_t> binary_out; - protobufs::TransformationSequence transformations_out; - Shrinker::ShrinkerResultStatus shrinker_result_status = - shrinker.Run(binary_in, initial_facts, transformation_sequence_in, - interestingness_function, &binary_out, &transformations_out); ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete == - shrinker_result_status || + shrinker_result.status || Shrinker::ShrinkerResultStatus::kStepLimitReached == - shrinker_result_status); + shrinker_result.status); // If a non-empty expected binary was provided, check that it matches the // result of shrinking and that the expected number of transformations remain. if (!expected_binary_out.empty()) { - ASSERT_EQ(expected_binary_out, binary_out); - ASSERT_EQ(expected_transformations_out_size, - static_cast<uint32_t>(transformations_out.transformation_size())); + ASSERT_EQ(expected_binary_out, shrinker_result.transformed_binary); + ASSERT_EQ( + expected_transformations_out_size, + static_cast<uint32_t>( + shrinker_result.applied_transformations.transformation_size())); } } @@ -1037,16 +1037,29 @@ void RunFuzzerAndShrinker(const std::string& shader, } // Run the fuzzer and check that it successfully yields a valid binary. - std::vector<uint32_t> fuzzer_binary_out; - protobufs::TransformationSequence fuzzer_transformation_sequence_out; spvtools::ValidatorOptions validator_options; - Fuzzer fuzzer(env, seed, true, validator_options); - fuzzer.SetMessageConsumer(kSilentConsumer); - auto fuzzer_result_status = - fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out, - &fuzzer_transformation_sequence_out); - ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status); - ASSERT_TRUE(t.Validate(fuzzer_binary_out)); + + // Depending on the seed, decide whether to enable all passes and which + // repeated pass manager to use. + bool enable_all_passes = (seed % 4) == 0; + Fuzzer::RepeatedPassStrategy repeated_pass_strategy; + if ((seed % 3) == 0) { + repeated_pass_strategy = Fuzzer::RepeatedPassStrategy::kSimple; + } else if ((seed % 3) == 1) { + repeated_pass_strategy = + Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + } else { + repeated_pass_strategy = + Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations; + } + + auto fuzzer_result = + Fuzzer(env, kSilentConsumer, binary_in, initial_facts, donor_suppliers, + MakeUnique<PseudoRandomGenerator>(seed), enable_all_passes, + repeated_pass_strategy, true, validator_options) + .Run(); + ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status); + ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary)); const uint32_t kReasonableStepLimit = 50; const uint32_t kSmallStepLimit = 20; @@ -1054,30 +1067,30 @@ void RunFuzzerAndShrinker(const std::string& shader, // With the AlwaysInteresting test, we should quickly shrink to the original // binary with no transformations remaining. RunAndCheckShrinker(env, binary_in, initial_facts, - fuzzer_transformation_sequence_out, + fuzzer_result.applied_transformations, AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit, validator_options); // With the OnlyInterestingFirstTime test, no shrinking should be achieved. RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_transformation_sequence_out, - OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out, + env, binary_in, initial_facts, fuzzer_result.applied_transformations, + OnlyInterestingFirstTime().AsFunction(), fuzzer_result.transformed_binary, static_cast<uint32_t>( - fuzzer_transformation_sequence_out.transformation_size()), + fuzzer_result.applied_transformations.transformation_size()), kReasonableStepLimit, validator_options); // The PingPong test is unpredictable; passing an empty expected binary // means that we don't check anything beyond that shrinking completes // successfully. RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_transformation_sequence_out, + env, binary_in, initial_facts, fuzzer_result.applied_transformations, PingPong().AsFunction(), {}, 0, kSmallStepLimit, validator_options); // The InterestingThenRandom test is unpredictable; passing an empty // expected binary means that we do not check anything about shrinking // results. RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_transformation_sequence_out, + env, binary_in, initial_facts, fuzzer_result.applied_transformations, InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0, kSmallStepLimit, validator_options); } @@ -1111,6 +1124,18 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) { *temp.mutable_constant_uniform_fact() = resolution_y_eq_100; *facts.mutable_fact()->Add() = temp; } + // Also add an invalid fact, which should be ignored. + { + protobufs::FactConstantUniform bad_fact; + // The descriptor set, binding and indices used here deliberately make no + // sense. + *bad_fact.mutable_uniform_buffer_element_descriptor() = + MakeUniformBufferElementDescriptor(22, 33, {44, 55}); + *bad_fact.mutable_constant_word()->Add() = 100; + protobufs::Fact temp; + *temp.mutable_constant_uniform_fact() = bad_fact; + *facts.mutable_fact()->Add() = temp; + } // Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen // arbitrarily). diff --git a/test/fuzz/instruction_descriptor_test.cpp b/test/fuzz/instruction_descriptor_test.cpp index 5165cfb0..c905aa15 100644 --- a/test/fuzz/instruction_descriptor_test.cpp +++ b/test/fuzz/instruction_descriptor_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/instruction_descriptor.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { diff --git a/test/fuzz/replayer_test.cpp b/test/fuzz/replayer_test.cpp index 5d6169e1..2444e9f8 100644 --- a/test/fuzz/replayer_test.cpp +++ b/test/fuzz/replayer_test.cpp @@ -89,20 +89,17 @@ TEST(ReplayerTest, PartialReplay) { { // Full replay - protobufs::TransformationSequence transformations_out; protobufs::FactSequence empty_facts; - std::vector<uint32_t> binary_out; - Replayer replayer(env, true, validator_options); - replayer.SetMessageConsumer(kSilentConsumer); - auto replayer_result_status = - replayer.Run(binary_in, empty_facts, transformations, 11, &binary_out, - &transformations_out); + auto replayer_result = + Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations, + 11, 0, true, validator_options) + .Run(); // Replay should succeed. ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, - replayer_result_status); + replayer_result.status); // All transformations should be applied. ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( - transformations, transformations_out)); + transformations, replayer_result.applied_transformations)); const std::string kFullySplitShader = R"( OpCapability Shader @@ -172,28 +169,26 @@ TEST(ReplayerTest, PartialReplay) { OpReturn OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, kFullySplitShader, binary_out)); + ASSERT_TRUE( + IsEqual(env, kFullySplitShader, replayer_result.transformed_binary)); } { // Half replay - protobufs::TransformationSequence transformations_out; protobufs::FactSequence empty_facts; - std::vector<uint32_t> binary_out; - Replayer replayer(env, true, validator_options); - replayer.SetMessageConsumer(kSilentConsumer); - auto replayer_result_status = - replayer.Run(binary_in, empty_facts, transformations, 5, &binary_out, - &transformations_out); + auto replayer_result = + Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations, + 5, 0, true, validator_options) + .Run(); // Replay should succeed. ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, - replayer_result_status); + replayer_result.status); // The first 5 transformations should be applied - ASSERT_EQ(5, transformations_out.transformation_size()); + ASSERT_EQ(5, replayer_result.applied_transformations.transformation_size()); for (uint32_t i = 0; i < 5; i++) { ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals( transformations.transformation(i), - transformations_out.transformation(i))); + replayer_result.applied_transformations.transformation(i))); } const std::string kHalfSplitShader = R"( @@ -252,47 +247,42 @@ TEST(ReplayerTest, PartialReplay) { OpReturn OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, kHalfSplitShader, binary_out)); + ASSERT_TRUE( + IsEqual(env, kHalfSplitShader, replayer_result.transformed_binary)); } { // Empty replay - protobufs::TransformationSequence transformations_out; protobufs::FactSequence empty_facts; - std::vector<uint32_t> binary_out; - Replayer replayer(env, true, validator_options); - replayer.SetMessageConsumer(kSilentConsumer); - auto replayer_result_status = - replayer.Run(binary_in, empty_facts, transformations, 0, &binary_out, - &transformations_out); + auto replayer_result = + Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations, + 0, 0, true, validator_options) + .Run(); // Replay should succeed. ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, - replayer_result_status); + replayer_result.status); // No transformations should be applied - ASSERT_EQ(0, transformations_out.transformation_size()); - ASSERT_TRUE(IsEqual(env, kTestShader, binary_out)); + ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); + ASSERT_TRUE(IsEqual(env, kTestShader, replayer_result.transformed_binary)); } { // Invalid replay: too many transformations - protobufs::TransformationSequence transformations_out; protobufs::FactSequence empty_facts; - std::vector<uint32_t> binary_out; // The number of transformations requested to be applied exceeds the number // of transformations - Replayer replayer(env, true, validator_options); - replayer.SetMessageConsumer(kSilentConsumer); - auto replayer_result_status = - replayer.Run(binary_in, empty_facts, transformations, 12, &binary_out, - &transformations_out); + auto replayer_result = + Replayer(env, kSilentConsumer, binary_in, empty_facts, transformations, + 12, 0, true, validator_options) + .Run(); // Replay should not succeed. ASSERT_EQ(Replayer::ReplayerResultStatus::kTooManyTransformationsRequested, - replayer_result_status); + replayer_result.status); // No transformations should be applied - ASSERT_EQ(0, transformations_out.transformation_size()); + ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size()); // The output binary should be empty - ASSERT_TRUE(binary_out.empty()); + ASSERT_TRUE(replayer_result.transformed_binary.empty()); } } diff --git a/test/fuzz/transformation_access_chain_test.cpp b/test/fuzz/transformation_access_chain_test.cpp index 905e6c2d..5f1d25ac 100644 --- a/test/fuzz/transformation_access_chain_test.cpp +++ b/test/fuzz/transformation_access_chain_test.cpp @@ -117,7 +117,7 @@ TEST(TransformationAccessChainTest, BasicTest) { // Indices 0-5 are in ids 80-85 - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -150,7 +150,7 @@ TEST(TransformationAccessChainTest, BasicTest) { 100, 43, {1000}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) .IsApplicable(context.get(), transformation_context)); - // Bad: index id is not a constant + // Bad: index id is not a constant and the pointer refers to a struct ASSERT_FALSE(TransformationAccessChain( 100, 43, {24}, MakeInstructionDescriptor(25, SpvOpIAdd, 0)) .IsApplicable(context.get(), transformation_context)); @@ -161,9 +161,9 @@ TEST(TransformationAccessChainTest, BasicTest) { MakeInstructionDescriptor(24, SpvOpLoad, 0)) .IsApplicable(context.get(), transformation_context)); - // Bad: index id is out of bounds + // Bad: index id is out of bounds when accessing a struct ASSERT_FALSE( - TransformationAccessChain(100, 43, {80, 83}, + TransformationAccessChain(100, 43, {83, 80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)) .IsApplicable(context.get(), transformation_context)); @@ -172,6 +172,12 @@ TEST(TransformationAccessChainTest, BasicTest) { 100, 34, {}, MakeInstructionDescriptor(36, SpvOpVariable, 0)) .IsApplicable(context.get(), transformation_context)); + // Bad: OpTypeBool must be present in the module to clamp an index + ASSERT_FALSE( + TransformationAccessChain(100, 36, {80, 81}, + MakeInstructionDescriptor(37, SpvOpStore, 0)) + .IsApplicable(context.get(), transformation_context)); + // Bad: pointer not available ASSERT_FALSE( TransformationAccessChain( @@ -230,18 +236,7 @@ TEST(TransformationAccessChainTest, BasicTest) { { TransformationAccessChain transformation( - 102, 36, {80, 81}, MakeInstructionDescriptor(37, SpvOpStore, 0)); - ASSERT_TRUE( - transformation.IsApplicable(context.get(), transformation_context)); - transformation.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_FALSE( - transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102)); - } - - { - TransformationAccessChain transformation( - 103, 44, {}, MakeInstructionDescriptor(44, SpvOpStore, 0)); + 102, 44, {}, MakeInstructionDescriptor(44, SpvOpStore, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); @@ -252,7 +247,7 @@ TEST(TransformationAccessChainTest, BasicTest) { { TransformationAccessChain transformation( - 104, 13, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0)); + 103, 13, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); @@ -263,7 +258,7 @@ TEST(TransformationAccessChainTest, BasicTest) { { TransformationAccessChain transformation( - 105, 34, {}, MakeInstructionDescriptor(44, SpvOpStore, 1)); + 104, 34, {}, MakeInstructionDescriptor(44, SpvOpStore, 1)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); @@ -274,7 +269,7 @@ TEST(TransformationAccessChainTest, BasicTest) { { TransformationAccessChain transformation( - 106, 38, {}, MakeInstructionDescriptor(40, SpvOpFunctionCall, 0)); + 105, 38, {}, MakeInstructionDescriptor(40, SpvOpFunctionCall, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); @@ -285,7 +280,7 @@ TEST(TransformationAccessChainTest, BasicTest) { { TransformationAccessChain transformation( - 107, 14, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); + 106, 14, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); @@ -294,28 +289,6 @@ TEST(TransformationAccessChainTest, BasicTest) { transformation_context.GetFactManager()->PointeeValueIsIrrelevant(107)); } - { - TransformationAccessChain transformation( - 108, 54, {85, 81, 81}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); - ASSERT_TRUE( - transformation.IsApplicable(context.get(), transformation_context)); - transformation.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE( - transformation_context.GetFactManager()->PointeeValueIsIrrelevant(108)); - } - - { - TransformationAccessChain transformation( - 109, 48, {80, 80}, MakeInstructionDescriptor(24, SpvOpLoad, 0)); - ASSERT_TRUE( - transformation.IsApplicable(context.get(), transformation_context)); - transformation.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_FALSE( - transformation_context.GetFactManager()->PointeeValueIsIrrelevant(109)); - } - std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -367,16 +340,15 @@ TEST(TransformationAccessChainTest, BasicTest) { %36 = OpVariable %9 Function %38 = OpVariable %11 Function %44 = OpCopyObject %9 %36 - %103 = OpAccessChain %9 %44 + %102 = OpAccessChain %9 %44 OpStore %28 %33 - %105 = OpAccessChain %11 %34 + %104 = OpAccessChain %11 %34 OpStore %34 %35 %37 = OpLoad %8 %28 - %102 = OpAccessChain %20 %36 %80 %81 OpStore %36 %37 %39 = OpLoad %10 %34 OpStore %38 %39 - %106 = OpAccessChain %11 %38 + %105 = OpAccessChain %11 %38 %40 = OpFunctionCall %10 %15 %36 %38 %41 = OpLoad %10 %34 %42 = OpIAdd %10 %41 %40 @@ -388,15 +360,13 @@ TEST(TransformationAccessChainTest, BasicTest) { %13 = OpFunctionParameter %9 %14 = OpFunctionParameter %11 %16 = OpLabel - %104 = OpAccessChain %70 %13 %80 + %103 = OpAccessChain %70 %13 %80 %21 = OpAccessChain %20 %13 %17 %19 %43 = OpCopyObject %9 %13 %22 = OpLoad %6 %21 %23 = OpConvertFToS %10 %22 %100 = OpAccessChain %70 %43 %80 - %107 = OpAccessChain %11 %14 - %108 = OpAccessChain %99 %54 %85 %81 %81 - %109 = OpAccessChain %99 %48 %80 %80 + %106 = OpAccessChain %11 %14 %24 = OpLoad %10 %14 %25 = OpIAdd %10 %23 %24 OpReturnValue %25 @@ -433,7 +403,7 @@ TEST(TransformationAccessChainTest, IsomorphicStructs) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -481,6 +451,237 @@ TEST(TransformationAccessChainTest, IsomorphicStructs) { ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } +TEST(TransformationAccessChainTest, ClampingVariables) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %4 = OpTypeVoid + %5 = OpTypeBool + %6 = OpTypeFunction %4 + %7 = OpTypeInt 32 1 + %8 = OpTypeVector %7 4 + %9 = OpTypePointer Function %8 + %10 = OpConstant %7 0 + %11 = OpConstant %7 1 + %12 = OpConstant %7 3 + %13 = OpConstant %7 2 + %14 = OpConstantComposite %8 %10 %11 %12 %13 + %15 = OpTypePointer Function %7 + %16 = OpTypeInt 32 0 + %17 = OpConstant %16 1 + %18 = OpConstant %16 3 + %19 = OpTypeStruct %8 + %20 = OpTypePointer Function %19 + %21 = OpConstant %7 9 + %22 = OpConstant %16 10 + %23 = OpTypeArray %19 %22 + %24 = OpTypePointer Function %23 + %25 = OpTypeFloat 32 + %26 = OpTypeVector %25 4 + %27 = OpTypePointer Output %26 + %3 = OpVariable %27 Output + %2 = OpFunction %4 None %6 + %28 = OpLabel + %29 = OpVariable %9 Function + %30 = OpVariable %15 Function + %31 = OpVariable %15 Function + %32 = OpVariable %20 Function + %33 = OpVariable %15 Function + %34 = OpVariable %24 Function + OpStore %29 %14 + OpStore %30 %10 + %36 = OpLoad %7 %30 + %38 = OpLoad %8 %29 + %39 = OpCompositeConstruct %19 %38 + %40 = OpLoad %7 %30 + %42 = OpLoad %8 %29 + %43 = OpCompositeConstruct %19 %42 + %45 = OpLoad %7 %30 + %46 = OpLoad %7 %33 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: no ids given for clamping + ASSERT_FALSE(TransformationAccessChain( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: an id given for clamping is not fresh + ASSERT_FALSE(TransformationAccessChain( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0), + {{46, 201}}) + .IsApplicable(context.get(), transformation_context)); + + // Bad: an id given for clamping is not fresh + ASSERT_FALSE(TransformationAccessChain( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0), + {{200, 46}}) + .IsApplicable(context.get(), transformation_context)); + + // Bad: an id given for clamping is the same as the id for the access chain + ASSERT_FALSE(TransformationAccessChain( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0), + {{100, 201}}) + .IsApplicable(context.get(), transformation_context)); + + // Bad: the fresh ids given are not distinct + ASSERT_FALSE(TransformationAccessChain( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0), + {{200, 200}}) + .IsApplicable(context.get(), transformation_context)); + + // Bad: not enough ids given for clamping (2 pairs needed) + ASSERT_FALSE( + TransformationAccessChain(104, 34, {45, 10, 46}, + MakeInstructionDescriptor(46, SpvOpReturn, 0), + {{208, 209}, {209, 211}}) + .IsApplicable(context.get(), transformation_context)); + + // Bad: the fresh ids given are not distinct + ASSERT_FALSE( + TransformationAccessChain(104, 34, {45, 10, 46}, + MakeInstructionDescriptor(46, SpvOpReturn, 0), + {{208, 209}, {209, 211}}) + .IsApplicable(context.get(), transformation_context)); + + { + TransformationAccessChain transformation( + 100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0), + {{200, 201}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + { + TransformationAccessChain transformation( + 101, 29, {36}, MakeInstructionDescriptor(38, SpvOpLoad, 0), + {{202, 203}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + { + TransformationAccessChain transformation( + 102, 32, {10, 40}, MakeInstructionDescriptor(42, SpvOpLoad, 0), + {{204, 205}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + { + TransformationAccessChain transformation( + 103, 34, {11}, MakeInstructionDescriptor(45, SpvOpLoad, 0), + {{206, 207}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + { + TransformationAccessChain transformation( + 104, 34, {45, 10, 46}, MakeInstructionDescriptor(46, SpvOpReturn, 0), + {{208, 209}, {210, 211}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %4 = OpTypeVoid + %5 = OpTypeBool + %6 = OpTypeFunction %4 + %7 = OpTypeInt 32 1 + %8 = OpTypeVector %7 4 + %9 = OpTypePointer Function %8 + %10 = OpConstant %7 0 + %11 = OpConstant %7 1 + %12 = OpConstant %7 3 + %13 = OpConstant %7 2 + %14 = OpConstantComposite %8 %10 %11 %12 %13 + %15 = OpTypePointer Function %7 + %16 = OpTypeInt 32 0 + %17 = OpConstant %16 1 + %18 = OpConstant %16 3 + %19 = OpTypeStruct %8 + %20 = OpTypePointer Function %19 + %21 = OpConstant %7 9 + %22 = OpConstant %16 10 + %23 = OpTypeArray %19 %22 + %24 = OpTypePointer Function %23 + %25 = OpTypeFloat 32 + %26 = OpTypeVector %25 4 + %27 = OpTypePointer Output %26 + %3 = OpVariable %27 Output + %2 = OpFunction %4 None %6 + %28 = OpLabel + %29 = OpVariable %9 Function + %30 = OpVariable %15 Function + %31 = OpVariable %15 Function + %32 = OpVariable %20 Function + %33 = OpVariable %15 Function + %34 = OpVariable %24 Function + OpStore %29 %14 + OpStore %30 %10 + %200 = OpULessThanEqual %5 %17 %18 + %201 = OpSelect %16 %200 %17 %18 + %100 = OpAccessChain %15 %29 %201 + %36 = OpLoad %7 %30 + %202 = OpULessThanEqual %5 %36 %12 + %203 = OpSelect %7 %202 %36 %12 + %101 = OpAccessChain %15 %29 %203 + %38 = OpLoad %8 %29 + %39 = OpCompositeConstruct %19 %38 + %40 = OpLoad %7 %30 + %204 = OpULessThanEqual %5 %40 %12 + %205 = OpSelect %7 %204 %40 %12 + %102 = OpAccessChain %15 %32 %10 %205 + %42 = OpLoad %8 %29 + %43 = OpCompositeConstruct %19 %42 + %206 = OpULessThanEqual %5 %11 %21 + %207 = OpSelect %7 %206 %11 %21 + %103 = OpAccessChain %20 %34 %207 + %45 = OpLoad %7 %30 + %46 = OpLoad %7 %33 + %208 = OpULessThanEqual %5 %45 %21 + %209 = OpSelect %7 %208 %45 %21 + %210 = OpULessThanEqual %5 %46 %12 + %211 = OpSelect %7 %210 %46 %12 + %104 = OpAccessChain %15 %34 %209 %10 %211 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp new file mode 100644 index 00000000..39fa3ccf --- /dev/null +++ b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp @@ -0,0 +1,464 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_bit_instruction_synonym.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddBitInstructionSynonymTest, IsApplicable) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %37 "main" + +; Types + %2 = OpTypeInt 32 0 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstant %2 0 + %6 = OpConstant %2 1 + %7 = OpConstant %2 2 + %8 = OpConstant %2 3 + %9 = OpConstant %2 4 + %10 = OpConstant %2 5 + %11 = OpConstant %2 6 + %12 = OpConstant %2 7 + %13 = OpConstant %2 8 + %14 = OpConstant %2 9 + %15 = OpConstant %2 10 + %16 = OpConstant %2 11 + %17 = OpConstant %2 12 + %18 = OpConstant %2 13 + %19 = OpConstant %2 14 + %20 = OpConstant %2 15 + %21 = OpConstant %2 16 + %22 = OpConstant %2 17 + %23 = OpConstant %2 18 + %24 = OpConstant %2 19 + %25 = OpConstant %2 20 + %26 = OpConstant %2 21 + %27 = OpConstant %2 22 + %28 = OpConstant %2 23 + %29 = OpConstant %2 24 + %30 = OpConstant %2 25 + %31 = OpConstant %2 26 + %32 = OpConstant %2 27 + %33 = OpConstant %2 28 + %34 = OpConstant %2 29 + %35 = OpConstant %2 30 + %36 = OpConstant %2 31 + +; main function + %37 = OpFunction %3 None %4 + %38 = OpLabel + %39 = OpBitwiseOr %2 %5 %6 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests undefined bit instruction. + auto transformation = TransformationAddBitInstructionSynonym( + 40, {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, + 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, + 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, + 158, 159, 160, 161, 162, 163, 164, 165, 166, 167}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests false bit instruction. + transformation = TransformationAddBitInstructionSynonym( + 38, {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests the number of fresh ids being different than the necessary. + transformation = TransformationAddBitInstructionSynonym( + 39, + {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests non-fresh ids. + transformation = TransformationAddBitInstructionSynonym( + 39, {38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, + 156, 157, 158, 159, 160, 161, 162, 163, 164, 165}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests applicable transformation. + transformation = TransformationAddBitInstructionSynonym( + 39, {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddBitInstructionSynonymTest, AddBitwiseSynonym) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %37 "main" + +; Types + %2 = OpTypeInt 32 0 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstant %2 0 + %6 = OpConstant %2 1 + %7 = OpConstant %2 2 + %8 = OpConstant %2 3 + %9 = OpConstant %2 4 + %10 = OpConstant %2 5 + %11 = OpConstant %2 6 + %12 = OpConstant %2 7 + %13 = OpConstant %2 8 + %14 = OpConstant %2 9 + %15 = OpConstant %2 10 + %16 = OpConstant %2 11 + %17 = OpConstant %2 12 + %18 = OpConstant %2 13 + %19 = OpConstant %2 14 + %20 = OpConstant %2 15 + %21 = OpConstant %2 16 + %22 = OpConstant %2 17 + %23 = OpConstant %2 18 + %24 = OpConstant %2 19 + %25 = OpConstant %2 20 + %26 = OpConstant %2 21 + %27 = OpConstant %2 22 + %28 = OpConstant %2 23 + %29 = OpConstant %2 24 + %30 = OpConstant %2 25 + %31 = OpConstant %2 26 + %32 = OpConstant %2 27 + %33 = OpConstant %2 28 + %34 = OpConstant %2 29 + %35 = OpConstant %2 30 + %36 = OpConstant %2 31 + +; main function + %37 = OpFunction %3 None %4 + %38 = OpLabel + %39 = OpBitwiseOr %2 %5 %6 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationAddBitInstructionSynonym( + 39, {40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166}); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %37 "main" + +; Types + %2 = OpTypeInt 32 0 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstant %2 0 + %6 = OpConstant %2 1 + %7 = OpConstant %2 2 + %8 = OpConstant %2 3 + %9 = OpConstant %2 4 + %10 = OpConstant %2 5 + %11 = OpConstant %2 6 + %12 = OpConstant %2 7 + %13 = OpConstant %2 8 + %14 = OpConstant %2 9 + %15 = OpConstant %2 10 + %16 = OpConstant %2 11 + %17 = OpConstant %2 12 + %18 = OpConstant %2 13 + %19 = OpConstant %2 14 + %20 = OpConstant %2 15 + %21 = OpConstant %2 16 + %22 = OpConstant %2 17 + %23 = OpConstant %2 18 + %24 = OpConstant %2 19 + %25 = OpConstant %2 20 + %26 = OpConstant %2 21 + %27 = OpConstant %2 22 + %28 = OpConstant %2 23 + %29 = OpConstant %2 24 + %30 = OpConstant %2 25 + %31 = OpConstant %2 26 + %32 = OpConstant %2 27 + %33 = OpConstant %2 28 + %34 = OpConstant %2 29 + %35 = OpConstant %2 30 + %36 = OpConstant %2 31 + +; main function + %37 = OpFunction %3 None %4 + %38 = OpLabel + + %40 = OpBitFieldUExtract %2 %5 %5 %6 ; extracts bit 0 from %5 + %41 = OpBitFieldUExtract %2 %6 %5 %6 ; extracts bit 0 from %6 + %42 = OpBitwiseOr %2 %40 %41 + + %43 = OpBitFieldUExtract %2 %5 %6 %6 ; extracts bit 1 from %5 + %44 = OpBitFieldUExtract %2 %6 %6 %6 ; extracts bit 1 from %6 + %45 = OpBitwiseOr %2 %43 %44 + + %46 = OpBitFieldUExtract %2 %5 %7 %6 ; extracts bit 2 from %5 + %47 = OpBitFieldUExtract %2 %6 %7 %6 ; extracts bit 2 from %6 + %48 = OpBitwiseOr %2 %46 %47 + + %49 = OpBitFieldUExtract %2 %5 %8 %6 ; extracts bit 3 from %5 + %50 = OpBitFieldUExtract %2 %6 %8 %6 ; extracts bit 3 from %6 + %51 = OpBitwiseOr %2 %49 %50 + + %52 = OpBitFieldUExtract %2 %5 %9 %6 ; extracts bit 4 from %5 + %53 = OpBitFieldUExtract %2 %6 %9 %6 ; extracts bit 4 from %6 + %54 = OpBitwiseOr %2 %52 %53 + + %55 = OpBitFieldUExtract %2 %5 %10 %6 ; extracts bit 5 from %5 + %56 = OpBitFieldUExtract %2 %6 %10 %6 ; extracts bit 5 from %6 + %57 = OpBitwiseOr %2 %55 %56 + + %58 = OpBitFieldUExtract %2 %5 %11 %6 ; extracts bit 6 from %5 + %59 = OpBitFieldUExtract %2 %6 %11 %6 ; extracts bit 6 from %6 + %60 = OpBitwiseOr %2 %58 %59 + + %61 = OpBitFieldUExtract %2 %5 %12 %6 ; extracts bit 7 from %5 + %62 = OpBitFieldUExtract %2 %6 %12 %6 ; extracts bit 7 from %6 + %63 = OpBitwiseOr %2 %61 %62 + + %64 = OpBitFieldUExtract %2 %5 %13 %6 ; extracts bit 8 from %5 + %65 = OpBitFieldUExtract %2 %6 %13 %6 ; extracts bit 8 from %6 + %66 = OpBitwiseOr %2 %64 %65 + + %67 = OpBitFieldUExtract %2 %5 %14 %6 ; extracts bit 9 from %5 + %68 = OpBitFieldUExtract %2 %6 %14 %6 ; extracts bit 9 from %6 + %69 = OpBitwiseOr %2 %67 %68 + + %70 = OpBitFieldUExtract %2 %5 %15 %6 ; extracts bit 10 from %5 + %71 = OpBitFieldUExtract %2 %6 %15 %6 ; extracts bit 10 from %6 + %72 = OpBitwiseOr %2 %70 %71 + + %73 = OpBitFieldUExtract %2 %5 %16 %6 ; extracts bit 11 from %5 + %74 = OpBitFieldUExtract %2 %6 %16 %6 ; extracts bit 11 from %6 + %75 = OpBitwiseOr %2 %73 %74 + + %76 = OpBitFieldUExtract %2 %5 %17 %6 ; extracts bit 12 from %5 + %77 = OpBitFieldUExtract %2 %6 %17 %6 ; extracts bit 12 from %6 + %78 = OpBitwiseOr %2 %76 %77 + + %79 = OpBitFieldUExtract %2 %5 %18 %6 ; extracts bit 13 from %5 + %80 = OpBitFieldUExtract %2 %6 %18 %6 ; extracts bit 13 from %6 + %81 = OpBitwiseOr %2 %79 %80 + + %82 = OpBitFieldUExtract %2 %5 %19 %6 ; extracts bit 14 from %5 + %83 = OpBitFieldUExtract %2 %6 %19 %6 ; extracts bit 14 from %6 + %84 = OpBitwiseOr %2 %82 %83 + + %85 = OpBitFieldUExtract %2 %5 %20 %6 ; extracts bit 15 from %5 + %86 = OpBitFieldUExtract %2 %6 %20 %6 ; extracts bit 15 from %6 + %87 = OpBitwiseOr %2 %85 %86 + + %88 = OpBitFieldUExtract %2 %5 %21 %6 ; extracts bit 16 from %5 + %89 = OpBitFieldUExtract %2 %6 %21 %6 ; extracts bit 16 from %6 + %90 = OpBitwiseOr %2 %88 %89 + + %91 = OpBitFieldUExtract %2 %5 %22 %6 ; extracts bit 17 from %5 + %92 = OpBitFieldUExtract %2 %6 %22 %6 ; extracts bit 17 from %6 + %93 = OpBitwiseOr %2 %91 %92 + + %94 = OpBitFieldUExtract %2 %5 %23 %6 ; extracts bit 18 from %5 + %95 = OpBitFieldUExtract %2 %6 %23 %6 ; extracts bit 18 from %6 + %96 = OpBitwiseOr %2 %94 %95 + + %97 = OpBitFieldUExtract %2 %5 %24 %6 ; extracts bit 19 from %5 + %98 = OpBitFieldUExtract %2 %6 %24 %6 ; extracts bit 19 from %6 + %99 = OpBitwiseOr %2 %97 %98 + + %100 = OpBitFieldUExtract %2 %5 %25 %6 ; extracts bit 20 from %5 + %101 = OpBitFieldUExtract %2 %6 %25 %6 ; extracts bit 20 from %6 + %102 = OpBitwiseOr %2 %100 %101 + + %103 = OpBitFieldUExtract %2 %5 %26 %6 ; extracts bit 21 from %5 + %104 = OpBitFieldUExtract %2 %6 %26 %6 ; extracts bit 21 from %6 + %105 = OpBitwiseOr %2 %103 %104 + + %106 = OpBitFieldUExtract %2 %5 %27 %6 ; extracts bit 22 from %5 + %107 = OpBitFieldUExtract %2 %6 %27 %6 ; extracts bit 22 from %6 + %108 = OpBitwiseOr %2 %106 %107 + + %109 = OpBitFieldUExtract %2 %5 %28 %6 ; extracts bit 23 from %5 + %110 = OpBitFieldUExtract %2 %6 %28 %6 ; extracts bit 23 from %6 + %111 = OpBitwiseOr %2 %109 %110 + + %112 = OpBitFieldUExtract %2 %5 %29 %6 ; extracts bit 24 from %5 + %113 = OpBitFieldUExtract %2 %6 %29 %6 ; extracts bit 24 from %6 + %114 = OpBitwiseOr %2 %112 %113 + + %115 = OpBitFieldUExtract %2 %5 %30 %6 ; extracts bit 25 from %5 + %116 = OpBitFieldUExtract %2 %6 %30 %6 ; extracts bit 25 from %6 + %117 = OpBitwiseOr %2 %115 %116 + + %118 = OpBitFieldUExtract %2 %5 %31 %6 ; extracts bit 26 from %5 + %119 = OpBitFieldUExtract %2 %6 %31 %6 ; extracts bit 26 from %6 + %120 = OpBitwiseOr %2 %118 %119 + + %121 = OpBitFieldUExtract %2 %5 %32 %6 ; extracts bit 27 from %5 + %122 = OpBitFieldUExtract %2 %6 %32 %6 ; extracts bit 27 from %6 + %123 = OpBitwiseOr %2 %121 %122 + + %124 = OpBitFieldUExtract %2 %5 %33 %6 ; extracts bit 28 from %5 + %125 = OpBitFieldUExtract %2 %6 %33 %6 ; extracts bit 28 from %6 + %126 = OpBitwiseOr %2 %124 %125 + + %127 = OpBitFieldUExtract %2 %5 %34 %6 ; extracts bit 29 from %5 + %128 = OpBitFieldUExtract %2 %6 %34 %6 ; extracts bit 29 from %6 + %129 = OpBitwiseOr %2 %127 %128 + + %130 = OpBitFieldUExtract %2 %5 %35 %6 ; extracts bit 30 from %5 + %131 = OpBitFieldUExtract %2 %6 %35 %6 ; extracts bit 30 from %6 + %132 = OpBitwiseOr %2 %130 %131 + + %133 = OpBitFieldUExtract %2 %5 %36 %6 ; extracts bit 31 from %5 + %134 = OpBitFieldUExtract %2 %6 %36 %6 ; extracts bit 31 from %6 + %135 = OpBitwiseOr %2 %133 %134 + + %136 = OpBitFieldInsert %2 %42 %45 %6 %6 ; inserts bit 1 + %137 = OpBitFieldInsert %2 %136 %48 %7 %6 ; inserts bit 2 + %138 = OpBitFieldInsert %2 %137 %51 %8 %6 ; inserts bit 3 + %139 = OpBitFieldInsert %2 %138 %54 %9 %6 ; inserts bit 4 + %140 = OpBitFieldInsert %2 %139 %57 %10 %6 ; inserts bit 5 + %141 = OpBitFieldInsert %2 %140 %60 %11 %6 ; inserts bit 6 + %142 = OpBitFieldInsert %2 %141 %63 %12 %6 ; inserts bit 7 + %143 = OpBitFieldInsert %2 %142 %66 %13 %6 ; inserts bit 8 + %144 = OpBitFieldInsert %2 %143 %69 %14 %6 ; inserts bit 9 + %145 = OpBitFieldInsert %2 %144 %72 %15 %6 ; inserts bit 10 + %146 = OpBitFieldInsert %2 %145 %75 %16 %6 ; inserts bit 11 + %147 = OpBitFieldInsert %2 %146 %78 %17 %6 ; inserts bit 12 + %148 = OpBitFieldInsert %2 %147 %81 %18 %6 ; inserts bit 13 + %149 = OpBitFieldInsert %2 %148 %84 %19 %6 ; inserts bit 14 + %150 = OpBitFieldInsert %2 %149 %87 %20 %6 ; inserts bit 15 + %151 = OpBitFieldInsert %2 %150 %90 %21 %6 ; inserts bit 16 + %152 = OpBitFieldInsert %2 %151 %93 %22 %6 ; inserts bit 17 + %153 = OpBitFieldInsert %2 %152 %96 %23 %6 ; inserts bit 18 + %154 = OpBitFieldInsert %2 %153 %99 %24 %6 ; inserts bit 19 + %155 = OpBitFieldInsert %2 %154 %102 %25 %6 ; inserts bit 20 + %156 = OpBitFieldInsert %2 %155 %105 %26 %6 ; inserts bit 21 + %157 = OpBitFieldInsert %2 %156 %108 %27 %6 ; inserts bit 22 + %158 = OpBitFieldInsert %2 %157 %111 %28 %6 ; inserts bit 23 + %159 = OpBitFieldInsert %2 %158 %114 %29 %6 ; inserts bit 24 + %160 = OpBitFieldInsert %2 %159 %117 %30 %6 ; inserts bit 25 + %161 = OpBitFieldInsert %2 %160 %120 %31 %6 ; inserts bit 26 + %162 = OpBitFieldInsert %2 %161 %123 %32 %6 ; inserts bit 27 + %163 = OpBitFieldInsert %2 %162 %126 %33 %6 ; inserts bit 28 + %164 = OpBitFieldInsert %2 %163 %129 %34 %6 ; inserts bit 29 + %165 = OpBitFieldInsert %2 %164 %132 %35 %6 ; inserts bit 30 + %166 = OpBitFieldInsert %2 %165 %135 %36 %6 ; inserts bit 31 + %39 = OpBitwiseOr %2 %5 %6 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(166, {}), + MakeDataDescriptor(39, {}))); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_add_constant_boolean_test.cpp b/test/fuzz/transformation_add_constant_boolean_test.cpp index c6033336..3c379222 100644 --- a/test/fuzz/transformation_add_constant_boolean_test.cpp +++ b/test/fuzz/transformation_add_constant_boolean_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_constant_boolean.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -42,23 +43,29 @@ TEST(TransformationAddConstantBooleanTest, NeitherPresentInitiallyAddBoth) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // True and false can both be added as neither is present. - ASSERT_TRUE(TransformationAddConstantBoolean(7, true).IsApplicable( - context.get(), transformation_context)); - ASSERT_TRUE(TransformationAddConstantBoolean(7, false).IsApplicable( - context.get(), transformation_context)); + ASSERT_TRUE(TransformationAddConstantBoolean(7, true, false) + .IsApplicable(context.get(), transformation_context)); + ASSERT_TRUE(TransformationAddConstantBoolean(7, false, false) + .IsApplicable(context.get(), transformation_context)); + + // Irrelevant true and false can both be added as neither is present. + ASSERT_TRUE(TransformationAddConstantBoolean(7, true, true) + .IsApplicable(context.get(), transformation_context)); + ASSERT_TRUE(TransformationAddConstantBoolean(7, false, true) + .IsApplicable(context.get(), transformation_context)); // Id 5 is already taken. - ASSERT_FALSE(TransformationAddConstantBoolean(5, true).IsApplicable( - context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddConstantBoolean(5, true, false) + .IsApplicable(context.get(), transformation_context)); - auto add_true = TransformationAddConstantBoolean(7, true); - auto add_false = TransformationAddConstantBoolean(8, false); + auto add_true = TransformationAddConstantBoolean(7, true, false); + auto add_false = TransformationAddConstantBoolean(8, false, false); ASSERT_TRUE(add_true.IsApplicable(context.get(), transformation_context)); add_true.Apply(context.get(), &transformation_context); @@ -67,7 +74,7 @@ TEST(TransformationAddConstantBooleanTest, NeitherPresentInitiallyAddBoth) { // Having added true, we cannot add it again with the same id. ASSERT_FALSE(add_true.IsApplicable(context.get(), transformation_context)); // But we can add it with a different id. - auto add_true_again = TransformationAddConstantBoolean(100, true); + auto add_true_again = TransformationAddConstantBoolean(100, true, false); ASSERT_TRUE( add_true_again.IsApplicable(context.get(), transformation_context)); add_true_again.Apply(context.get(), &transformation_context); @@ -80,12 +87,31 @@ TEST(TransformationAddConstantBooleanTest, NeitherPresentInitiallyAddBoth) { // Having added false, we cannot add it again with the same id. ASSERT_FALSE(add_false.IsApplicable(context.get(), transformation_context)); // But we can add it with a different id. - auto add_false_again = TransformationAddConstantBoolean(101, false); + auto add_false_again = TransformationAddConstantBoolean(101, false, false); ASSERT_TRUE( add_false_again.IsApplicable(context.get(), transformation_context)); add_false_again.Apply(context.get(), &transformation_context); ASSERT_TRUE(IsValid(env, context.get())); + // We can create an irrelevant OpConstantTrue. + TransformationAddConstantBoolean irrelevant_true(102, true, true); + ASSERT_TRUE( + irrelevant_true.IsApplicable(context.get(), transformation_context)); + irrelevant_true.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // We can create an irrelevant OpConstantFalse. + TransformationAddConstantBoolean irrelevant_false(103, false, true); + ASSERT_TRUE( + irrelevant_false.IsApplicable(context.get(), transformation_context)); + irrelevant_false.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + ASSERT_FALSE(fact_manager.IdIsIrrelevant(100)); + ASSERT_FALSE(fact_manager.IdIsIrrelevant(101)); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(102)); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(103)); + std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -101,6 +127,8 @@ TEST(TransformationAddConstantBooleanTest, NeitherPresentInitiallyAddBoth) { %100 = OpConstantTrue %6 %8 = OpConstantFalse %6 %101 = OpConstantFalse %6 + %102 = OpConstantTrue %6 + %103 = OpConstantFalse %6 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn @@ -132,16 +160,22 @@ TEST(TransformationAddConstantBooleanTest, NoOpTypeBoolPresent) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Neither true nor false can be added as OpTypeBool is not present. - ASSERT_FALSE(TransformationAddConstantBoolean(6, true).IsApplicable( - context.get(), transformation_context)); - ASSERT_FALSE(TransformationAddConstantBoolean(6, false).IsApplicable( - context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddConstantBoolean(6, true, false) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddConstantBoolean(6, false, false) + .IsApplicable(context.get(), transformation_context)); + + // This does not depend on whether the constant is relevant or not. + ASSERT_FALSE(TransformationAddConstantBoolean(6, true, true) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddConstantBoolean(6, false, true) + .IsApplicable(context.get(), transformation_context)); } } // namespace diff --git a/test/fuzz/transformation_add_constant_composite_test.cpp b/test/fuzz/transformation_add_constant_composite_test.cpp index 021bf58e..e59c2ffe 100644 --- a/test/fuzz/transformation_add_constant_composite_test.cpp +++ b/test/fuzz/transformation_add_constant_composite_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_constant_composite.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -63,45 +64,68 @@ TEST(TransformationAddConstantCompositeTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Too few ids - ASSERT_FALSE(TransformationAddConstantComposite(103, 8, {100, 101}) + ASSERT_FALSE(TransformationAddConstantComposite(103, 8, {100, 101}, false) .IsApplicable(context.get(), transformation_context)); // Too many ids - ASSERT_FALSE(TransformationAddConstantComposite(101, 7, {14, 15, 14}) + ASSERT_FALSE(TransformationAddConstantComposite(101, 7, {14, 15, 14}, false) .IsApplicable(context.get(), transformation_context)); // Id already in use - ASSERT_FALSE(TransformationAddConstantComposite(40, 7, {11, 12}) + ASSERT_FALSE(TransformationAddConstantComposite(40, 7, {11, 12}, false) .IsApplicable(context.get(), transformation_context)); // %39 is not a type - ASSERT_FALSE(TransformationAddConstantComposite(100, 39, {11, 12}) + ASSERT_FALSE(TransformationAddConstantComposite(100, 39, {11, 12}, false) .IsApplicable(context.get(), transformation_context)); TransformationAddConstantComposite transformations[] = { // %100 = OpConstantComposite %7 %11 %12 - TransformationAddConstantComposite(100, 7, {11, 12}), + TransformationAddConstantComposite(100, 7, {11, 12}, false), // %101 = OpConstantComposite %7 %14 %15 - TransformationAddConstantComposite(101, 7, {14, 15}), + TransformationAddConstantComposite(101, 7, {14, 15}, false), // %102 = OpConstantComposite %7 %17 %18 - TransformationAddConstantComposite(102, 7, {17, 18}), + TransformationAddConstantComposite(102, 7, {17, 18}, false), // %103 = OpConstantComposite %8 %100 %101 %102 - TransformationAddConstantComposite(103, 8, {100, 101, 102}), + TransformationAddConstantComposite(103, 8, {100, 101, 102}, false), // %104 = OpConstantComposite %24 %29 %30 %31 - TransformationAddConstantComposite(104, 24, {29, 30, 31}), + TransformationAddConstantComposite(104, 24, {29, 30, 31}, false), // %105 = OpConstantComposite %26 %104 %33 - TransformationAddConstantComposite(105, 26, {104, 33}), + TransformationAddConstantComposite(105, 26, {104, 33}, false), // %106 = OpConstantComposite %35 %38 %39 %40 - TransformationAddConstantComposite(106, 35, {38, 39, 40})}; + TransformationAddConstantComposite(106, 35, {38, 39, 40}, false), + + // Same constants but with an irrelevant fact applied. + + // %107 = OpConstantComposite %7 %11 %12 + TransformationAddConstantComposite(107, 7, {11, 12}, true), + + // %108 = OpConstantComposite %7 %14 %15 + TransformationAddConstantComposite(108, 7, {14, 15}, true), + + // %109 = OpConstantComposite %7 %17 %18 + TransformationAddConstantComposite(109, 7, {17, 18}, true), + + // %110 = OpConstantComposite %8 %100 %101 %102 + TransformationAddConstantComposite(110, 8, {100, 101, 102}, true), + + // %111 = OpConstantComposite %24 %29 %30 %31 + TransformationAddConstantComposite(111, 24, {29, 30, 31}, true), + + // %112 = OpConstantComposite %26 %104 %33 + TransformationAddConstantComposite(112, 26, {104, 33}, true), + + // %113 = OpConstantComposite %35 %38 %39 %40 + TransformationAddConstantComposite(113, 35, {38, 39, 40}, true)}; for (auto& transformation : transformations) { ASSERT_TRUE( @@ -110,6 +134,14 @@ TEST(TransformationAddConstantCompositeTest, BasicTest) { } ASSERT_TRUE(IsValid(env, context.get())); + for (uint32_t id = 100; id <= 106; ++id) { + ASSERT_FALSE(fact_manager.IdIsIrrelevant(id)); + } + + for (uint32_t id = 107; id <= 113; ++id) { + ASSERT_TRUE(fact_manager.IdIsIrrelevant(id)); + } + std::string after_transformation = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -149,6 +181,13 @@ TEST(TransformationAddConstantCompositeTest, BasicTest) { %104 = OpConstantComposite %24 %29 %30 %31 %105 = OpConstantComposite %26 %104 %33 %106 = OpConstantComposite %35 %38 %39 %40 + %107 = OpConstantComposite %7 %11 %12 + %108 = OpConstantComposite %7 %14 %15 + %109 = OpConstantComposite %7 %17 %18 + %110 = OpConstantComposite %8 %100 %101 %102 + %111 = OpConstantComposite %24 %29 %30 %31 + %112 = OpConstantComposite %26 %104 %33 + %113 = OpConstantComposite %35 %38 %39 %40 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn diff --git a/test/fuzz/transformation_add_constant_null_test.cpp b/test/fuzz/transformation_add_constant_null_test.cpp index 0bfee34b..aba6a09a 100644 --- a/test/fuzz/transformation_add_constant_null_test.cpp +++ b/test/fuzz/transformation_add_constant_null_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_constant_null.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -49,7 +50,7 @@ TEST(TransformationAddConstantNullTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_constant_scalar_test.cpp b/test/fuzz/transformation_add_constant_scalar_test.cpp index 5124b7d8..61b5fe34 100644 --- a/test/fuzz/transformation_add_constant_scalar_test.cpp +++ b/test/fuzz/transformation_add_constant_scalar_test.cpp @@ -13,183 +13,330 @@ // limitations under the License. #include "source/fuzz/transformation_add_constant_scalar.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { -TEST(TransformationAddConstantScalarTest, BasicTest) { - std::string shader = R"( +TEST(TransformationAddConstantScalarTest, IsApplicable) { + std::string reference_shader = R"( OpCapability Shader + OpCapability Int64 + OpCapability Float64 %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - OpName %8 "x" - OpName %12 "y" - OpName %16 "z" - OpDecorate %8 RelaxedPrecision - OpDecorate %12 RelaxedPrecision - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeInt 32 1 - %7 = OpTypePointer Function %6 - %9 = OpConstant %6 1 - %10 = OpTypeInt 32 0 - %11 = OpTypePointer Function %10 - %13 = OpConstant %10 2 - %14 = OpTypeFloat 32 - %15 = OpTypePointer Function %14 - %17 = OpConstant %14 3 - %4 = OpFunction %2 None %3 - %5 = OpLabel - %8 = OpVariable %7 Function - %12 = OpVariable %11 Function - %16 = OpVariable %15 Function - OpStore %8 %9 - OpStore %12 %13 - OpStore %16 %17 + OpEntryPoint Vertex %17 "main" + +; Types + + ; 32-bit types + %2 = OpTypeInt 32 0 + %3 = OpTypeInt 32 1 + %4 = OpTypeFloat 32 + + ; 64-bit types + %5 = OpTypeInt 64 0 + %6 = OpTypeInt 64 1 + %7 = OpTypeFloat 64 + + %8 = OpTypePointer Private %2 + %9 = OpTypeVoid + %10 = OpTypeFunction %9 + +; Constants + + ; 32-bit constants + %11 = OpConstant %2 1 + %12 = OpConstant %3 2 + %13 = OpConstant %4 3 + + ; 64-bit constants + %14 = OpConstant %5 1 + %15 = OpConstant %6 2 + %16 = OpConstant %7 3 + +; main function + %17 = OpFunction %9 None %10 + %18 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const float float_values[2] = {3.0, 30.0}; - uint32_t uint_for_float[2]; - memcpy(uint_for_float, float_values, sizeof(float_values)); - - auto add_signed_int_1 = TransformationAddConstantScalar(100, 6, {1}); - auto add_signed_int_10 = TransformationAddConstantScalar(101, 6, {10}); - auto add_unsigned_int_2 = TransformationAddConstantScalar(102, 10, {2}); - auto add_unsigned_int_20 = TransformationAddConstantScalar(103, 10, {20}); - auto add_float_3 = - TransformationAddConstantScalar(104, 14, {uint_for_float[0]}); - auto add_float_30 = - TransformationAddConstantScalar(105, 14, {uint_for_float[1]}); - auto bad_add_float_30_id_already_used = - TransformationAddConstantScalar(104, 14, {uint_for_float[1]}); - auto bad_id_already_used = TransformationAddConstantScalar(1, 6, {1}); - auto bad_no_data = TransformationAddConstantScalar(100, 6, {}); - auto bad_too_much_data = TransformationAddConstantScalar(100, 6, {1, 2}); - auto bad_type_id_does_not_exist = - TransformationAddConstantScalar(108, 2020, {uint_for_float[0]}); - auto bad_type_id_is_not_a_type = TransformationAddConstantScalar(109, 9, {0}); - auto bad_type_id_is_void = TransformationAddConstantScalar(110, 2, {0}); - auto bad_type_id_is_pointer = TransformationAddConstantScalar(111, 11, {0}); - - // Id is already in use. + // Tests |fresh_id| being non-fresh. + auto transformation = TransformationAddConstantScalar(18, 2, {0}, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests undefined |type_id|. + transformation = TransformationAddConstantScalar(19, 20, {0}, false); ASSERT_FALSE( - bad_id_already_used.IsApplicable(context.get(), transformation_context)); + transformation.IsApplicable(context.get(), transformation_context)); - // At least one word of data must be provided. - ASSERT_FALSE(bad_no_data.IsApplicable(context.get(), transformation_context)); + // Tests |type_id| not representing a type instruction. + transformation = TransformationAddConstantScalar(19, 11, {0}, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - // Cannot give two data words for a 32-bit type. + // Tests |type_id| representing an OpTypePointer instruction. + transformation = TransformationAddConstantScalar(19, 8, {0}, false); ASSERT_FALSE( - bad_too_much_data.IsApplicable(context.get(), transformation_context)); + transformation.IsApplicable(context.get(), transformation_context)); - // Type id does not exist - ASSERT_FALSE(bad_type_id_does_not_exist.IsApplicable(context.get(), - transformation_context)); + // Tests |type_id| representing an OpTypeVoid instruction. + transformation = TransformationAddConstantScalar(19, 9, {0}, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - // Type id is not a type - ASSERT_FALSE(bad_type_id_is_not_a_type.IsApplicable(context.get(), - transformation_context)); + // Tests |words| having no words. + transformation = TransformationAddConstantScalar(19, 2, {}, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - // Type id is void + // Tests |words| having 2 words for a 32-bit type. + transformation = TransformationAddConstantScalar(19, 2, {0, 1}, false); ASSERT_FALSE( - bad_type_id_is_void.IsApplicable(context.get(), transformation_context)); + transformation.IsApplicable(context.get(), transformation_context)); - // Type id is pointer - ASSERT_FALSE(bad_type_id_is_pointer.IsApplicable(context.get(), - transformation_context)); + // Tests |words| having 3 words for a 64-bit type. + transformation = TransformationAddConstantScalar(19, 5, {0, 1, 2}, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddConstantScalarTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + OpCapability Int64 + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %17 "main" + +; Types + + ; 32-bit types + %2 = OpTypeInt 32 0 + %3 = OpTypeInt 32 1 + %4 = OpTypeFloat 32 + + ; 64-bit types + %5 = OpTypeInt 64 0 + %6 = OpTypeInt 64 1 + %7 = OpTypeFloat 64 + + %8 = OpTypePointer Private %2 + %9 = OpTypeVoid + %10 = OpTypeFunction %9 + +; Constants + + ; 32-bit constants + %11 = OpConstant %2 1 + %12 = OpConstant %3 2 + %13 = OpConstant %4 3 + + ; 64-bit constants + %14 = OpConstant %5 1 + %15 = OpConstant %6 2 + %16 = OpConstant %7 3 + +; main function + %17 = OpFunction %9 None %10 + %18 = OpLabel + OpReturn + OpFunctionEnd + )"; - ASSERT_TRUE( - add_signed_int_1.IsApplicable(context.get(), transformation_context)); - add_signed_int_1.Apply(context.get(), &transformation_context); + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Adds 32-bit unsigned integer (1 logical operand with 1 word). + auto transformation = TransformationAddConstantScalar(19, 2, {4}, false); + transformation.Apply(context.get(), &transformation_context); + auto* constant_instruction = context->get_def_use_mgr()->GetDef(19); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds 32-bit signed integer (1 logical operand with 1 word). + transformation = TransformationAddConstantScalar(20, 3, {5}, false); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(20); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds 32-bit float (1 logical operand with 1 word). + transformation = TransformationAddConstantScalar( + 21, 4, {0b01000000110000000000000000000000}, false); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(21); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds 64-bit unsigned integer (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar(22, 5, {7, 0}, false); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(22); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds 64-bit signed integer (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar(23, 6, {8, 0}, false); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(23); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds 64-bit float (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar( + 24, 7, {0, 0b01000000001000100000000000000000}, false); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(24); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); + ASSERT_TRUE(IsValid(env, context.get())); + + // Adds irrelevant 32-bit unsigned integer (1 logical operand with 1 word). + transformation = TransformationAddConstantScalar(25, 2, {10}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(25); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE( - add_signed_int_10.IsApplicable(context.get(), transformation_context)); - add_signed_int_10.Apply(context.get(), &transformation_context); + // Adds irrelevant 32-bit signed integer (1 logical operand with 1 word). + transformation = TransformationAddConstantScalar(26, 3, {11}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(26); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE( - add_unsigned_int_2.IsApplicable(context.get(), transformation_context)); - add_unsigned_int_2.Apply(context.get(), &transformation_context); + // Adds irrelevant 32-bit float (1 logical operand with 1 word). + transformation = TransformationAddConstantScalar( + 27, 4, {0b01000001010000000000000000000000}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(27); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 1); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE( - add_unsigned_int_20.IsApplicable(context.get(), transformation_context)); - add_unsigned_int_20.Apply(context.get(), &transformation_context); + // Adds irrelevant 64-bit unsigned integer (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar(28, 5, {13, 0}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(28); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE(add_float_3.IsApplicable(context.get(), transformation_context)); - add_float_3.Apply(context.get(), &transformation_context); + // Adds irrelevant 64-bit signed integer (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar(29, 6, {14, 0}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(29); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE(add_float_30.IsApplicable(context.get(), transformation_context)); - add_float_30.Apply(context.get(), &transformation_context); + // Adds irrelevant 64-bit float (1 logical operand with 2 words). + transformation = TransformationAddConstantScalar( + 30, 7, {0, 0b01000000001011100000000000000000}, true); + transformation.Apply(context.get(), &transformation_context); + constant_instruction = context->get_def_use_mgr()->GetDef(30); + EXPECT_EQ(constant_instruction->NumInOperands(), 1); + EXPECT_EQ(constant_instruction->NumInOperandWords(), 2); ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_FALSE(bad_add_float_30_id_already_used.IsApplicable( - context.get(), transformation_context)); + for (uint32_t result_id = 19; result_id <= 24; ++result_id) { + ASSERT_FALSE(fact_manager.IdIsIrrelevant(result_id)); + } + + for (uint32_t result_id = 25; result_id <= 30; ++result_id) { + ASSERT_TRUE(fact_manager.IdIsIrrelevant(result_id)); + } - std::string after_transformation = R"( + std::string variant_shader = R"( OpCapability Shader + OpCapability Int64 + OpCapability Float64 %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - OpName %8 "x" - OpName %12 "y" - OpName %16 "z" - OpDecorate %8 RelaxedPrecision - OpDecorate %12 RelaxedPrecision - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeInt 32 1 - %7 = OpTypePointer Function %6 - %9 = OpConstant %6 1 - %10 = OpTypeInt 32 0 - %11 = OpTypePointer Function %10 - %13 = OpConstant %10 2 - %14 = OpTypeFloat 32 - %15 = OpTypePointer Function %14 - %17 = OpConstant %14 3 - %100 = OpConstant %6 1 - %101 = OpConstant %6 10 - %102 = OpConstant %10 2 - %103 = OpConstant %10 20 - %104 = OpConstant %14 3 - %105 = OpConstant %14 30 - %4 = OpFunction %2 None %3 - %5 = OpLabel - %8 = OpVariable %7 Function - %12 = OpVariable %11 Function - %16 = OpVariable %15 Function - OpStore %8 %9 - OpStore %12 %13 - OpStore %16 %17 + OpEntryPoint Vertex %17 "main" + +; Types + + ; 32-bit types + %2 = OpTypeInt 32 0 + %3 = OpTypeInt 32 1 + %4 = OpTypeFloat 32 + + ; 64-bit types + %5 = OpTypeInt 64 0 + %6 = OpTypeInt 64 1 + %7 = OpTypeFloat 64 + + %8 = OpTypePointer Private %2 + %9 = OpTypeVoid + %10 = OpTypeFunction %9 + +; Constants + + ; 32-bit constants + %11 = OpConstant %2 1 + %12 = OpConstant %3 2 + %13 = OpConstant %4 3 + + ; 64-bit constants + %14 = OpConstant %5 1 + %15 = OpConstant %6 2 + %16 = OpConstant %7 3 + + ; added constants + %19 = OpConstant %2 4 + %20 = OpConstant %3 5 + %21 = OpConstant %4 6 + %22 = OpConstant %5 7 + %23 = OpConstant %6 8 + %24 = OpConstant %7 9 + %25 = OpConstant %2 10 + %26 = OpConstant %3 11 + %27 = OpConstant %4 12 + %28 = OpConstant %5 13 + %29 = OpConstant %6 14 + %30 = OpConstant %7 15 + +; main function + %17 = OpFunction %9 None %10 + %18 = OpLabel OpReturn OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } } // namespace diff --git a/test/fuzz/transformation_add_copy_memory_test.cpp b/test/fuzz/transformation_add_copy_memory_test.cpp index 66a15f47..5bb59a8d 100644 --- a/test/fuzz/transformation_add_copy_memory_test.cpp +++ b/test/fuzz/transformation_add_copy_memory_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_copy_memory.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -139,7 +140,7 @@ TEST(TransformationAddCopyMemoryTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp index c9be5209..db89a2b2 100644 --- a/test/fuzz/transformation_add_dead_block_test.cpp +++ b/test/fuzz/transformation_add_dead_block_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_dead_block.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -20,75 +21,124 @@ namespace fuzz { namespace { TEST(TransformationAddDeadBlockTest, BasicTest) { - std::string shader = R"( + std::string reference_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeBool - %7 = OpConstantTrue %6 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpBranch %8 + OpEntryPoint Fragment %6 "main" + OpExecutionMode %6 OriginUpperLeft + +; Types + %2 = OpTypeBool + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstantTrue %2 + +; main function + %6 = OpFunction %3 None %4 + %7 = OpLabel + OpSelectionMerge %11 None + OpBranchConditional %5 %8 %9 %8 = OpLabel + OpBranch %10 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpBranch %13 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Id 4 is already in use - ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true) - .IsApplicable(context.get(), transformation_context)); + auto transformation = TransformationAddDeadBlock(4, 11, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - // Id 7 is not a block - ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true) - .IsApplicable(context.get(), transformation_context)); + // Id 5 is not a block + transformation = TransformationAddDeadBlock(14, 5, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - TransformationAddDeadBlock transformation(100, 5, true); + // Tests existing block not dominating its successor block. + transformation = TransformationAddDeadBlock(14, 8, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationAddDeadBlock(14, 9, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests existing block being an unreachable block. + transformation = TransformationAddDeadBlock(14, 12, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests applicable case. + transformation = TransformationAddDeadBlock(14, 11, true); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); transformation.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); - ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100)); + ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(14)); - std::string after_transformation = R"( + std::string variant_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeBool - %7 = OpConstantTrue %6 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpSelectionMerge %8 None - OpBranchConditional %7 %8 %100 - %100 = OpLabel - OpBranch %8 + OpEntryPoint Fragment %6 "main" + OpExecutionMode %6 OriginUpperLeft + +; Types + %2 = OpTypeBool + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstantTrue %2 + +; main function + %6 = OpFunction %3 None %4 + %7 = OpLabel + OpSelectionMerge %11 None + OpBranchConditional %5 %8 %9 %8 = OpLabel + OpBranch %10 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %5 %13 %14 + %14 = OpLabel + OpBranch %13 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel OpReturn OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeSelectionMerge) { @@ -122,7 +172,7 @@ TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeSelectionMerge) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -136,27 +186,31 @@ TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeLoopMergeOrContinue) { OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeBool - %7 = OpConstantTrue %6 - %4 = OpFunction %2 None %3 - %5 = OpLabel + OpEntryPoint Fragment %6 "main" + OpExecutionMode %6 OriginUpperLeft + +; Types + %2 = OpTypeBool + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstantTrue %2 + +; main function + %6 = OpFunction %3 None %4 + %7 = OpLabel OpBranch %8 %8 = OpLabel - OpLoopMerge %11 %12 None - OpBranchConditional %7 %9 %10 + OpLoopMerge %12 %11 None + OpBranchConditional %5 %9 %10 %9 = OpLabel - OpBranch %12 - %10 = OpLabel OpBranch %11 - %12 = OpLabel - OpBranch %8 + %10 = OpLabel + OpBranch %12 %11 = OpLabel + OpBranch %8 + %12 = OpLabel OpReturn OpFunctionEnd )"; @@ -166,7 +220,7 @@ TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeLoopMergeOrContinue) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -212,7 +266,7 @@ TEST(TransformationAddDeadBlockTest, SourceBlockMustNotBeLoopHead) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -252,7 +306,7 @@ TEST(TransformationAddDeadBlockTest, OpPhiInTarget) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -325,7 +379,7 @@ TEST(TransformationAddDeadBlockTest, BackEdge) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_dead_break_test.cpp b/test/fuzz/transformation_add_dead_break_test.cpp index 19fac35d..c6f2775f 100644 --- a/test/fuzz/transformation_add_dead_break_test.cpp +++ b/test/fuzz/transformation_add_dead_break_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_dead_break.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -21,8 +22,7 @@ namespace { TEST(TransformationAddDeadBreakTest, BreaksOutOfSimpleIf) { // For a simple if-then-else, checks that some dead break scenarios are - // possible, and sanity-checks that some illegal scenarios are indeed not - // allowed. + // possible, and that some invalid scenarios are indeed not allowed. // The SPIR-V for this test is adapted from the following GLSL, by separating // some assignments into their own basic blocks, and adding constants for true @@ -99,7 +99,7 @@ TEST(TransformationAddDeadBreakTest, BreaksOutOfSimpleIf) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -341,7 +341,7 @@ TEST(TransformationAddDeadBreakTest, BreakOutOfNestedIfs) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -704,7 +704,7 @@ TEST(TransformationAddDeadBreakTest, BreakOutOfNestedSwitches) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1126,7 +1126,7 @@ TEST(TransformationAddDeadBreakTest, BreakOutOfLoopNest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1463,7 +1463,7 @@ TEST(TransformationAddDeadBreakTest, NoBreakFromContinueConstruct) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1526,7 +1526,7 @@ TEST(TransformationAddDeadBreakTest, BreakFromBackEdgeBlock) { BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1660,7 +1660,7 @@ TEST(TransformationAddDeadBreakTest, SelectionInContinueConstruct) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1878,7 +1878,7 @@ TEST(TransformationAddDeadBreakTest, LoopInContinueConstruct) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2099,7 +2099,7 @@ TEST(TransformationAddDeadBreakTest, PhiInstructions) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2290,7 +2290,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2347,7 +2347,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2398,7 +2398,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules3) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2490,7 +2490,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules4) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2576,7 +2576,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules5) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2637,7 +2637,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules6) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2700,7 +2700,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules7) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2750,7 +2750,7 @@ TEST(TransformationAddDeadBreakTest, RespectDominanceRules8) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2800,7 +2800,7 @@ TEST(TransformationAddDeadBreakTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_dead_continue_test.cpp b/test/fuzz/transformation_add_dead_continue_test.cpp index 07ee3b18..7a1c3c6c 100644 --- a/test/fuzz/transformation_add_dead_continue_test.cpp +++ b/test/fuzz/transformation_add_dead_continue_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_dead_continue.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -21,8 +22,8 @@ namespace { TEST(TransformationAddDeadContinueTest, SimpleExample) { // For a simple loop, checks that some dead continue scenarios are possible, - // sanity-checks that some illegal scenarios are indeed not allowed, and then - // applies a transformation. + // checks that some invalid scenarios are indeed not allowed, and then applies + // a transformation. // The SPIR-V for this test is adapted from the following GLSL, by separating // some assignments into their own basic blocks, and adding constants for true @@ -96,7 +97,7 @@ TEST(TransformationAddDeadContinueTest, SimpleExample) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -370,7 +371,7 @@ TEST(TransformationAddDeadContinueTest, LoopNest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -610,7 +611,7 @@ TEST(TransformationAddDeadConditionalTest, LoopInContinueConstruct) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -821,7 +822,7 @@ TEST(TransformationAddDeadContinueTest, PhiInstructions) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -996,7 +997,7 @@ TEST(TransformationAddDeadContinueTest, RespectDominanceRules1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1112,7 +1113,7 @@ TEST(TransformationAddDeadContinueTest, RespectDominanceRules2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1164,7 +1165,7 @@ TEST(TransformationAddDeadContinueTest, RespectDominanceRules3) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1307,7 +1308,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1377,7 +1378,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1439,7 +1440,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous3) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1503,7 +1504,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous4) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1559,7 +1560,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous5) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1608,7 +1609,7 @@ TEST(TransformationAddDeadContinueTest, Miscellaneous6) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_function_test.cpp b/test/fuzz/transformation_add_function_test.cpp index bbd915b0..6c4e22b3 100644 --- a/test/fuzz/transformation_add_function_test.cpp +++ b/test/fuzz/transformation_add_function_test.cpp @@ -144,7 +144,7 @@ TEST(TransformationAddFunctionTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -493,7 +493,7 @@ TEST(TransformationAddFunctionTest, InapplicableTransformations) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -631,18 +631,18 @@ TEST(TransformationAddFunctionTest, LoopLimiters) { instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); TransformationContext transformation_context2(&fact_manager2, validator_options); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -853,18 +853,18 @@ TEST(TransformationAddFunctionTest, KillAndUnreachableInVoidFunction) { instructions.push_back(MakeInstructionMessage(SpvOpKill, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); TransformationContext transformation_context2(&fact_manager2, validator_options); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -1008,18 +1008,18 @@ TEST(TransformationAddFunctionTest, KillAndUnreachableInNonVoidFunction) { instructions.push_back(MakeInstructionMessage(SpvOpKill, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); TransformationContext transformation_context2(&fact_manager2, validator_options); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -1295,18 +1295,18 @@ TEST(TransformationAddFunctionTest, ClampedAccessChains) { instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); TransformationContext transformation_context2(&fact_manager2, validator_options); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -1622,8 +1622,12 @@ TEST(TransformationAddFunctionTest, LivesafeCanCallLivesafe) { instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); @@ -1633,10 +1637,6 @@ TEST(TransformationAddFunctionTest, LivesafeCanCallLivesafe) { // Mark function 6 as livesafe. transformation_context2.GetFactManager()->AddFactFunctionIsLivesafe(6); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -1722,18 +1722,18 @@ TEST(TransformationAddFunctionTest, LivesafeOnlyCallsLivesafe) { instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {})); instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {})); - FactManager fact_manager1; - FactManager fact_manager2; + const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context1.get())); + + FactManager fact_manager1(context1.get()); + FactManager fact_manager2(context2.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context1(&fact_manager1, validator_options); TransformationContext transformation_context2(&fact_manager2, validator_options); - const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context1.get())); - TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); @@ -1853,15 +1853,14 @@ TEST(TransformationAddFunctionTest, )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2011,15 +2010,14 @@ TEST(TransformationAddFunctionTest, )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2167,15 +2165,14 @@ TEST(TransformationAddFunctionTest, LoopLimitersHeaderIsBackEdgeBlock) { )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2246,7 +2243,7 @@ TEST(TransformationAddFunctionTest, LoopLimitersHeaderIsBackEdgeBlock) { ASSERT_TRUE(IsEqual(env, expected, context.get())); } -TEST(TransformationAddFunctionTest, InfiniteLoop1) { +TEST(TransformationAddFunctionTest, InfiniteLoop) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -2315,15 +2312,14 @@ TEST(TransformationAddFunctionTest, InfiniteLoop1) { )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2337,54 +2333,11 @@ TEST(TransformationAddFunctionTest, InfiniteLoop1) { loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); - ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), - transformation_context)); - add_livesafe_function.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); - std::string expected = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %8 = OpTypeInt 32 1 - %9 = OpTypePointer Function %8 - %11 = OpConstant %8 0 - %18 = OpConstant %8 10 - %19 = OpTypeBool - %26 = OpConstantTrue %19 - %27 = OpConstantFalse %19 - %28 = OpTypeInt 32 0 - %29 = OpTypePointer Function %28 - %30 = OpConstant %28 0 - %31 = OpConstant %28 1 - %32 = OpConstant %28 5 - %22 = OpConstant %8 1 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpReturn - OpFunctionEnd - %6 = OpFunction %2 None %3 - %7 = OpLabel - %100 = OpVariable %29 Function %30 - %10 = OpVariable %9 Function - OpStore %10 %11 - OpBranch %12 - %12 = OpLabel - %102 = OpLoad %28 %100 - %103 = OpIAdd %28 %102 %31 - OpStore %100 %103 - %104 = OpUGreaterThanEqual %19 %102 %32 - OpLoopMerge %14 %12 None - OpBranchConditional %104 %14 %12 - %14 = OpLabel - OpReturn - OpFunctionEnd - )"; - ASSERT_TRUE(IsEqual(env, expected, context.get())); + + // To make sure the loop's merge block is reachable, it must be dominated by + // the loop header. + ASSERT_FALSE(add_livesafe_function.IsApplicable(context.get(), + transformation_context)); } TEST(TransformationAddFunctionTest, UnreachableContinueConstruct) { @@ -2473,15 +2426,14 @@ TEST(TransformationAddFunctionTest, UnreachableContinueConstruct) { const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2639,15 +2591,14 @@ TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi1) { const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %8 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2832,15 +2783,14 @@ TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi2) { const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %8 in // |donor|. std::vector<protobufs::Instruction> instructions = @@ -2983,15 +2933,14 @@ TEST(TransformationAddFunctionTest, StaticallyOutOfBoundsArrayAccess) { )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector<protobufs::Instruction> instructions = diff --git a/test/fuzz/transformation_add_global_undef_test.cpp b/test/fuzz/transformation_add_global_undef_test.cpp index 8c06db02..91e871dd 100644 --- a/test/fuzz/transformation_add_global_undef_test.cpp +++ b/test/fuzz/transformation_add_global_undef_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_global_undef.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -46,7 +47,7 @@ TEST(TransformationAddGlobalUndefTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_global_variable_test.cpp b/test/fuzz/transformation_add_global_variable_test.cpp index 5c74ca06..933789f6 100644 --- a/test/fuzz/transformation_add_global_variable_test.cpp +++ b/test/fuzz/transformation_add_global_variable_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_global_variable.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -59,7 +60,7 @@ TEST(TransformationAddGlobalVariableTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -248,7 +249,7 @@ TEST(TransformationAddGlobalVariableTest, TestEntryPointInterfaceEnlargement) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -345,7 +346,7 @@ TEST(TransformationAddGlobalVariableTest, TestAddingWorkgroupGlobals) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_image_sample_unused_components_test.cpp b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp index fc78f9f9..7c092281 100644 --- a/test/fuzz/transformation_add_image_sample_unused_components_test.cpp +++ b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_image_sample_unused_components.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -65,7 +66,7 @@ TEST(TransformationAddImageSampleUnusedComponentsTest, IsApplicable) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -193,7 +194,7 @@ TEST(TransformationAddImageSampleUnusedComponentsTest, Apply) { BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp index e989b33e..74e350bd 100644 --- a/test/fuzz/transformation_add_local_variable_test.cpp +++ b/test/fuzz/transformation_add_local_variable_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_local_variable.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -78,7 +79,7 @@ TEST(TransformationAddLocalVariableTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_loop_preheader_test.cpp b/test/fuzz/transformation_add_loop_preheader_test.cpp new file mode 100644 index 00000000..98870963 --- /dev/null +++ b/test/fuzz/transformation_add_loop_preheader_test.cpp @@ -0,0 +1,292 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_loop_preheader.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddLoopPreheaderTest, SimpleTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %10 None + OpBranchConditional %7 %8 %9 + %8 = OpLabel + OpBranch %10 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %11 None + OpBranch %11 + %11 = OpLabel + OpBranchConditional %7 %10 %12 + %12 = OpLabel + OpLoopMerge %14 %13 None + OpBranch %13 + %13 = OpLabel + OpBranchConditional %7 %14 %12 + %15 = OpLabel + OpLoopMerge %17 %16 None + OpBranch %16 + %16 = OpLabel + OpBranchConditional %7 %15 %17 + %17 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + // %9 is not a loop header + ASSERT_FALSE(TransformationAddLoopPreheader(9, 15, {}).IsApplicable( + context.get(), transformation_context)); + + // The id %12 is not fresh + ASSERT_FALSE(TransformationAddLoopPreheader(10, 12, {}) + .IsApplicable(context.get(), transformation_context)); + + // Loop header %15 is not reachable (the only predecessor is the back-edge + // block) + ASSERT_FALSE(TransformationAddLoopPreheader(15, 100, {}) + .IsApplicable(context.get(), transformation_context)); + + auto transformation1 = TransformationAddLoopPreheader(10, 20, {}); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + auto transformation2 = TransformationAddLoopPreheader(12, 21, {}); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %20 None + OpBranchConditional %7 %8 %9 + %8 = OpLabel + OpBranch %20 + %9 = OpLabel + OpBranch %20 + %20 = OpLabel + OpBranch %10 + %10 = OpLabel + OpLoopMerge %21 %11 None + OpBranch %11 + %11 = OpLabel + OpBranchConditional %7 %10 %21 + %21 = OpLabel + OpBranch %12 + %12 = OpLabel + OpLoopMerge %14 %13 None + OpBranch %13 + %13 = OpLabel + OpBranchConditional %7 %14 %12 + %15 = OpLabel + OpLoopMerge %17 %16 None + OpBranch %16 + %16 = OpLabel + OpBranchConditional %7 %15 %17 + %17 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopPreheaderTest, OpPhi) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpCopyObject %6 %7 + OpBranch %8 + %8 = OpLabel + %31 = OpPhi %6 %20 %5 %21 %9 + OpLoopMerge %10 %9 None + OpBranch %9 + %9 = OpLabel + %21 = OpCopyObject %6 %7 + OpBranchConditional %7 %8 %10 + %10 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %7 %11 %12 + %11 = OpLabel + %22 = OpCopyObject %6 %7 + OpBranch %13 + %12 = OpLabel + %23 = OpCopyObject %6 %7 + OpBranch %13 + %13 = OpLabel + %32 = OpPhi %6 %22 %11 %23 %12 %24 %14 + %33 = OpPhi %6 %7 %11 %7 %12 %24 %14 + OpLoopMerge %15 %14 None + OpBranch %14 + %14 = OpLabel + %24 = OpCopyObject %6 %7 + OpBranchConditional %7 %13 %15 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation1 = TransformationAddLoopPreheader(8, 40, {}); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + // Not enough ids for the OpPhi instructions are given + ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {}) + .IsApplicable(context.get(), transformation_context)); + + // Not enough ids for the OpPhi instructions are given + ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {42}) + .IsApplicable(context.get(), transformation_context)); + + // One of the ids is not fresh + ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {31, 42}) + .IsApplicable(context.get(), transformation_context)); + + // One of the ids is repeated + ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {41, 42}) + .IsApplicable(context.get(), transformation_context)); + + auto transformation2 = TransformationAddLoopPreheader(13, 41, {42, 43}); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantFalse %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpCopyObject %6 %7 + OpBranch %40 + %40 = OpLabel + OpBranch %8 + %8 = OpLabel + %31 = OpPhi %6 %20 %40 %21 %9 + OpLoopMerge %10 %9 None + OpBranch %9 + %9 = OpLabel + %21 = OpCopyObject %6 %7 + OpBranchConditional %7 %8 %10 + %10 = OpLabel + OpSelectionMerge %41 None + OpBranchConditional %7 %11 %12 + %11 = OpLabel + %22 = OpCopyObject %6 %7 + OpBranch %41 + %12 = OpLabel + %23 = OpCopyObject %6 %7 + OpBranch %41 + %41 = OpLabel + %42 = OpPhi %6 %22 %11 %23 %12 + %43 = OpPhi %6 %7 %11 %7 %12 + OpBranch %13 + %13 = OpLabel + %32 = OpPhi %6 %42 %41 %24 %14 + %33 = OpPhi %6 %43 %41 %24 %14 + OpLoopMerge %15 %14 None + OpBranch %14 + %14 = OpLabel + %24 = OpCopyObject %6 %7 + OpBranchConditional %7 %13 %15 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp new file mode 100644 index 00000000..7815b83e --- /dev/null +++ b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp @@ -0,0 +1,957 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + ConstantsNotSuitable) { + std::string shader = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %36 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 -1 + %7 = OpConstant %5 0 + %8 = OpConstant %5 1 + %9 = OpConstant %5 2 + %10 = OpConstant %5 5 + %11 = OpConstant %5 10 + %12 = OpConstant %5 20 + %13 = OpConstant %5 33 + %14 = OpTypeVector %5 2 + %15 = OpConstantComposite %14 %10 %11 + %16 = OpConstantComposite %14 %12 %12 + %17 = OpTypeVector %5 3 + %18 = OpConstantComposite %17 %11 %7 %11 + %19 = OpTypeInt 64 1 + %20 = OpConstant %19 0 + %21 = OpConstant %19 10 + %22 = OpTypeVector %19 2 + %23 = OpConstantComposite %22 %21 %20 + %24 = OpTypeFloat 32 + %25 = OpConstant %24 0 + %26 = OpConstant %24 5 + %27 = OpConstant %24 10 + %28 = OpConstant %24 20 + %29 = OpTypeVector %24 3 + %30 = OpConstantComposite %29 %26 %27 %26 + %31 = OpConstantComposite %29 %28 %28 %28 + %32 = OpConstantComposite %29 %27 %25 %27 + %2 = OpFunction %3 None %4 + %33 = OpLabel + %34 = OpCopyObject %5 %11 + OpBranch %35 + %35 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Reminder: the first four parameters of the constructor are the constants + // with values for C, I, S, N respectively. + + // %70 does not correspond to an id in the module. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 70, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %35 is not a constant. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 35, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %27, %28 and %26 are not integer constants, but scalar floats. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 27, 28, 26, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %32, %31 and %30 are not integer constants, but vector floats. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 32, 31, 30, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %18=(10, 0, 10) has 3 components, while %16=(20, 20) and %15=(5, 10) + // have 2. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 18, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %21 has bit width 64, while the width of %12 and %10 is 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 21, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %13 has component width 64, while the component width of %16 and %15 is 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 13, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %21 (N) is a 64-bit integer, not 32-bit. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 21, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %7 (N) has value 0, so N <= 0. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 7, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %6 (N) has value -1, so N <= 1. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 6, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // %13 (N) has value 33, so N > 32. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 7, 7, 7, 6, 13, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // C(%11)=10, I(%12)=20, S(%10)=5, N(%8)=1, so C=I-S*N does not hold, as + // 20-5*1=15. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 11, 12, 10, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // C(%15)=(5, 10), I(%16)=(20, 20), S(%15)=(5, 10), N(%8)=1, so C=I-S*N does + // not hold, as (20, 20)-1*(5, 10) = (15, 10). + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 15, 16, 15, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + MissingConstantsOrBoolType) { + { + // The shader is missing the boolean type. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %20 = OpConstant %5 0 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } + { + // The shader is missing a 32-bit integer 0 constant. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %20 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } + { + // The shader is missing a 32-bit integer 1 constant. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %20 = OpTypeBool + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 0 + %7 = OpConstant %5 2 + %8 = OpConstant %5 5 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + } +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Simple) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %22 = OpPhi %7 %12 %14 + OpSelectionMerge %16 None + OpBranchConditional %6 %17 %18 + %17 = OpLabel + %23 = OpPhi %7 %13 %15 + OpBranch %18 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpBranch %19 + %19 = OpLabel + OpLoopMerge %20 %19 None + OpBranchConditional %6 %20 %19 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %24 + %24 = OpLabel + OpLoopMerge %27 %25 None + OpBranch %25 + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpBranchConditional %6 %24 %27 + %27 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Block %14 has no predecessors. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 14, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %18 has more than one predecessor. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 18, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %16 is a merge block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 16, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %25 is a continue block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 25, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %19 has more than one predecessor. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 19, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Block %20 is a merge block. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 20, 100, 101, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %20 is supposed to be fresh, but it is not. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 20, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %100 is used twice. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 100, 102, 103, 104, 105, 106, 107) + .IsApplicable(context.get(), transformation_context)); + + // Id %100 is used twice. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 100) + .IsApplicable(context.get(), transformation_context)); + + // Only the last id (for the additional block) is optional, so the other ones + // cannot be 0. + ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 0, 101, 102, 103, 104, 105, 106, 100) + .IsApplicable(context.get(), transformation_context)); + + // This transformation will create a synonym of constant %12 from a 1-block + // loop. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // This transformation will create a synonym of constant %12 from a 2-block + // loop. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 17, 107, 108, 109, 110, 111, 112, 113, 114); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // This transformation will create a synonym of constant %12 from a 2-block + // loop. + auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 13, 11, 10, 26, 115, 116, 117, 118, 119, 120, 121, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(115, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %14 %105 %101 + %103 = OpPhi %7 %13 %14 %104 %101 + %104 = OpISub %7 %103 %11 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %15 %101 None + OpBranchConditional %106 %101 %15 + %15 = OpLabel + %100 = OpPhi %7 %104 %101 + %22 = OpPhi %7 %12 %101 + OpSelectionMerge %16 None + OpBranchConditional %6 %108 %18 + %108 = OpLabel + %109 = OpPhi %7 %8 %15 %112 %114 + %110 = OpPhi %7 %13 %15 %111 %114 + OpLoopMerge %17 %114 None + OpBranch %114 + %114 = OpLabel + %111 = OpISub %7 %110 %11 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpBranchConditional %113 %108 %17 + %17 = OpLabel + %107 = OpPhi %7 %111 %114 + %23 = OpPhi %7 %13 %114 + OpBranch %18 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpBranch %19 + %19 = OpLabel + OpLoopMerge %20 %19 None + OpBranchConditional %6 %20 %19 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %24 + %24 = OpLabel + OpLoopMerge %27 %25 None + OpBranch %25 + %25 = OpLabel + OpBranch %116 + %116 = OpLabel + %117 = OpPhi %7 %8 %25 %120 %116 + %118 = OpPhi %7 %13 %25 %119 %116 + %119 = OpISub %7 %118 %11 + %120 = OpIAdd %7 %117 %9 + %121 = OpSLessThan %5 %120 %10 + OpLoopMerge %26 %116 None + OpBranchConditional %121 %116 %26 + %26 = OpLabel + %115 = OpPhi %7 %119 %116 + OpBranchConditional %6 %24 %27 + %27 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, + DifferentSignednessAndVectors) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %14 5 + %17 = OpConstant %14 10 + %18 = OpConstant %14 20 + %19 = OpTypeVector %7 2 + %20 = OpTypeVector %14 2 + %21 = OpConstantComposite %19 %12 %8 + %22 = OpConstantComposite %20 %17 %15 + %23 = OpConstantComposite %19 %13 %12 + %24 = OpConstantComposite %19 %11 %11 + %2 = OpFunction %3 None %4 + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpBranch %27 + %27 = OpLabel + OpBranch %28 + %28 = OpLabel + OpBranch %29 + %29 = OpLabel + OpBranch %30 + %30 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that the transformation is applicable and is applied + // correctly with integers, scalar and vectors, of different signedness. + + // %12 is a signed integer, %18 and %16 are unsigned integers. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 18, 16, 10, 26, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %12 and %11 are signed integers, %18 is an unsigned integer. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 18, 11, 10, 27, 108, 109, 110, 111, 112, 113, 114, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(108, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %17, %18 and %16 are all signed integers. + auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym( + 17, 18, 16, 10, 28, 115, 116, 117, 118, 119, 120, 121, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(17, {}), MakeDataDescriptor(115, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %22 is an unsigned integer vector, %23 and %24 are signed integer vectors. + auto transformation4 = TransformationAddLoopToCreateIntConstantSynonym( + 22, 23, 24, 10, 29, 122, 123, 124, 125, 126, 127, 128, 0); + ASSERT_TRUE( + transformation4.IsApplicable(context.get(), transformation_context)); + transformation4.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(22, {}), MakeDataDescriptor(122, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // %21, %23 and %24 are all signed integer vectors. + auto transformation5 = TransformationAddLoopToCreateIntConstantSynonym( + 21, 23, 24, 10, 30, 129, 130, 131, 132, 133, 134, 135, 0); + ASSERT_TRUE( + transformation5.IsApplicable(context.get(), transformation_context)); + transformation5.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(21, {}), MakeDataDescriptor(129, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 5 + %12 = OpConstant %7 10 + %13 = OpConstant %7 20 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %14 5 + %17 = OpConstant %14 10 + %18 = OpConstant %14 20 + %19 = OpTypeVector %7 2 + %20 = OpTypeVector %14 2 + %21 = OpConstantComposite %19 %12 %8 + %22 = OpConstantComposite %20 %17 %15 + %23 = OpConstantComposite %19 %13 %12 + %24 = OpConstantComposite %19 %11 %11 + %2 = OpFunction %3 None %4 + %25 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %25 %105 %101 + %103 = OpPhi %14 %18 %25 %104 %101 + %104 = OpISub %14 %103 %16 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %26 %101 None + OpBranchConditional %106 %101 %26 + %26 = OpLabel + %100 = OpPhi %14 %104 %101 + OpBranch %109 + %109 = OpLabel + %110 = OpPhi %7 %8 %26 %113 %109 + %111 = OpPhi %14 %18 %26 %112 %109 + %112 = OpISub %14 %111 %11 + %113 = OpIAdd %7 %110 %9 + %114 = OpSLessThan %5 %113 %10 + OpLoopMerge %27 %109 None + OpBranchConditional %114 %109 %27 + %27 = OpLabel + %108 = OpPhi %14 %112 %109 + OpBranch %116 + %116 = OpLabel + %117 = OpPhi %7 %8 %27 %120 %116 + %118 = OpPhi %14 %18 %27 %119 %116 + %119 = OpISub %14 %118 %16 + %120 = OpIAdd %7 %117 %9 + %121 = OpSLessThan %5 %120 %10 + OpLoopMerge %28 %116 None + OpBranchConditional %121 %116 %28 + %28 = OpLabel + %115 = OpPhi %14 %119 %116 + OpBranch %123 + %123 = OpLabel + %124 = OpPhi %7 %8 %28 %127 %123 + %125 = OpPhi %19 %23 %28 %126 %123 + %126 = OpISub %19 %125 %24 + %127 = OpIAdd %7 %124 %9 + %128 = OpSLessThan %5 %127 %10 + OpLoopMerge %29 %123 None + OpBranchConditional %128 %123 %29 + %29 = OpLabel + %122 = OpPhi %19 %126 %123 + OpBranch %130 + %130 = OpLabel + %131 = OpPhi %7 %8 %29 %134 %130 + %132 = OpPhi %19 %23 %29 %133 %130 + %133 = OpISub %19 %132 %24 + %134 = OpIAdd %7 %131 %9 + %135 = OpSLessThan %5 %134 %10 + OpLoopMerge %30 %130 None + OpBranchConditional %135 %130 %30 + %30 = OpLabel + %129 = OpPhi %19 %133 %130 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, 64BitConstants) { + std::string shader = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpTypeInt 64 1 + %12 = OpConstant %11 5 + %13 = OpConstant %11 10 + %14 = OpConstant %11 20 + %15 = OpTypeVector %11 2 + %16 = OpConstantComposite %15 %13 %13 + %17 = OpConstantComposite %15 %14 %14 + %18 = OpConstantComposite %15 %12 %12 + %2 = OpFunction %3 None %4 + %19 = OpLabel + OpBranch %20 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that the transformation can be applied, and is applied + // correctly, to 64-bit integer (scalar and vector) constants. + + // 64-bit scalar integers. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 13, 14, 12, 10, 20, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(13, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // 64-bit vector integers. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 16, 17, 18, 10, 21, 107, 108, 109, 110, 111, 112, 113, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(16, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpTypeInt 64 1 + %12 = OpConstant %11 5 + %13 = OpConstant %11 10 + %14 = OpConstant %11 20 + %15 = OpTypeVector %11 2 + %16 = OpConstantComposite %15 %13 %13 + %17 = OpConstantComposite %15 %14 %14 + %18 = OpConstantComposite %15 %12 %12 + %2 = OpFunction %3 None %4 + %19 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %19 %105 %101 + %103 = OpPhi %11 %14 %19 %104 %101 + %104 = OpISub %11 %103 %12 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %10 + OpLoopMerge %20 %101 None + OpBranchConditional %106 %101 %20 + %20 = OpLabel + %100 = OpPhi %11 %104 %101 + OpBranch %108 + %108 = OpLabel + %109 = OpPhi %7 %8 %20 %112 %108 + %110 = OpPhi %15 %17 %20 %111 %108 + %111 = OpISub %15 %110 %18 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpLoopMerge %21 %108 None + OpBranchConditional %113 %108 %21 + %21 = OpLabel + %107 = OpPhi %15 %111 %108 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Underflow) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 20 + %12 = OpConstant %7 -4 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 214748365 + %15 = OpConstant %13 4294967256 + %2 = OpFunction %3 None %4 + %16 = OpLabel + OpBranch %17 + %17 = OpLabel + OpBranch %18 + %18 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // These tests check that underflows are taken into consideration when + // deciding if transformation is applicable. + + // Subtracting 2147483648 20 times from 32-bit integer 0 underflows 2 times + // and the result is equivalent to -4. + auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym( + 12, 8, 14, 11, 17, 100, 101, 102, 103, 104, 105, 106, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + // Subtracting 20 twice from 0 underflows and gives the unsigned integer + // 4294967256. + auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym( + 15, 8, 11, 10, 18, 107, 108, 109, 110, 111, 112, 113, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(15, {}), MakeDataDescriptor(107, {}))); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpConstant %7 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %7 20 + %12 = OpConstant %7 -4 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 214748365 + %15 = OpConstant %13 4294967256 + %2 = OpFunction %3 None %4 + %16 = OpLabel + OpBranch %101 + %101 = OpLabel + %102 = OpPhi %7 %8 %16 %105 %101 + %103 = OpPhi %7 %8 %16 %104 %101 + %104 = OpISub %7 %103 %14 + %105 = OpIAdd %7 %102 %9 + %106 = OpSLessThan %5 %105 %11 + OpLoopMerge %17 %101 None + OpBranchConditional %106 %101 %17 + %17 = OpLabel + %100 = OpPhi %7 %104 %101 + OpBranch %108 + %108 = OpLabel + %109 = OpPhi %7 %8 %17 %112 %108 + %110 = OpPhi %7 %8 %17 %111 %108 + %111 = OpISub %7 %110 %11 + %112 = OpIAdd %7 %109 %9 + %113 = OpSLessThan %5 %112 %10 + OpLoopMerge %18 %108 None + OpBranchConditional %113 %108 %18 + %18 = OpLabel + %107 = OpPhi %7 %111 %108 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp index 46841a52..bec15bae 100644 --- a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp +++ b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_no_contraction_decoration.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -93,7 +94,7 @@ TEST(TransformationAddNoContractionDecorationTest, BasicScenarios) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_opphi_synonym_test.cpp b/test/fuzz/transformation_add_opphi_synonym_test.cpp new file mode 100644 index 00000000..19a9081d --- /dev/null +++ b/test/fuzz/transformation_add_opphi_synonym_test.cpp @@ -0,0 +1,423 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_opphi_synonym.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) { + protobufs::FactDataSynonym data_synonym_fact; + *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {}); + *data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {}); + protobufs::Fact result; + *result.mutable_data_synonym_fact() = data_synonym_fact; + return result; +} + +// Adds synonym facts to the fact manager. +void SetUpIdSynonyms(FactManager* fact_manager) { + fact_manager->AddFact(MakeSynonymFact(11, 9)); + fact_manager->AddFact(MakeSynonymFact(13, 9)); + fact_manager->AddFact(MakeSynonymFact(14, 9)); + fact_manager->AddFact(MakeSynonymFact(19, 9)); + fact_manager->AddFact(MakeSynonymFact(20, 9)); + fact_manager->AddFact(MakeSynonymFact(10, 21)); +} + +TEST(TransformationAddOpPhiSynonymTest, Inapplicable) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %22 = OpTypePointer Function %7 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %8 1 + %2 = OpFunction %3 None %4 + %12 = OpLabel + %23 = OpVariable %22 Function + %13 = OpCopyObject %7 %9 + %14 = OpCopyObject %8 %11 + OpBranch %15 + %15 = OpLabel + OpSelectionMerge %16 None + OpBranchConditional %6 %17 %18 + %17 = OpLabel + %19 = OpCopyObject %7 %13 + %20 = OpCopyObject %8 %14 + %21 = OpCopyObject %7 %10 + OpBranch %16 + %18 = OpLabel + %24 = OpCopyObject %22 %23 + OpBranch %16 + %16 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + SetUpIdSynonyms(&fact_manager); + fact_manager.AddFact(MakeSynonymFact(23, 24)); + + // %13 is not a block label. + ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // Block %12 does not have a predecessor. + ASSERT_FALSE(TransformationAddOpPhiSynonym(12, {{}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // Not all predecessors of %16 (%17 and %18) are considered in the map. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %30 does not exist in the module. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{30, 19}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %20 is not a block label. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{20, 19}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %15 is not the id of one of the predecessors of the block. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{15, 19}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %30 does not exist in the module. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 30}, {18, 13}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %19 and %10 are not synonymous. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 10}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %19 and %14 do not have the same type. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 14}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %19 is not available at the end of %18. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 19}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // %21 is not a fresh id. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 9}}}, 21) + .IsApplicable(context.get(), transformation_context)); + + // %23 and %24 have pointer id. + ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 23}, {18, 24}}}, 100) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddOpPhiSynonymTest, Apply) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %8 1 + %2 = OpFunction %3 None %4 + %12 = OpLabel + %13 = OpCopyObject %7 %9 + %14 = OpCopyObject %8 %11 + OpBranch %15 + %15 = OpLabel + OpSelectionMerge %16 None + OpBranchConditional %6 %17 %18 + %17 = OpLabel + %19 = OpCopyObject %7 %13 + %20 = OpCopyObject %8 %14 + %21 = OpCopyObject %7 %10 + OpBranch %16 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpBranch %22 + %22 = OpLabel + OpLoopMerge %23 %24 None + OpBranchConditional %6 %25 %23 + %25 = OpLabel + OpSelectionMerge %26 None + OpBranchConditional %6 %27 %26 + %27 = OpLabel + %28 = OpCopyObject %7 %13 + OpBranch %23 + %26 = OpLabel + OpSelectionMerge %29 None + OpBranchConditional %6 %29 %24 + %29 = OpLabel + %30 = OpCopyObject %7 %13 + OpBranch %23 + %24 = OpLabel + OpBranch %22 + %23 = OpLabel + OpSelectionMerge %31 None + OpBranchConditional %6 %31 %31 + %31 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + SetUpIdSynonyms(&fact_manager); + + // Add some further synonym facts. + fact_manager.AddFact(MakeSynonymFact(28, 9)); + fact_manager.AddFact(MakeSynonymFact(30, 9)); + + auto transformation1 = TransformationAddOpPhiSynonym(17, {{{15, 13}}}, 100); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}), + MakeDataDescriptor(9, {}))); + + auto transformation2 = + TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 13}}}, 101); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(101, {}), + MakeDataDescriptor(9, {}))); + + auto transformation3 = + TransformationAddOpPhiSynonym(23, {{{22, 13}, {27, 28}, {29, 30}}}, 102); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(102, {}), + MakeDataDescriptor(9, {}))); + + auto transformation4 = TransformationAddOpPhiSynonym(31, {{{23, 13}}}, 103); + ASSERT_TRUE( + transformation4.IsApplicable(context.get(), transformation_context)); + transformation4.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(103, {}), + MakeDataDescriptor(9, {}))); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %9 = OpConstant %7 1 + %10 = OpConstant %7 2 + %11 = OpConstant %8 1 + %2 = OpFunction %3 None %4 + %12 = OpLabel + %13 = OpCopyObject %7 %9 + %14 = OpCopyObject %8 %11 + OpBranch %15 + %15 = OpLabel + OpSelectionMerge %16 None + OpBranchConditional %6 %17 %18 + %17 = OpLabel + %100 = OpPhi %7 %13 %15 + %19 = OpCopyObject %7 %13 + %20 = OpCopyObject %8 %14 + %21 = OpCopyObject %7 %10 + OpBranch %16 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + %101 = OpPhi %7 %19 %17 %13 %18 + OpBranch %22 + %22 = OpLabel + OpLoopMerge %23 %24 None + OpBranchConditional %6 %25 %23 + %25 = OpLabel + OpSelectionMerge %26 None + OpBranchConditional %6 %27 %26 + %27 = OpLabel + %28 = OpCopyObject %7 %13 + OpBranch %23 + %26 = OpLabel + OpSelectionMerge %29 None + OpBranchConditional %6 %29 %24 + %29 = OpLabel + %30 = OpCopyObject %7 %13 + OpBranch %23 + %24 = OpLabel + OpBranch %22 + %23 = OpLabel + %102 = OpPhi %7 %13 %22 %28 %27 %30 %29 + OpSelectionMerge %31 None + OpBranchConditional %6 %31 %31 + %31 = OpLabel + %103 = OpPhi %7 %13 %23 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationAddOpPhiSynonymTest, VariablePointers) { + std::string shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpTypePointer Workgroup %8 + %3 = OpVariable %10 Workgroup + %2 = OpFunction %4 None %5 + %11 = OpLabel + %12 = OpVariable %9 Function + OpSelectionMerge %13 None + OpBranchConditional %7 %14 %13 + %14 = OpLabel + %15 = OpCopyObject %10 %3 + %16 = OpCopyObject %9 %12 + OpBranch %13 + %13 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Declare synonyms + fact_manager.AddFact(MakeSynonymFact(3, 15)); + fact_manager.AddFact(MakeSynonymFact(12, 16)); + + // Remove the VariablePointers capability. + context.get()->get_feature_mgr()->RemoveCapability( + SpvCapabilityVariablePointers); + + // The VariablePointers capability is required to add an OpPhi instruction of + // pointer type. + ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + // Add the VariablePointers capability back. + context.get()->get_feature_mgr()->AddCapability( + SpvCapabilityVariablePointers); + + // If the ids have pointer type, the storage class must be Workgroup or + // StorageBuffer, but it is Function in this case. + ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 12}, {14, 16}}}, 100) + .IsApplicable(context.get(), transformation_context)); + + auto transformation = + TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + std::string after_transformation = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpTypePointer Workgroup %8 + %3 = OpVariable %10 Workgroup + %2 = OpFunction %4 None %5 + %11 = OpLabel + %12 = OpVariable %9 Function + OpSelectionMerge %13 None + OpBranchConditional %7 %14 %13 + %14 = OpLabel + %15 = OpCopyObject %10 %3 + %16 = OpCopyObject %9 %12 + OpBranch %13 + %13 = OpLabel + %100 = OpPhi %10 %3 %11 %15 %14 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp index 62e7dc1b..a89f956d 100644 --- a/test/fuzz/transformation_add_parameter_test.cpp +++ b/test/fuzz/transformation_add_parameter_test.cpp @@ -20,7 +20,7 @@ namespace spvtools { namespace fuzz { namespace { -TEST(TransformationAddParameterTest, BasicTest) { +TEST(TransformationAddParameterTest, NonPointerBasicTest) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" @@ -32,20 +32,69 @@ TEST(TransformationAddParameterTest, BasicTest) { %2 = OpTypeVoid %7 = OpTypeBool %11 = OpTypeInt 32 1 + %16 = OpTypeFloat 32 %3 = OpTypeFunction %2 %6 = OpTypeFunction %7 %7 %8 = OpConstant %11 23 %12 = OpConstantTrue %7 + %15 = OpTypeFunction %2 %16 + %24 = OpTypeFunction %2 %16 %7 + %31 = OpTypeStruct %7 %11 + %32 = OpConstant %16 23 + %33 = OpConstantComposite %31 %12 %8 + %41 = OpTypeStruct %11 %16 + %42 = OpConstantComposite %41 %8 %32 + %43 = OpTypeFunction %2 %41 + %44 = OpTypeFunction %2 %41 %7 %4 = OpFunction %2 None %3 %5 = OpLabel %13 = OpFunctionCall %7 %9 %12 OpReturn OpFunctionEnd + + ; adjust type of the function in-place %9 = OpFunction %7 None %6 %14 = OpFunctionParameter %7 %10 = OpLabel OpReturnValue %12 OpFunctionEnd + + ; reuse an existing function type + %17 = OpFunction %2 None %15 + %18 = OpFunctionParameter %16 + %19 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %2 None %15 + %21 = OpFunctionParameter %16 + %22 = OpLabel + OpReturn + OpFunctionEnd + %25 = OpFunction %2 None %24 + %26 = OpFunctionParameter %16 + %27 = OpFunctionParameter %7 + %28 = OpLabel + OpReturn + OpFunctionEnd + + ; create a new function type + %29 = OpFunction %2 None %3 + %30 = OpLabel + OpReturn + OpFunctionEnd + + ; don't adjust the type of the function if it creates a duplicate + %34 = OpFunction %2 None %43 + %35 = OpFunctionParameter %41 + %36 = OpLabel + OpReturn + OpFunctionEnd + %37 = OpFunction %2 None %44 + %38 = OpFunctionParameter %41 + %39 = OpFunctionParameter %7 + %40 = OpLabel + OpReturn + OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; @@ -53,42 +102,64 @@ TEST(TransformationAddParameterTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Can't modify entry point function. - ASSERT_FALSE(TransformationAddParameter(4, 15, 12, 16) + ASSERT_FALSE(TransformationAddParameter(4, 60, 7, {{}}, 61) .IsApplicable(context.get(), transformation_context)); - // There is no function with result id 29. - ASSERT_FALSE(TransformationAddParameter(29, 15, 8, 16) + // There is no function with result id 60. + ASSERT_FALSE(TransformationAddParameter(60, 60, 11, {{}}, 61) .IsApplicable(context.get(), transformation_context)); // Parameter id is not fresh. - ASSERT_FALSE(TransformationAddParameter(9, 14, 8, 16) + ASSERT_FALSE(TransformationAddParameter(9, 14, 11, {{{13, 8}}}, 61) .IsApplicable(context.get(), transformation_context)); // Function type id is not fresh. - ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 14) + ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 14) .IsApplicable(context.get(), transformation_context)); // Function type id and parameter type id are equal. - ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 15) + ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 60) .IsApplicable(context.get(), transformation_context)); // Parameter's initializer doesn't exist. - ASSERT_FALSE(TransformationAddParameter(9, 15, 15, 16) + ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 60}}}, 61) .IsApplicable(context.get(), transformation_context)); - // Correct transformation. - TransformationAddParameter correct(9, 15, 8, 16); - ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); - correct.Apply(context.get(), &transformation_context); - - // The module remains valid. - ASSERT_TRUE(IsValid(env, context.get())); + // Correct transformations. + { + TransformationAddParameter correct(9, 60, 11, {{{13, 8}}}, 61); + ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); + correct.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(60)); + } + { + TransformationAddParameter correct(17, 62, 7, {{}}, 63); + ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); + correct.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(62)); + } + { + TransformationAddParameter correct(29, 64, 31, {{}}, 65); + ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); + correct.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(64)); + } + { + TransformationAddParameter correct(34, 66, 7, {{}}, 67); + ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context)); + correct.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IdIsIrrelevant(66)); + } std::string expected_shader = R"( OpCapability Shader @@ -101,26 +172,910 @@ TEST(TransformationAddParameterTest, BasicTest) { %2 = OpTypeVoid %7 = OpTypeBool %11 = OpTypeInt 32 1 + %16 = OpTypeFloat 32 %3 = OpTypeFunction %2 %8 = OpConstant %11 23 %12 = OpConstantTrue %7 + %15 = OpTypeFunction %2 %16 + %24 = OpTypeFunction %2 %16 %7 + %31 = OpTypeStruct %7 %11 + %32 = OpConstant %16 23 + %33 = OpConstantComposite %31 %12 %8 + %41 = OpTypeStruct %11 %16 + %42 = OpConstantComposite %41 %8 %32 + %44 = OpTypeFunction %2 %41 %7 %6 = OpTypeFunction %7 %7 %11 + %65 = OpTypeFunction %2 %31 %4 = OpFunction %2 None %3 %5 = OpLabel %13 = OpFunctionCall %7 %9 %12 %8 OpReturn OpFunctionEnd + + ; adjust type of the function in-place %9 = OpFunction %7 None %6 %14 = OpFunctionParameter %7 - %15 = OpFunctionParameter %11 + %60 = OpFunctionParameter %11 %10 = OpLabel OpReturnValue %12 OpFunctionEnd + + ; reuse an existing function type + %17 = OpFunction %2 None %24 + %18 = OpFunctionParameter %16 + %62 = OpFunctionParameter %7 + %19 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %2 None %15 + %21 = OpFunctionParameter %16 + %22 = OpLabel + OpReturn + OpFunctionEnd + %25 = OpFunction %2 None %24 + %26 = OpFunctionParameter %16 + %27 = OpFunctionParameter %7 + %28 = OpLabel + OpReturn + OpFunctionEnd + + ; create a new function type + %29 = OpFunction %2 None %65 + %64 = OpFunctionParameter %31 + %30 = OpLabel + OpReturn + OpFunctionEnd + + ; don't adjust the type of the function if it creates a duplicate + %34 = OpFunction %2 None %44 + %35 = OpFunctionParameter %41 + %66 = OpFunctionParameter %7 + %36 = OpLabel + OpReturn + OpFunctionEnd + %37 = OpFunction %2 None %44 + %38 = OpFunctionParameter %41 + %39 = OpFunctionParameter %7 + %40 = OpLabel + OpReturn + OpFunctionEnd )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationAddParameterTest, NonPointerNotApplicableTest) { + // This types handles case of adding a new parameter of a non-pointer type + // where the transformation is not applicable. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun1(" + OpName %12 "fun2(i1;" + OpName %11 "a" + OpName %14 "fun3(" + OpName %24 "f1" + OpName %27 "f2" + OpName %30 "i1" + OpName %31 "i2" + OpName %32 "param" + OpName %35 "i3" + OpName %36 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpTypeFunction %8 %9 + %18 = OpConstant %8 2 + %22 = OpTypeFloat 32 + %23 = OpTypePointer Private %22 + %24 = OpVariable %23 Private + %25 = OpConstant %22 1 + %26 = OpTypePointer Function %22 + %28 = OpConstant %22 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %27 = OpVariable %26 Function + %30 = OpVariable %9 Function + %31 = OpVariable %9 Function + %32 = OpVariable %9 Function + %35 = OpVariable %9 Function + %36 = OpVariable %9 Function + OpStore %24 %25 + OpStore %27 %28 + %29 = OpFunctionCall %2 %6 + OpStore %30 %18 + %33 = OpLoad %8 %30 + OpStore %32 %33 + %34 = OpFunctionCall %8 %12 %32 + OpStore %31 %34 + %37 = OpLoad %8 %31 + OpStore %36 %37 + %38 = OpFunctionCall %8 %12 %36 + OpStore %35 %38 + ; %39 = OpFunctionCall %2 %14 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %8 None %10 + %11 = OpFunctionParameter %9 + %13 = OpLabel + %17 = OpLoad %8 %11 + %19 = OpIAdd %8 %17 %18 + OpReturnValue %19 + OpFunctionEnd + %14 = OpFunction %2 None %3 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: Id 19 is not available in the caller that has id 34. + TransformationAddParameter transformation_bad_1(12, 50, 8, + {{{34, 19}, {38, 19}}}, 51); + + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: Id 8 does not have a type. + TransformationAddParameter transformation_bad_2(12, 50, 8, + {{{34, 8}, {38, 8}}}, 51); + + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + // Bad: Types of id 25 and id 18 are different. + TransformationAddParameter transformation_bad_3(12, 50, 22, + {{{34, 25}, {38, 18}}}, 51); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Function with id 14 does not have any callers. + // Bad: Id 18 is not a vaild type. + TransformationAddParameter transformation_bad_4(14, 50, 18, {{}}, 51); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); + + // Function with id 14 does not have any callers. + // Bad: Id 3 refers to OpTypeVoid, which is not supported. + TransformationAddParameter transformation_bad_6(14, 50, 3, {{}}, 51); + ASSERT_FALSE( + transformation_bad_6.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddParameterTest, PointerFunctionTest) { + // This types handles case of adding a new parameter of a pointer type with + // storage class Function. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun1(" + OpName %12 "fun2(i1;" + OpName %11 "a" + OpName %14 "fun3(" + OpName %17 "s" + OpName %24 "s" + OpName %28 "f1" + OpName %31 "f2" + OpName %34 "i1" + OpName %35 "i2" + OpName %36 "param" + OpName %39 "i3" + OpName %40 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpTypeFunction %8 %9 + %20 = OpConstant %8 2 + %25 = OpConstant %8 0 + %26 = OpTypeFloat 32 + %27 = OpTypePointer Private %26 + %28 = OpVariable %27 Private + %60 = OpTypePointer Output %26 + %61 = OpVariable %60 Output + %29 = OpConstant %26 1 + %30 = OpTypePointer Function %26 + %32 = OpConstant %26 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %31 = OpVariable %30 Function + %34 = OpVariable %9 Function + %35 = OpVariable %9 Function + %36 = OpVariable %9 Function + %39 = OpVariable %9 Function + %40 = OpVariable %9 Function + OpStore %28 %29 + OpStore %31 %32 + %33 = OpFunctionCall %2 %6 + OpStore %34 %20 + %37 = OpLoad %8 %34 + OpStore %36 %37 + %38 = OpFunctionCall %8 %12 %36 + OpStore %35 %38 + %41 = OpLoad %8 %35 + OpStore %40 %41 + %42 = OpFunctionCall %8 %12 %40 + OpStore %39 %42 + %43 = OpFunctionCall %2 %14 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %8 None %10 + %11 = OpFunctionParameter %9 + %13 = OpLabel + %17 = OpVariable %9 Function + %18 = OpLoad %8 %11 + OpStore %17 %18 + %19 = OpLoad %8 %17 + %21 = OpIAdd %8 %19 %20 + OpReturnValue %21 + OpFunctionEnd + %14 = OpFunction %2 None %3 + %15 = OpLabel + %24 = OpVariable %9 Function + OpStore %24 %25 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: Pointer of id 61 has storage class Output, which is not supported. + TransformationAddParameter transformation_bad_1(12, 50, 60, + {{{38, 61}, {42, 61}}}, 51); + + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Good: Local variable of id 31 is defined in the caller (main). + TransformationAddParameter transformation_good_1(12, 50, 30, + {{{38, 31}, {42, 31}}}, 51); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Local variable of id 34 is defined in the caller (main). + TransformationAddParameter transformation_good_2(14, 52, 9, {{{43, 34}}}, 53); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Local variable of id 39 is defined in the caller (main). + TransformationAddParameter transformation_good_3(6, 54, 9, {{{33, 39}}}, 55); + ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(), + transformation_context)); + transformation_good_3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: This adds another pointer parameter to the function of id 6. + TransformationAddParameter transformation_good_4(6, 56, 30, {{{33, 31}}}, 57); + ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(), + transformation_context)); + transformation_good_4.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun1(" + OpName %12 "fun2(i1;" + OpName %11 "a" + OpName %14 "fun3(" + OpName %17 "s" + OpName %24 "s" + OpName %28 "f1" + OpName %31 "f2" + OpName %34 "i1" + OpName %35 "i2" + OpName %36 "param" + OpName %39 "i3" + OpName %40 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %20 = OpConstant %8 2 + %25 = OpConstant %8 0 + %26 = OpTypeFloat 32 + %27 = OpTypePointer Private %26 + %28 = OpVariable %27 Private + %60 = OpTypePointer Output %26 + %61 = OpVariable %60 Output + %29 = OpConstant %26 1 + %30 = OpTypePointer Function %26 + %32 = OpConstant %26 2 + %10 = OpTypeFunction %8 %9 %30 + %53 = OpTypeFunction %2 %9 + %57 = OpTypeFunction %2 %9 %30 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %31 = OpVariable %30 Function + %34 = OpVariable %9 Function + %35 = OpVariable %9 Function + %36 = OpVariable %9 Function + %39 = OpVariable %9 Function + %40 = OpVariable %9 Function + OpStore %28 %29 + OpStore %31 %32 + %33 = OpFunctionCall %2 %6 %39 %31 + OpStore %34 %20 + %37 = OpLoad %8 %34 + OpStore %36 %37 + %38 = OpFunctionCall %8 %12 %36 %31 + OpStore %35 %38 + %41 = OpLoad %8 %35 + OpStore %40 %41 + %42 = OpFunctionCall %8 %12 %40 %31 + OpStore %39 %42 + %43 = OpFunctionCall %2 %14 %34 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %57 + %54 = OpFunctionParameter %9 + %56 = OpFunctionParameter %30 + %7 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %8 None %10 + %11 = OpFunctionParameter %9 + %50 = OpFunctionParameter %30 + %13 = OpLabel + %17 = OpVariable %9 Function + %18 = OpLoad %8 %11 + OpStore %17 %18 + %19 = OpLoad %8 %17 + %21 = OpIAdd %8 %19 %20 + OpReturnValue %21 + OpFunctionEnd + %14 = OpFunction %2 None %53 + %52 = OpFunctionParameter %9 + %15 = OpLabel + %24 = OpVariable %9 Function + OpStore %24 %25 + OpReturn + OpFunctionEnd + )"; ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); } +TEST(TransformationAddParameterTest, PointerPrivateWorkgroupTest) { + // This types handles case of adding a new parameter of a pointer type with + // storage class Private or Workgroup. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun1(" + OpName %12 "fun2(i1;" + OpName %11 "a" + OpName %14 "fun3(" + OpName %17 "s" + OpName %24 "s" + OpName %28 "f1" + OpName %31 "f2" + OpName %34 "i1" + OpName %35 "i2" + OpName %36 "param" + OpName %39 "i3" + OpName %40 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpTypeFunction %8 %9 + %20 = OpConstant %8 2 + %25 = OpConstant %8 0 + %26 = OpTypeFloat 32 + %27 = OpTypePointer Private %26 + %28 = OpVariable %27 Private + %60 = OpTypePointer Workgroup %26 + %61 = OpVariable %60 Workgroup + %29 = OpConstant %26 1 + %30 = OpTypePointer Function %26 + %32 = OpConstant %26 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %31 = OpVariable %30 Function + %34 = OpVariable %9 Function + %35 = OpVariable %9 Function + %36 = OpVariable %9 Function + %39 = OpVariable %9 Function + %40 = OpVariable %9 Function + OpStore %28 %29 + OpStore %31 %32 + %33 = OpFunctionCall %2 %6 + OpStore %34 %20 + %37 = OpLoad %8 %34 + OpStore %36 %37 + %38 = OpFunctionCall %8 %12 %36 + OpStore %35 %38 + %41 = OpLoad %8 %35 + OpStore %40 %41 + %42 = OpFunctionCall %8 %12 %40 + OpStore %39 %42 + %43 = OpFunctionCall %2 %14 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %8 None %10 + %11 = OpFunctionParameter %9 + %13 = OpLabel + %17 = OpVariable %9 Function + %18 = OpLoad %8 %11 + OpStore %17 %18 + %19 = OpLoad %8 %17 + %21 = OpIAdd %8 %19 %20 + OpReturnValue %21 + OpFunctionEnd + %14 = OpFunction %2 None %3 + %15 = OpLabel + %24 = OpVariable %9 Function + OpStore %24 %25 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Good: Global variable of id 28 (storage class Private) is defined in the + // caller (main). + TransformationAddParameter transformation_good_1(12, 70, 27, + {{{38, 28}, {42, 28}}}, 71); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Global variable of id 61 is (storage class Workgroup) is defined in + // the caller (main). + TransformationAddParameter transformation_good_2(12, 72, 27, + {{{38, 28}, {42, 28}}}, 73); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + + // Good: Global variable of id 28 (storage class Private) is defined in the + // caller (main). + TransformationAddParameter transformation_good_3(6, 74, 27, {{{33, 28}}}, 75); + ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(), + transformation_context)); + transformation_good_3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Global variable of id 61 is (storage class Workgroup) is defined in + // the caller (main). + TransformationAddParameter transformation_good_4(6, 76, 60, {{{33, 61}}}, 77); + ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(), + transformation_context)); + transformation_good_4.Apply(context.get(), &transformation_context); + + // Good: Global variable of id 28 (storage class Private) is defined in the + // caller (main). + TransformationAddParameter transformation_good_5(14, 78, 27, {{{43, 28}}}, + 79); + ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(), + transformation_context)); + transformation_good_5.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Global variable of id 61 is (storage class Workgroup) is defined in + // the caller (main). + TransformationAddParameter transformation_good_6(14, 80, 60, {{{43, 61}}}, + 81); + ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(), + transformation_context)); + transformation_good_6.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun1(" + OpName %12 "fun2(i1;" + OpName %11 "a" + OpName %14 "fun3(" + OpName %17 "s" + OpName %24 "s" + OpName %28 "f1" + OpName %31 "f2" + OpName %34 "i1" + OpName %35 "i2" + OpName %36 "param" + OpName %39 "i3" + OpName %40 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %20 = OpConstant %8 2 + %25 = OpConstant %8 0 + %26 = OpTypeFloat 32 + %27 = OpTypePointer Private %26 + %28 = OpVariable %27 Private + %60 = OpTypePointer Workgroup %26 + %61 = OpVariable %60 Workgroup + %29 = OpConstant %26 1 + %30 = OpTypePointer Function %26 + %32 = OpConstant %26 2 + %10 = OpTypeFunction %8 %9 %27 %27 + %75 = OpTypeFunction %2 %27 %60 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %31 = OpVariable %30 Function + %34 = OpVariable %9 Function + %35 = OpVariable %9 Function + %36 = OpVariable %9 Function + %39 = OpVariable %9 Function + %40 = OpVariable %9 Function + OpStore %28 %29 + OpStore %31 %32 + %33 = OpFunctionCall %2 %6 %28 %61 + OpStore %34 %20 + %37 = OpLoad %8 %34 + OpStore %36 %37 + %38 = OpFunctionCall %8 %12 %36 %28 %28 + OpStore %35 %38 + %41 = OpLoad %8 %35 + OpStore %40 %41 + %42 = OpFunctionCall %8 %12 %40 %28 %28 + OpStore %39 %42 + %43 = OpFunctionCall %2 %14 %28 %61 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %75 + %74 = OpFunctionParameter %27 + %76 = OpFunctionParameter %60 + %7 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %8 None %10 + %11 = OpFunctionParameter %9 + %70 = OpFunctionParameter %27 + %72 = OpFunctionParameter %27 + %13 = OpLabel + %17 = OpVariable %9 Function + %18 = OpLoad %8 %11 + OpStore %17 %18 + %19 = OpLoad %8 %17 + %21 = OpIAdd %8 %19 %20 + OpReturnValue %21 + OpFunctionEnd + %14 = OpFunction %2 None %75 + %78 = OpFunctionParameter %27 + %80 = OpFunctionParameter %60 + %15 = OpLabel + %24 = OpVariable %9 Function + OpStore %24 %25 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationAddParameterTest, PointerMoreEntriesInMapTest) { + // This types handles case where call_parameter_id has an entry for at least + // every caller (there are more entries than it is necessary). + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %19 "i1" + OpName %21 "i2" + OpName %22 "i3" + OpName %24 "i4" + OpName %25 "param" + OpName %28 "i5" + OpName %29 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %15 = OpConstant %6 2 + %20 = OpConstant %6 1 + %23 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %19 = OpVariable %7 Function + %21 = OpVariable %7 Function + %22 = OpVariable %7 Function + %24 = OpVariable %7 Function + %25 = OpVariable %7 Function + %28 = OpVariable %7 Function + %29 = OpVariable %7 Function + OpStore %19 %20 + OpStore %21 %15 + OpStore %22 %23 + %26 = OpLoad %6 %19 + OpStore %25 %26 + %27 = OpFunctionCall %6 %10 %25 + OpStore %24 %27 + %30 = OpLoad %6 %21 + OpStore %29 %30 + %31 = OpFunctionCall %6 %10 %29 + OpStore %28 %31 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %13 = OpLoad %6 %9 + OpStore %12 %13 + %14 = OpLoad %6 %12 + %16 = OpIAdd %6 %14 %15 + OpReturnValue %16 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Good: Local variable of id 21 is defined in every caller (id 27 and id 31). + TransformationAddParameter transformation_good_1( + 10, 70, 7, {{{27, 21}, {31, 21}, {30, 21}}}, 71); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Good: Local variable of id 28 is defined in every caller (id 27 and id 31). + TransformationAddParameter transformation_good_2( + 10, 72, 7, {{{27, 28}, {31, 28}, {14, 21}, {16, 14}}}, 73); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %19 "i1" + OpName %21 "i2" + OpName %22 "i3" + OpName %24 "i4" + OpName %25 "param" + OpName %28 "i5" + OpName %29 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %15 = OpConstant %6 2 + %20 = OpConstant %6 1 + %23 = OpConstant %6 3 + %8 = OpTypeFunction %6 %7 %7 %7 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %19 = OpVariable %7 Function + %21 = OpVariable %7 Function + %22 = OpVariable %7 Function + %24 = OpVariable %7 Function + %25 = OpVariable %7 Function + %28 = OpVariable %7 Function + %29 = OpVariable %7 Function + OpStore %19 %20 + OpStore %21 %15 + OpStore %22 %23 + %26 = OpLoad %6 %19 + OpStore %25 %26 + %27 = OpFunctionCall %6 %10 %25 %21 %28 + OpStore %24 %27 + %30 = OpLoad %6 %21 + OpStore %29 %30 + %31 = OpFunctionCall %6 %10 %29 %21 %28 + OpStore %28 %31 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %70 = OpFunctionParameter %7 + %72 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %13 = OpLoad %6 %9 + OpStore %12 %13 + %14 = OpLoad %6 %12 + %16 = OpIAdd %6 %14 %15 + OpReturnValue %16 + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationAddParameterTest, PointeeValueIsIrrelevantTest) { + // This test checks if the transformation has correctly applied the + // PointeeValueIsIrrelevant fact for new pointer parameters. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %20 "b" + OpName %22 "i1" + OpName %24 "i2" + OpName %25 "i3" + OpName %26 "param" + OpName %29 "i4" + OpName %30 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %50 = OpTypePointer Workgroup %6 + %51 = OpVariable %50 Workgroup + %8 = OpTypeFunction %6 %7 + %15 = OpConstant %6 2 + %19 = OpTypePointer Private %6 + %20 = OpVariable %19 Private + %21 = OpConstant %6 0 + %23 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %22 = OpVariable %7 Function + %24 = OpVariable %7 Function + %25 = OpVariable %7 Function + %26 = OpVariable %7 Function + %29 = OpVariable %7 Function + %30 = OpVariable %7 Function + OpStore %20 %21 + OpStore %22 %23 + OpStore %24 %15 + %27 = OpLoad %6 %22 + OpStore %26 %27 + %28 = OpFunctionCall %6 %10 %26 + OpStore %25 %28 + %31 = OpLoad %6 %24 + OpStore %30 %31 + %32 = OpFunctionCall %6 %10 %30 + OpStore %29 %32 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %13 = OpLoad %6 %9 + OpStore %12 %13 + %14 = OpLoad %6 %12 + %16 = OpIAdd %6 %14 %15 + OpReturnValue %16 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationAddParameter transformation_good_1(10, 70, 7, + {{{28, 22}, {32, 22}}}, 71); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Check if the fact PointeeValueIsIrrelevant is set for the new parameter + // (storage class Function). + ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(70)); + + TransformationAddParameter transformation_good_2(10, 72, 19, + {{{28, 20}, {32, 20}}}, 73); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Check if the fact PointeeValueIsIrrelevant is set for the new parameter + // (storage class Private). + ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(72)); + + TransformationAddParameter transformation_good_3(10, 74, 50, + {{{28, 51}, {32, 51}}}, 75); + ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(), + transformation_context)); + transformation_good_3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // Check if the fact PointeeValueIsIrrelevant is set for the new parameter + // (storage class Workgroup). + ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(74)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_add_relaxed_decoration_test.cpp b/test/fuzz/transformation_add_relaxed_decoration_test.cpp index 6e163ade..e91800aa 100644 --- a/test/fuzz/transformation_add_relaxed_decoration_test.cpp +++ b/test/fuzz/transformation_add_relaxed_decoration_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_relaxed_decoration.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -66,7 +67,7 @@ TEST(TransformationAddRelaxedDecorationTest, BasicScenarios) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -76,6 +77,9 @@ TEST(TransformationAddRelaxedDecorationTest, BasicScenarios) { // Invalid: 200 is not an id. ASSERT_FALSE(TransformationAddRelaxedDecoration(200).IsApplicable( context.get(), transformation_context)); + // Invalid: 1 is not in a block. + ASSERT_FALSE(TransformationAddRelaxedDecoration(1).IsApplicable( + context.get(), transformation_context)); // Invalid: 27 is not in a dead block. ASSERT_FALSE(TransformationAddRelaxedDecoration(27).IsApplicable( context.get(), transformation_context)); diff --git a/test/fuzz/transformation_add_synonym_test.cpp b/test/fuzz/transformation_add_synonym_test.cpp index ee9bea37..603a3dbf 100644 --- a/test/fuzz/transformation_add_synonym_test.cpp +++ b/test/fuzz/transformation_add_synonym_test.cpp @@ -70,11 +70,13 @@ TEST(TransformationAddSynonymTest, NotApplicable) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); + fact_manager.AddFactIdIsIrrelevant(24); + auto insert_before = MakeInstructionDescriptor(22, SpvOpReturn, 0); #ifndef NDEBUG @@ -86,65 +88,73 @@ TEST(TransformationAddSynonymTest, NotApplicable) { "Synonym type is invalid"); #endif - // |synonym_fresh_id| is not fresh. - ASSERT_FALSE( - TransformationAddSynonym(9, protobufs::TransformationAddSynonym::ADD_ZERO, - 9, insert_before) - .IsApplicable(context.get(), transformation_context)); - - // |result_id| is invalid. - ASSERT_FALSE( - TransformationAddSynonym( - 40, protobufs::TransformationAddSynonym::ADD_ZERO, 40, insert_before) - .IsApplicable(context.get(), transformation_context)); - - // Instruction with |result_id| has no type id. - ASSERT_FALSE( - TransformationAddSynonym(5, protobufs::TransformationAddSynonym::ADD_ZERO, - 40, insert_before) - .IsApplicable(context.get(), transformation_context)); - - // Instruction with |result_id| is an OpUndef. - ASSERT_FALSE( - TransformationAddSynonym( - 25, protobufs::TransformationAddSynonym::ADD_ZERO, 40, insert_before) - .IsApplicable(context.get(), transformation_context)); - - // Instruction with |result_id| is an OpConstantNull. - ASSERT_FALSE( - TransformationAddSynonym( - 26, protobufs::TransformationAddSynonym::ADD_ZERO, 40, insert_before) - .IsApplicable(context.get(), transformation_context)); - - // |insert_before| is invalid. - ASSERT_FALSE( - TransformationAddSynonym(9, protobufs::TransformationAddSynonym::ADD_ZERO, - 40, MakeInstructionDescriptor(25, SpvOpStore, 0)) - .IsApplicable(context.get(), transformation_context)); - - // Can't insert before |insert_before|. - ASSERT_FALSE( - TransformationAddSynonym(9, protobufs::TransformationAddSynonym::ADD_ZERO, - 40, MakeInstructionDescriptor(5, SpvOpLabel, 0)) - .IsApplicable(context.get(), transformation_context)); - ASSERT_FALSE(TransformationAddSynonym( - 9, protobufs::TransformationAddSynonym::ADD_ZERO, 40, - MakeInstructionDescriptor(22, SpvOpVariable, 0)) - .IsApplicable(context.get(), transformation_context)); - ASSERT_FALSE(TransformationAddSynonym( - 9, protobufs::TransformationAddSynonym::ADD_ZERO, 40, - MakeInstructionDescriptor(25, SpvOpFunctionEnd, 0)) - .IsApplicable(context.get(), transformation_context)); + // These tests should succeed regardless of the synonym type. + for (int i = 0; + i < protobufs::TransformationAddSynonym::SynonymType_descriptor() + ->value_count(); + ++i) { + const auto* synonym_value = + protobufs::TransformationAddSynonym::SynonymType_descriptor()->value(i); + ASSERT_TRUE(protobufs::TransformationAddSynonym::SynonymType_IsValid( + synonym_value->number())); + auto synonym_type = + static_cast<protobufs::TransformationAddSynonym::SynonymType>( + synonym_value->number()); + + // |synonym_fresh_id| is not fresh. + ASSERT_FALSE(TransformationAddSynonym(9, synonym_type, 9, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |result_id| is invalid. + ASSERT_FALSE(TransformationAddSynonym(40, synonym_type, 40, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // Instruction with |result_id| has no type id. + ASSERT_FALSE(TransformationAddSynonym(5, synonym_type, 40, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // Instruction with |result_id| is an OpUndef. + ASSERT_FALSE(TransformationAddSynonym(25, synonym_type, 40, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // Instruction with |result_id| is an OpConstantNull. + ASSERT_FALSE(TransformationAddSynonym(26, synonym_type, 40, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |result_id| is irrelevant. + ASSERT_FALSE(TransformationAddSynonym(24, synonym_type, 40, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |insert_before| is invalid. + ASSERT_FALSE( + TransformationAddSynonym(9, synonym_type, 40, + MakeInstructionDescriptor(25, SpvOpStore, 0)) + .IsApplicable(context.get(), transformation_context)); - // Domination rules are not satisfied. - ASSERT_FALSE(TransformationAddSynonym( - 27, protobufs::TransformationAddSynonym::ADD_ZERO, 40, - MakeInstructionDescriptor(27, SpvOpLoad, 0)) - .IsApplicable(context.get(), transformation_context)); - ASSERT_FALSE(TransformationAddSynonym( - 27, protobufs::TransformationAddSynonym::ADD_ZERO, 40, - MakeInstructionDescriptor(22, SpvOpStore, 1)) - .IsApplicable(context.get(), transformation_context)); + // Can't insert before |insert_before|. + ASSERT_FALSE( + TransformationAddSynonym(9, synonym_type, 40, + MakeInstructionDescriptor(5, SpvOpLabel, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddSynonym( + 9, synonym_type, 40, + MakeInstructionDescriptor(22, SpvOpVariable, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationAddSynonym( + 9, synonym_type, 40, + MakeInstructionDescriptor(25, SpvOpFunctionEnd, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Domination rules are not satisfied. + ASSERT_FALSE( + TransformationAddSynonym(27, synonym_type, 40, + MakeInstructionDescriptor(27, SpvOpLoad, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationAddSynonym(27, synonym_type, 40, + MakeInstructionDescriptor(22, SpvOpStore, 1)) + .IsApplicable(context.get(), transformation_context)); + } } TEST(TransformationAddSynonymTest, AddZeroSubZeroMulOne) { @@ -198,7 +208,7 @@ TEST(TransformationAddSynonymTest, AddZeroSubZeroMulOne) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -335,7 +345,7 @@ TEST(TransformationAddSynonymTest, LogicalAndLogicalOr) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -429,7 +439,7 @@ TEST(TransformationAddSynonymTest, LogicalAndConstantIsNotPresent) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -469,7 +479,7 @@ TEST(TransformationAddSynonymTest, LogicalOrConstantIsNotPresent) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -529,7 +539,7 @@ TEST(TransformationAddSynonymTest, CopyObject) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -626,7 +636,7 @@ TEST(TransformationAddSynonymTest, CopyBooleanConstants) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -934,7 +944,7 @@ TEST(TransformationAddSynonymTest, CheckIllegalCases) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1129,7 +1139,7 @@ TEST(TransformationAddSynonymTest, MiscellaneousCopies) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1239,7 +1249,7 @@ TEST(TransformationAddSynonymTest, DoNotCopyNullOrUndefPointers) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1285,7 +1295,7 @@ TEST(TransformationAddSynonymTest, PropagateIrrelevantPointeeFact) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1324,7 +1334,7 @@ TEST(TransformationAddSynonymTest, PropagateIrrelevantPointeeFact) { transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101)); } -TEST(TransformationAddSynonym, DoNotCopyOpSampledImage) { +TEST(TransformationAddSynonymTest, DoNotCopyOpSampledImage) { // This checks that we do not try to copy the result id of an OpSampledImage // instruction. std::string shader = R"( @@ -1370,7 +1380,7 @@ TEST(TransformationAddSynonym, DoNotCopyOpSampledImage) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1382,6 +1392,96 @@ TEST(TransformationAddSynonym, DoNotCopyOpSampledImage) { .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationAddSynonymTest, DoNotCopyVoidRunctionResult) { + // This checks that we do not try to copy the result of a void function. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + OpName %4 "main" + OpName %6 "foo(" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationAddSynonym( + 8, protobufs::TransformationAddSynonym::COPY_OBJECT, 500, + MakeInstructionDescriptor(8, SpvOpReturn, 0)) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddSynonymTest, HandlesDeadBlocks) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %11 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %11 Function + OpSelectionMerge %10 None + OpBranchConditional %7 %8 %9 + %8 = OpLabel + OpBranch %10 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactBlockIsDead(9); + + auto insert_before = MakeInstructionDescriptor(9, SpvOpBranch, 0); + + ASSERT_FALSE(TransformationAddSynonym( + 7, protobufs::TransformationAddSynonym::COPY_OBJECT, 100, + insert_before) + .IsApplicable(context.get(), transformation_context)); + + ASSERT_FALSE(TransformationAddSynonym( + 12, protobufs::TransformationAddSynonym::COPY_OBJECT, 100, + insert_before) + .IsApplicable(context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_add_type_array_test.cpp b/test/fuzz/transformation_add_type_array_test.cpp index 4392f99f..dae8aa68 100644 --- a/test/fuzz/transformation_add_type_array_test.cpp +++ b/test/fuzz/transformation_add_type_array_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_array.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -53,7 +54,7 @@ TEST(TransformationAddTypeArrayTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_type_boolean_test.cpp b/test/fuzz/transformation_add_type_boolean_test.cpp index 60eabd9d..e12651ef 100644 --- a/test/fuzz/transformation_add_type_boolean_test.cpp +++ b/test/fuzz/transformation_add_type_boolean_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_boolean.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -41,7 +42,7 @@ TEST(TransformationAddTypeBooleanTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_type_float_test.cpp b/test/fuzz/transformation_add_type_float_test.cpp index 7d172667..68b516e3 100644 --- a/test/fuzz/transformation_add_type_float_test.cpp +++ b/test/fuzz/transformation_add_type_float_test.cpp @@ -13,70 +13,132 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_float.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { -TEST(TransformationAddTypeFloatTest, BasicTest) { - std::string shader = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpReturn - OpFunctionEnd +TEST(TransformationAddTypeFloatTest, IsApplicable) { + std::string reference_shader = R"( + OpCapability Shader + OpCapability Float16 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %5 "main" + +; Types + %2 = OpTypeFloat 16 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; main function + %5 = OpFunction %3 None %4 + %6 = OpLabel + OpReturn + OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - // Not applicable because id 1 is already in use. - ASSERT_FALSE(TransformationAddTypeFloat(1, 32).IsApplicable( - context.get(), transformation_context)); + // Tests non-fresh id. + auto transformation = TransformationAddTypeFloat(1, 32); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - auto add_type_float_32 = TransformationAddTypeFloat(100, 32); + // Tests missing Float64 capability. + transformation = TransformationAddTypeFloat(7, 64); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests existing 16-bit float type. + transformation = TransformationAddTypeFloat(7, 16); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests adding 32-bit float type. + transformation = TransformationAddTypeFloat(7, 32); ASSERT_TRUE( - add_type_float_32.IsApplicable(context.get(), transformation_context)); - add_type_float_32.Apply(context.get(), &transformation_context); + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddTypeFloatTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + OpCapability Float16 + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + +; main function + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - // Not applicable as we already have this type now. - ASSERT_FALSE(TransformationAddTypeFloat(101, 32).IsApplicable( - context.get(), transformation_context)); - - std::string after_transformation = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %100 = OpTypeFloat 32 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpReturn - OpFunctionEnd + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Adds 16-bit float type. + auto transformation = TransformationAddTypeFloat(6, 16); + transformation.Apply(context.get(), &transformation_context); + + // Adds 32-bit float type. + transformation = TransformationAddTypeFloat(7, 32); + transformation.Apply(context.get(), &transformation_context); + + // Adds 64-bit float type. + transformation = TransformationAddTypeFloat(8, 64); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + OpCapability Float16 + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 16 + %7 = OpTypeFloat 32 + %8 = OpTypeFloat 64 + +; main function + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } } // namespace diff --git a/test/fuzz/transformation_add_type_function_test.cpp b/test/fuzz/transformation_add_type_function_test.cpp index 1557bb81..298c2ff1 100644 --- a/test/fuzz/transformation_add_type_function_test.cpp +++ b/test/fuzz/transformation_add_type_function_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_function.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -58,7 +59,7 @@ TEST(TransformationAddTypeFunctionTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp index 63b17c22..5a577d27 100644 --- a/test/fuzz/transformation_add_type_int_test.cpp +++ b/test/fuzz/transformation_add_type_int_test.cpp @@ -13,83 +13,174 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_int.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { -TEST(TransformationAddTypeIntTest, BasicTest) { - std::string shader = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpReturn - OpFunctionEnd +TEST(TransformationAddTypeIntTest, IsApplicable) { + std::string reference_shader = R"( + OpCapability Shader + OpCapability Int8 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %5 "main" + +; Types + %2 = OpTypeInt 8 1 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; main function + %5 = OpFunction %3 None %4 + %6 = OpLabel + OpReturn + OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - // Not applicable because id 1 is already in use. - ASSERT_FALSE(TransformationAddTypeInt(1, 32, false) - .IsApplicable(context.get(), transformation_context)); + // Tests non-fresh id. + auto transformation = TransformationAddTypeInt(1, 32, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - auto add_type_signed_int_32 = TransformationAddTypeInt(100, 32, true); - auto add_type_unsigned_int_32 = TransformationAddTypeInt(101, 32, false); - auto add_type_signed_int_32_again = TransformationAddTypeInt(102, 32, true); - auto add_type_unsigned_int_32_again = - TransformationAddTypeInt(103, 32, false); + // Tests missing Int16 capability. + transformation = TransformationAddTypeInt(7, 16, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); - ASSERT_TRUE(add_type_signed_int_32.IsApplicable(context.get(), - transformation_context)); - add_type_signed_int_32.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); + // Tests missing Int64 capability. + transformation = TransformationAddTypeInt(7, 64, false); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests existing signed 8-bit integer type. + transformation = TransformationAddTypeInt(7, 8, true); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests adding unsigned 8-bit integer type. + transformation = TransformationAddTypeInt(7, 8, false); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests adding unsigned 32-bit integer type. + transformation = TransformationAddTypeInt(7, 32, false); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests adding signed 32-bit integer type. + transformation = TransformationAddTypeInt(7, 32, true); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationAddTypeIntTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + OpCapability Int8 + OpCapability Int16 + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + +; main function + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; - ASSERT_TRUE(add_type_unsigned_int_32.IsApplicable(context.get(), - transformation_context)); - add_type_unsigned_int_32.Apply(context.get(), &transformation_context); + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - // Not applicable as we already have these types now. - ASSERT_FALSE(add_type_signed_int_32_again.IsApplicable( - context.get(), transformation_context)); - ASSERT_FALSE(add_type_unsigned_int_32_again.IsApplicable( - context.get(), transformation_context)); - - std::string after_transformation = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %100 = OpTypeInt 32 1 - %101 = OpTypeInt 32 0 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpReturn - OpFunctionEnd + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Adds signed 8-bit integer type. + auto transformation = TransformationAddTypeInt(6, 8, true); + transformation.Apply(context.get(), &transformation_context); + + // Adds signed 16-bit integer type. + transformation = TransformationAddTypeInt(7, 16, true); + transformation.Apply(context.get(), &transformation_context); + + // Adds signed 32-bit integer type. + transformation = TransformationAddTypeInt(8, 32, true); + transformation.Apply(context.get(), &transformation_context); + + // Adds signed 64-bit integer type. + transformation = TransformationAddTypeInt(9, 64, true); + transformation.Apply(context.get(), &transformation_context); + + // Adds unsigned 8-bit integer type. + transformation = TransformationAddTypeInt(10, 8, false); + transformation.Apply(context.get(), &transformation_context); + + // Adds unsigned 16-bit integer type. + transformation = TransformationAddTypeInt(11, 16, false); + transformation.Apply(context.get(), &transformation_context); + + // Adds unsigned 32-bit integer type. + transformation = TransformationAddTypeInt(12, 32, false); + transformation.Apply(context.get(), &transformation_context); + + // Adds unsigned 64-bit integer type. + transformation = TransformationAddTypeInt(13, 64, false); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + OpCapability Int8 + OpCapability Int16 + OpCapability Int64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 8 1 + %7 = OpTypeInt 16 1 + %8 = OpTypeInt 32 1 + %9 = OpTypeInt 64 1 + %10 = OpTypeInt 8 0 + %11 = OpTypeInt 16 0 + %12 = OpTypeInt 32 0 + %13 = OpTypeInt 64 0 + +; main function + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd )"; - ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } } // namespace diff --git a/test/fuzz/transformation_add_type_matrix_test.cpp b/test/fuzz/transformation_add_type_matrix_test.cpp index e925012e..48709f2f 100644 --- a/test/fuzz/transformation_add_type_matrix_test.cpp +++ b/test/fuzz/transformation_add_type_matrix_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_matrix.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -46,7 +47,7 @@ TEST(TransformationAddTypeMatrixTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp index 35303e41..57080ee1 100644 --- a/test/fuzz/transformation_add_type_pointer_test.cpp +++ b/test/fuzz/transformation_add_type_pointer_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_pointer.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -96,7 +97,7 @@ TEST(TransformationAddTypePointerTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_add_type_struct_test.cpp b/test/fuzz/transformation_add_type_struct_test.cpp index 06f78cd3..89ccf8a4 100644 --- a/test/fuzz/transformation_add_type_struct_test.cpp +++ b/test/fuzz/transformation_add_type_struct_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_struct.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -46,7 +47,7 @@ TEST(TransformationAddTypeStructTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -109,6 +110,49 @@ TEST(TransformationAddTypeStructTest, BasicTest) { ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } +TEST(TransformationAddTypeStructTest, HandlesBuiltInMembers) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %2 "main" + OpMemberDecorate %4 0 BuiltIn Position + OpMemberDecorate %4 1 BuiltIn PointSize + OpMemberDecorate %4 2 BuiltIn ClipDistance + %6 = OpTypeFloat 32 + %5 = OpTypeVector %6 4 + %9 = OpTypeInt 32 1 + %8 = OpConstant %9 1 + %7 = OpTypeArray %6 %8 + %4 = OpTypeStruct %5 %6 %7 + %27 = OpTypeVoid + %28 = OpTypeFunction %27 + %2 = OpFunction %27 None %28 + %29 = OpLabel + OpReturn + OpFunctionEnd + + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // From the spec for the BuiltIn decoration: + // - When applied to a structure-type member, that structure type cannot + // be contained as a member of another structure type. + // + // OpTypeStruct with id %4 has BuiltIn members. + ASSERT_FALSE(TransformationAddTypeStruct(50, {6, 5, 4, 6, 7}) + .IsApplicable(context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_add_type_vector_test.cpp b/test/fuzz/transformation_add_type_vector_test.cpp index f1252a30..f286bc39 100644 --- a/test/fuzz/transformation_add_type_vector_test.cpp +++ b/test/fuzz/transformation_add_type_vector_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_add_type_vector.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -44,7 +45,7 @@ TEST(TransformationAddTypeVectorTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_adjust_branch_weights_test.cpp b/test/fuzz/transformation_adjust_branch_weights_test.cpp index 7f8ba317..b13afe7a 100644 --- a/test/fuzz/transformation_adjust_branch_weights_test.cpp +++ b/test/fuzz/transformation_adjust_branch_weights_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_adjust_branch_weights.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -100,7 +101,7 @@ TEST(TransformationAdjustBranchWeightsTest, IsApplicableTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -250,7 +251,7 @@ TEST(TransformationAdjustBranchWeightsTest, ApplyTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_composite_construct_test.cpp b/test/fuzz/transformation_composite_construct_test.cpp index b6638668..ed5c9305 100644 --- a/test/fuzz/transformation_composite_construct_test.cpp +++ b/test/fuzz/transformation_composite_construct_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_composite_construct.h" + #include "source/fuzz/data_descriptor.h" #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -128,7 +129,7 @@ TEST(TransformationCompositeConstructTest, ConstructArrays) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -394,7 +395,7 @@ TEST(TransformationCompositeConstructTest, ConstructMatrices) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -607,7 +608,7 @@ TEST(TransformationCompositeConstructTest, ConstructStructs) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -931,7 +932,7 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1358,6 +1359,176 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) { ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } +TEST(TransformationCompositeConstructTest, AddSynonymsForRelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 3 + %8 = OpTypePointer Function %7 + %10 = OpConstant %6 1 + %11 = OpConstantComposite %7 %10 %10 %10 + %17 = OpTypeVector %6 4 + %18 = OpTypePointer Function %17 + %21 = OpConstant %6 2 + %32 = OpTypeMatrix %17 3 + %33 = OpTypePointer Private %32 + %34 = OpVariable %33 Private + %35 = OpTypeMatrix %7 4 + %36 = OpTypePointer Private %35 + %37 = OpVariable %36 Private + %38 = OpTypeVector %6 2 + %39 = OpTypeInt 32 0 + %40 = OpConstant %39 3 + %41 = OpTypeArray %38 %40 + %42 = OpTypePointer Private %41 + %43 = OpVariable %42 Private + %100 = OpUndef %7 + %101 = OpUndef %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %9 = OpVariable %8 Function + %12 = OpVariable %8 Function + %14 = OpVariable %8 Function + %19 = OpVariable %18 Function + %26 = OpVariable %18 Function + %29 = OpVariable %18 Function + OpStore %9 %11 + %13 = OpLoad %7 %9 + OpStore %12 %13 + %15 = OpLoad %7 %12 + %16 = OpVectorShuffle %7 %15 %15 2 1 0 + OpStore %14 %16 + %20 = OpLoad %7 %14 + %22 = OpCompositeExtract %6 %20 0 + %23 = OpCompositeExtract %6 %20 1 + %24 = OpCompositeExtract %6 %20 2 + %25 = OpCompositeConstruct %17 %22 %23 %24 %21 + OpStore %19 %25 + %27 = OpLoad %17 %19 + %28 = OpVectorShuffle %17 %27 %27 3 2 1 0 + OpStore %26 %28 + %30 = OpLoad %7 %9 + %31 = OpVectorShuffle %17 %30 %30 0 0 1 1 + OpStore %29 %31 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationCompositeConstruct transformation( + 32, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}), + MakeDataDescriptor(200, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}), + MakeDataDescriptor(200, {1}))); +} + +TEST(TransformationCompositeConstructTest, DontAddSynonymsForIrrelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 3 + %8 = OpTypePointer Function %7 + %10 = OpConstant %6 1 + %11 = OpConstantComposite %7 %10 %10 %10 + %17 = OpTypeVector %6 4 + %18 = OpTypePointer Function %17 + %21 = OpConstant %6 2 + %32 = OpTypeMatrix %17 3 + %33 = OpTypePointer Private %32 + %34 = OpVariable %33 Private + %35 = OpTypeMatrix %7 4 + %36 = OpTypePointer Private %35 + %37 = OpVariable %36 Private + %38 = OpTypeVector %6 2 + %39 = OpTypeInt 32 0 + %40 = OpConstant %39 3 + %41 = OpTypeArray %38 %40 + %42 = OpTypePointer Private %41 + %43 = OpVariable %42 Private + %100 = OpUndef %7 + %101 = OpUndef %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %9 = OpVariable %8 Function + %12 = OpVariable %8 Function + %14 = OpVariable %8 Function + %19 = OpVariable %18 Function + %26 = OpVariable %18 Function + %29 = OpVariable %18 Function + OpStore %9 %11 + %13 = OpLoad %7 %9 + OpStore %12 %13 + %15 = OpLoad %7 %12 + %16 = OpVectorShuffle %7 %15 %15 2 1 0 + OpStore %14 %16 + %20 = OpLoad %7 %14 + %22 = OpCompositeExtract %6 %20 0 + %23 = OpCompositeExtract %6 %20 1 + %24 = OpCompositeExtract %6 %20 2 + %25 = OpCompositeConstruct %17 %22 %23 %24 %21 + OpStore %19 %25 + %27 = OpLoad %17 %19 + %28 = OpVectorShuffle %17 %27 %27 3 2 1 0 + OpStore %26 %28 + %30 = OpLoad %7 %9 + %31 = OpVectorShuffle %17 %30 %30 0 0 1 1 + OpStore %29 %31 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(25); + + TransformationCompositeConstruct transformation( + 32, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}), + MakeDataDescriptor(200, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}), + MakeDataDescriptor(200, {1}))); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_composite_extract_test.cpp b/test/fuzz/transformation_composite_extract_test.cpp index a7674a65..b25313e9 100644 --- a/test/fuzz/transformation_composite_extract_test.cpp +++ b/test/fuzz/transformation_composite_extract_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_composite_extract.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -95,7 +96,7 @@ TEST(TransformationCompositeExtractTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -350,7 +351,7 @@ TEST(TransformationCompositeExtractTest, IllegalInsertionPoints) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -399,6 +400,185 @@ TEST(TransformationCompositeExtractTest, IllegalInsertionPoints) { .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationCompositeExtractTest, AddSynonymsForRelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %17 "FunnyPoint" + OpMemberName %17 0 "x" + OpMemberName %17 1 "y" + OpMemberName %17 2 "z" + OpName %19 "p" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %12 = OpTypeBool + %16 = OpTypeFloat 32 + %17 = OpTypeStruct %16 %16 %6 + %81 = OpTypeStruct %17 %16 + %18 = OpTypePointer Function %17 + %20 = OpConstant %6 0 + %23 = OpTypePointer Function %16 + %26 = OpConstant %6 1 + %30 = OpConstant %6 2 + %80 = OpUndef %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %19 = OpVariable %18 Function + %9 = OpLoad %6 %8 + %11 = OpLoad %6 %10 + %100 = OpCompositeConstruct %17 %80 %80 %26 + %104 = OpCompositeConstruct %81 %100 %80 + %13 = OpIEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %25 + %14 = OpLabel + %21 = OpLoad %6 %8 + %22 = OpConvertSToF %16 %21 + %101 = OpCompositeConstruct %17 %22 %80 %30 + %24 = OpAccessChain %23 %19 %20 + OpStore %24 %22 + OpBranch %15 + %25 = OpLabel + %27 = OpLoad %6 %10 + %28 = OpConvertSToF %16 %27 + %102 = OpCompositeConstruct %17 %80 %28 %27 + %29 = OpAccessChain %23 %19 %26 + OpStore %29 %28 + OpBranch %15 + %15 = OpLabel + %31 = OpAccessChain %23 %19 %20 + %32 = OpLoad %16 %31 + %33 = OpAccessChain %23 %19 %26 + %34 = OpLoad %16 %33 + %103 = OpCompositeConstruct %17 %34 %32 %9 + %35 = OpFAdd %16 %32 %34 + %36 = OpConvertFToS %6 %35 + %37 = OpAccessChain %7 %19 %30 + OpStore %37 %36 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationCompositeExtract transformation( + MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}), + MakeDataDescriptor(100, {2}))); +} + +TEST(TransformationCompositeExtractTest, DontAddSynonymsForIrrelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %17 "FunnyPoint" + OpMemberName %17 0 "x" + OpMemberName %17 1 "y" + OpMemberName %17 2 "z" + OpName %19 "p" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %12 = OpTypeBool + %16 = OpTypeFloat 32 + %17 = OpTypeStruct %16 %16 %6 + %81 = OpTypeStruct %17 %16 + %18 = OpTypePointer Function %17 + %20 = OpConstant %6 0 + %23 = OpTypePointer Function %16 + %26 = OpConstant %6 1 + %30 = OpConstant %6 2 + %80 = OpUndef %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %19 = OpVariable %18 Function + %9 = OpLoad %6 %8 + %11 = OpLoad %6 %10 + %100 = OpCompositeConstruct %17 %80 %80 %26 + %104 = OpCompositeConstruct %81 %100 %80 + %13 = OpIEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %25 + %14 = OpLabel + %21 = OpLoad %6 %8 + %22 = OpConvertSToF %16 %21 + %101 = OpCompositeConstruct %17 %22 %80 %30 + %24 = OpAccessChain %23 %19 %20 + OpStore %24 %22 + OpBranch %15 + %25 = OpLabel + %27 = OpLoad %6 %10 + %28 = OpConvertSToF %16 %27 + %102 = OpCompositeConstruct %17 %80 %28 %27 + %29 = OpAccessChain %23 %19 %26 + OpStore %29 %28 + OpBranch %15 + %15 = OpLabel + %31 = OpAccessChain %23 %19 %20 + %32 = OpLoad %16 %31 + %33 = OpAccessChain %23 %19 %26 + %34 = OpLoad %16 %33 + %103 = OpCompositeConstruct %17 %34 %32 %9 + %35 = OpFAdd %16 %32 %34 + %36 = OpConvertFToS %6 %35 + %37 = OpAccessChain %7 %19 %30 + OpStore %37 %36 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(100); + TransformationCompositeExtract transformation( + MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}), + MakeDataDescriptor(100, {2}))); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_composite_insert_test.cpp b/test/fuzz/transformation_composite_insert_test.cpp new file mode 100644 index 00000000..7cb17fa9 --- /dev/null +++ b/test/fuzz/transformation_composite_insert_test.cpp @@ -0,0 +1,814 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_composite_insert.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationCompositeInsertTest, NotApplicableScenarios) { + // This test handles cases where IsApplicable() returns false. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpName %20 "l1" + OpName %24 "level_2" + OpMemberName %24 0 "c1" + OpMemberName %24 1 "c2" + OpName %26 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 + %19 = OpTypePointer Function %18 + %24 = OpTypeStruct %18 %18 + %25 = OpTypePointer Function %24 + %30 = OpTypeBool + %31 = OpConstantTrue %30 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %26 = OpVariable %25 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpCompositeConstruct %18 %21 %22 + OpStore %20 %23 + %27 = OpLoad %18 %20 + %28 = OpLoad %18 %20 + %29 = OpCompositeConstruct %24 %27 %28 + OpStore %26 %29 + OpSelectionMerge %33 None + OpBranchConditional %31 %32 %33 + %32 = OpLabel + OpBranch %33 + %33 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: |fresh_id| is not fresh. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 20, 29, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not refer to a existing instruction. + auto transformation_bad_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 40, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not refer to a composite value. + auto transformation_bad_3 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 9, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: |object_id| does not refer to a defined instruction. + auto transformation_bad_4 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 40, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); + + // Bad: |object_id| cannot refer to a pointer. + auto transformation_bad_5 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 8, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_5.IsApplicable(context.get(), transformation_context)); + + // Bad: |index| is not a correct index. + auto transformation_bad_6 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {2, 0, 0}); + ASSERT_FALSE( + transformation_bad_6.IsApplicable(context.get(), transformation_context)); + + // Bad: Type id of the object to be inserted and the type id of the + // component at |index| are not the same. + auto transformation_bad_7 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {1, 0}); + ASSERT_FALSE( + transformation_bad_7.IsApplicable(context.get(), transformation_context)); + + // Bad: |instruction_to_insert_before| does not refer to a defined + // instruction. + auto transformation_bad_8 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpIMul, 0), 50, 29, 11, {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_8.IsApplicable(context.get(), transformation_context)); + + // Bad: OpCompositeInsert cannot be inserted before OpBranchConditional with + // OpSelectionMerge above it. + auto transformation_bad_9 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpBranchConditional, 0), 50, 29, 11, + {1, 0, 0}); + ASSERT_FALSE( + transformation_bad_9.IsApplicable(context.get(), transformation_context)); + + // Bad: |composite_id| does not have a type_id. + auto transformation_bad_10 = TransformationCompositeInsert( + MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 1, 11, {1, 0, 0}); + ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(), + transformation_context)); +} + +TEST(TransformationCompositeInsertTest, EmptyCompositeScenarios) { + // This test handles cases where either the composite is empty or the + // composite contains an empty composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + %2 = OpTypeVoid + %60 = OpTypeStruct + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %61 = OpConstantComposite %60 + %62 = OpConstantComposite %60 + %12 = OpTypeStruct %6 %6 + %63 = OpTypeStruct %6 %60 + %13 = OpTypePointer Function %12 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + %64 = OpCompositeConstruct %63 %15 %61 + OpStore %14 %17 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: The composite with |composite_id| cannot be empty. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 61, 62, {1}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Good: It is possible to insert into a composite an element which is an + // empty composite. + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 64, 62, {1}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + %2 = OpTypeVoid + %60 = OpTypeStruct + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %61 = OpConstantComposite %60 + %62 = OpConstantComposite %60 + %12 = OpTypeStruct %6 %6 + %63 = OpTypeStruct %6 %60 + %13 = OpTypePointer Function %12 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + %64 = OpCompositeConstruct %63 %15 %61 + %50 = OpCompositeInsert %63 %62 %64 1 + OpStore %14 %17 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationCompositeInsertTest, IrrelevantCompositeNoSynonyms) { + // This test handles cases where either |composite| is irrelevant. + // The transformation shouldn't create any synonyms. + // The member composite has a different number of elements than the parent + // composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Add fact that the composite is irrelevant. + fact_manager.AddFactIdIsIrrelevant(30); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // No synonyms should have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); +} +TEST(TransformationCompositeInsertTest, IrrelevantObjectSomeSynonyms) { + // This test handles cases where |object| is irrelevant. + // The transformation should create some synonyms. It shouldn't create a + // synonym related to |object|. The member composite has a different number of + // elements than the parent composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Add fact that the object is irrelevant. + fact_manager.AddFactIdIsIrrelevant(11); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + // This synonym shouldn't have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); +} + +TEST(TransformationCompositeInsertTest, ApplicableCreatedSynonyms) { + // This test handles cases where neither |composite| nor |object| is + // irrelevant. The transformation should create synonyms. + // The member composite has a different number of elements than the parent + // composite. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation_good_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), + MakeDataDescriptor(50, {0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}), + MakeDataDescriptor(50, {1, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}), + MakeDataDescriptor(50, {1, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}), + MakeDataDescriptor(50, {1, 0, 1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}), + MakeDataDescriptor(11, {}))); + + // These synonyms should not have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}), + MakeDataDescriptor(50, {1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0}), + MakeDataDescriptor(50, {1, 0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 0}), + MakeDataDescriptor(50, {1, 0, 0}))); + + auto transformation_good_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(50, SpvOpStore, 0), 51, 50, 11, {0, 1, 1}); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + // These synonyms should have been added. + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1}), + MakeDataDescriptor(51, {1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 0}), + MakeDataDescriptor(51, {0, 0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 2}), + MakeDataDescriptor(51, {0, 2}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 0}), + MakeDataDescriptor(51, {0, 1, 0}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(51, {0, 1, 1}), + MakeDataDescriptor(11, {}))); + + // These synonyms should not have been added. + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0}), + MakeDataDescriptor(51, {0}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1}), + MakeDataDescriptor(51, {0, 1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 1}), + MakeDataDescriptor(51, {0, 1, 1}))); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b" + OpName %18 "level_1" + OpMemberName %18 0 "b1" + OpMemberName %18 1 "b2" + OpMemberName %18 2 "b3" + OpName %20 "l1" + OpName %25 "level_2" + OpMemberName %25 0 "c1" + OpMemberName %25 1 "c2" + OpName %27 "l2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %18 = OpTypeStruct %12 %12 %12 + %19 = OpTypePointer Function %18 + %25 = OpTypeStruct %18 %18 + %26 = OpTypePointer Function %25 + %31 = OpTypeBool + %32 = OpConstantTrue %31 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %20 = OpVariable %19 Function + %27 = OpVariable %26 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %21 = OpLoad %12 %14 + %22 = OpLoad %12 %14 + %23 = OpLoad %12 %14 + %24 = OpCompositeConstruct %18 %21 %22 %23 + OpStore %20 %24 + %28 = OpLoad %18 %20 + %29 = OpLoad %18 %20 + %30 = OpCompositeConstruct %25 %28 %29 + %50 = OpCompositeInsert %25 %11 %30 1 0 0 + %51 = OpCompositeInsert %25 %11 %50 0 1 1 + OpStore %27 %30 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + OpBranch %34 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationCompositeInsertTest, IdNotAvailableScenarios) { + // This test handles cases where either the composite or the object is not + // available before the |instruction_to_insert_before|. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "base" + OpMemberName %12 0 "a1" + OpMemberName %12 1 "a2" + OpName %14 "b1" + OpName %18 "b2" + OpName %22 "lvl1" + OpMemberName %22 0 "b1" + OpMemberName %22 1 "b2" + OpName %24 "l1" + OpName %28 "i3" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpTypeStruct %6 %6 + %13 = OpTypePointer Function %12 + %22 = OpTypeStruct %12 %12 + %23 = OpTypePointer Function %22 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %18 = OpVariable %13 Function + %24 = OpVariable %23 Function + %28 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + %15 = OpLoad %6 %8 + %16 = OpLoad %6 %10 + %17 = OpCompositeConstruct %12 %15 %16 + OpStore %14 %17 + %19 = OpLoad %6 %10 + %20 = OpLoad %6 %8 + %21 = OpCompositeConstruct %12 %19 %20 + OpStore %18 %21 + %25 = OpLoad %12 %14 + %26 = OpLoad %12 %18 + %27 = OpCompositeConstruct %22 %25 %26 + OpStore %24 %27 + %29 = OpLoad %6 %8 + %30 = OpLoad %6 %10 + %31 = OpIMul %6 %29 %30 + OpStore %28 %31 + %60 = OpCompositeConstruct %12 %20 %19 + %61 = OpCompositeConstruct %22 %26 %25 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: The object with |object_id| is not available at + // |instruction_to_insert_before|. + auto transformation_bad_1 = TransformationCompositeInsert( + MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 27, 60, {1}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The composite with |composite_id| is not available at + // |instruction_to_insert_before|. + auto transformation_bad_2 = TransformationCompositeInsert( + MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 61, 21, {1}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The |instruction_to_insert_before| is the composite itself and is + // available. + auto transformation_bad_3 = TransformationCompositeInsert( + MakeInstructionDescriptor(61, SpvOpCompositeConstruct, 0), 50, 61, 21, + {1}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: The |instruction_to_insert_before| is the object itself and is not + // available. + auto transformation_bad_4 = TransformationCompositeInsert( + MakeInstructionDescriptor(60, SpvOpCompositeConstruct, 0), 50, 27, 60, + {1}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); +} +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp b/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp index 5fa74b70..6fd2ef86 100644 --- a/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp +++ b/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -122,7 +123,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -140,7 +141,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { MakeDataDescriptor(101, {1}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(24, {}), - MakeDataDescriptor(101, {}), context.get()); + MakeDataDescriptor(101, {})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}), @@ -157,7 +158,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {0}), - MakeDataDescriptor(102, {0}), context.get()); + MakeDataDescriptor(102, {0})); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}), @@ -165,7 +166,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {1}), - MakeDataDescriptor(102, {1}), context.get()); + MakeDataDescriptor(102, {1})); TransformationComputeDataSynonymFactClosure(100).Apply( context.get(), &transformation_context); @@ -200,15 +201,15 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}), MakeDataDescriptor(105, {3}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(30, {}), - MakeDataDescriptor(103, {}), context.get()); + MakeDataDescriptor(103, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(33, {}), - MakeDataDescriptor(104, {}), context.get()); + MakeDataDescriptor(104, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {0}), - MakeDataDescriptor(105, {0}), context.get()); + MakeDataDescriptor(105, {0})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {1}), - MakeDataDescriptor(105, {1}), context.get()); + MakeDataDescriptor(105, {1})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {2}), - MakeDataDescriptor(105, {2}), context.get()); + MakeDataDescriptor(105, {2})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}), @@ -233,7 +234,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { MakeDataDescriptor(105, {3}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {3}), - MakeDataDescriptor(105, {3}), context.get()); + MakeDataDescriptor(105, {3})); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}), MakeDataDescriptor(104, {0}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}), @@ -242,15 +243,15 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}), MakeDataDescriptor(100, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {0}), - MakeDataDescriptor(100, {0}), context.get()); + MakeDataDescriptor(100, {0})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {1}), - MakeDataDescriptor(100, {1}), context.get()); + MakeDataDescriptor(100, {1})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {2}), - MakeDataDescriptor(100, {2}), context.get()); + MakeDataDescriptor(100, {2})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {3}), - MakeDataDescriptor(100, {3}), context.get()); + MakeDataDescriptor(100, {3})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {4}), - MakeDataDescriptor(100, {4}), context.get()); + MakeDataDescriptor(100, {4})); TransformationComputeDataSynonymFactClosure(100).Apply( context.get(), &transformation_context); @@ -263,7 +264,7 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}), MakeDataDescriptor(39, {0}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(39, {0}), - MakeDataDescriptor(35, {}), context.get()); + MakeDataDescriptor(35, {})); ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}), MakeDataDescriptor(107, {0}))); ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}), @@ -280,13 +281,13 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}), MakeDataDescriptor(106, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {0}), - MakeDataDescriptor(36, {}), context.get()); + MakeDataDescriptor(36, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {0}), - MakeDataDescriptor(36, {}), context.get()); + MakeDataDescriptor(36, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {1}), - MakeDataDescriptor(37, {}), context.get()); + MakeDataDescriptor(37, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {1}), - MakeDataDescriptor(37, {}), context.get()); + MakeDataDescriptor(37, {})); TransformationComputeDataSynonymFactClosure(100).Apply( context.get(), &transformation_context); @@ -305,13 +306,13 @@ TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) { ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), MakeDataDescriptor(108, {}))); fact_manager.AddFactDataSynonym(MakeDataDescriptor(107, {0}), - MakeDataDescriptor(35, {}), context.get()); + MakeDataDescriptor(35, {})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {0}), - MakeDataDescriptor(108, {0}), context.get()); + MakeDataDescriptor(108, {0})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {1}), - MakeDataDescriptor(108, {1}), context.get()); + MakeDataDescriptor(108, {1})); fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {2}), - MakeDataDescriptor(108, {2}), context.get()); + MakeDataDescriptor(108, {2})); TransformationComputeDataSynonymFactClosure(100).Apply( context.get(), &transformation_context); diff --git a/test/fuzz/transformation_duplicate_region_with_selection_test.cpp b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp new file mode 100644 index 00000000..3a45c993 --- /dev/null +++ b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp @@ -0,0 +1,1686 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_duplicate_region_with_selection.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationDuplicateRegionWithSelectionTest, BasicUseTest) { + // This test handles a case where the ids from the original region are used in + // subsequent block. + + std::string shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "b" + OpName %18 "c" + OpName %20 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %14 = OpConstant %6 2 + %16 = OpTypeBool + %17 = OpTypePointer Function %16 + %19 = OpConstantTrue %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpVariable %17 Function + %20 = OpVariable %7 Function + OpStore %18 %19 + OpStore %20 %14 + %21 = OpFunctionCall %2 %10 %20 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %800 + %800 = OpLabel + %13 = OpLoad %6 %9 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + OpBranch %900 + %900 = OpLabel + %901 = OpIAdd %6 %15 %13 + %902 = OpISub %6 %13 %15 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + std::string expected_shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "b" + OpName %18 "c" + OpName %20 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %14 = OpConstant %6 2 + %16 = OpTypeBool + %17 = OpTypePointer Function %16 + %19 = OpConstantTrue %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpVariable %17 Function + %20 = OpVariable %7 Function + OpStore %18 %19 + OpStore %20 %14 + %21 = OpFunctionCall %2 %10 %20 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %19 %800 %100 + %800 = OpLabel + %13 = OpLoad %6 %9 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + OpBranch %501 + %100 = OpLabel + %201 = OpLoad %6 %9 + %202 = OpIAdd %6 %201 %14 + OpStore %12 %202 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %6 %13 %800 %201 %100 + %302 = OpPhi %6 %15 %800 %202 %100 + OpBranch %900 + %900 = OpLabel + %901 = OpIAdd %6 %302 %301 + %902 = OpISub %6 %301 %302 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, BasicExitBlockTest) { + // This test handles a case where the exit block of the region is the exit + // block of the containing function. + + std::string shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "b" + OpName %18 "c" + OpName %20 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %14 = OpConstant %6 2 + %16 = OpTypeBool + %17 = OpTypePointer Function %16 + %19 = OpConstantTrue %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpVariable %17 Function + %20 = OpVariable %7 Function + OpStore %18 %19 + OpStore %20 %14 + %21 = OpFunctionCall %2 %10 %20 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %800 + %800 = OpLabel + %13 = OpLoad %6 %9 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "b" + OpName %18 "c" + OpName %20 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %14 = OpConstant %6 2 + %16 = OpTypeBool + %17 = OpTypePointer Function %16 + %19 = OpConstantTrue %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpVariable %17 Function + %20 = OpVariable %7 Function + OpStore %18 %19 + OpStore %20 %14 + %21 = OpFunctionCall %2 %10 %20 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %19 %800 %100 + %800 = OpLabel + %13 = OpLoad %6 %9 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + OpBranch %501 + %100 = OpLabel + %201 = OpLoad %6 %9 + %202 = OpIAdd %6 %201 %14 + OpStore %12 %202 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %6 %13 %800 %201 %100 + %302 = OpPhi %6 %15 %800 %202 %100 + OpReturn + OpFunctionEnd + + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest) { + // This test handles few cases where the transformation is not applicable + // because of the control flow graph or layout of the blocks. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %18 "b" + OpName %25 "c" + OpName %27 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 2 + %14 = OpTypeBool + %24 = OpTypePointer Function %14 + %26 = OpConstantTrue %14 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %25 = OpVariable %24 Function + %27 = OpVariable %7 Function + OpStore %25 %26 + OpStore %27 %13 + %28 = OpFunctionCall %2 %10 %27 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %18 = OpVariable %7 Function + %12 = OpLoad %6 %9 + %15 = OpSLessThan %14 %12 %13 + OpSelectionMerge %17 None + OpBranchConditional %15 %16 %21 + %16 = OpLabel + %19 = OpLoad %6 %9 + %20 = OpIAdd %6 %19 %13 + OpStore %18 %20 + OpBranch %17 + %21 = OpLabel + %22 = OpLoad %6 %9 + %23 = OpISub %6 %22 %13 + OpStore %18 %23 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: |entry_block_id| refers to the entry block of the function (this + // transformation currently avoids such cases). + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection( + 500, 26, 501, 11, 11, {{11, 100}}, {{18, 201}, {12, 202}, {15, 203}}, + {{18, 301}, {12, 302}, {15, 303}}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The block with id 16 does not dominate the block with id 21. + TransformationDuplicateRegionWithSelection transformation_bad_2 = + TransformationDuplicateRegionWithSelection( + 500, 26, 501, 16, 21, {{16, 100}, {21, 101}}, + {{19, 201}, {20, 202}, {22, 203}, {23, 204}}, + {{19, 301}, {20, 302}, {22, 303}, {23, 304}}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The block with id 21 does not post-dominate the block with id 11. + TransformationDuplicateRegionWithSelection transformation_bad_3 = + TransformationDuplicateRegionWithSelection( + 500, 26, 501, 11, 21, {{11, 100}, {21, 101}}, + {{18, 201}, {12, 202}, {15, 203}, {22, 204}, {23, 205}}, + {{18, 301}, {12, 302}, {15, 303}, {22, 304}, {23, 305}}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: The block with id 5 is contained in a different function than the + // block with id 11. + TransformationDuplicateRegionWithSelection transformation_bad_4 = + TransformationDuplicateRegionWithSelection( + 500, 26, 501, 5, 11, {{5, 100}, {11, 101}}, + {{25, 201}, {27, 202}, {28, 203}, {18, 204}, {12, 205}, {15, 206}}, + {{25, 301}, {27, 302}, {28, 303}, {18, 304}, {12, 305}, {15, 306}}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableIdTest) { + // This test handles a case where the supplied ids are either not fresh, not + // distinct, not valid in their context or do not refer to the existing + // instructions. + + std::string shader = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "b" + OpName %18 "c" + OpName %20 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %14 = OpConstant %6 2 + %16 = OpTypeBool + %17 = OpTypePointer Function %16 + %19 = OpConstantTrue %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %18 = OpVariable %17 Function + %20 = OpVariable %7 Function + OpStore %18 %19 + OpStore %20 %14 + %21 = OpFunctionCall %2 %10 %20 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %800 + %800 = OpLabel + %13 = OpLoad %6 %9 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: A value in the |original_label_to_duplicate_label| is not a fresh id. + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 21}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: Values in the |original_id_to_duplicate_id| are not distinct. + TransformationDuplicateRegionWithSelection transformation_bad_2 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 201}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: Values in the |original_id_to_phi_id| are not fresh and are not + // distinct with previous values. + TransformationDuplicateRegionWithSelection transformation_bad_3 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 18}, {15, 202}}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: |entry_block_id| does not refer to an existing instruction. + TransformationDuplicateRegionWithSelection transformation_bad_4 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 802, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); + + // Bad: |exit_block_id| does not refer to a block. + TransformationDuplicateRegionWithSelection transformation_bad_5 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 9, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_5.IsApplicable(context.get(), transformation_context)); + + // Bad: |new_entry_fresh_id| is not fresh. + TransformationDuplicateRegionWithSelection transformation_bad_6 = + TransformationDuplicateRegionWithSelection( + 20, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_6.IsApplicable(context.get(), transformation_context)); + + // Bad: |merge_label_fresh_id| is not fresh. + TransformationDuplicateRegionWithSelection transformation_bad_7 = + TransformationDuplicateRegionWithSelection( + 500, 19, 20, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_7.IsApplicable(context.get(), transformation_context)); + + // Bad: Instruction with id 15 is from the original region and is available + // at the end of the region but it is not present in the + // |original_id_to_phi_id|. + TransformationDuplicateRegionWithSelection transformation_bad_8 = + TransformationDuplicateRegionWithSelection( + 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}}); + ASSERT_FALSE( + transformation_bad_8.IsApplicable(context.get(), transformation_context)); + + // Bad: Instruction with id 15 is from the original region but it is + // not present in the |original_id_to_duplicate_id|. + TransformationDuplicateRegionWithSelection transformation_bad_9 = + TransformationDuplicateRegionWithSelection(500, 19, 501, 800, 800, + {{800, 100}}, {{13, 201}}, + {{13, 301}, {15, 302}}); + ASSERT_FALSE( + transformation_bad_9.IsApplicable(context.get(), transformation_context)); + + // Bad: |condition_id| does not refer to the valid instruction. + TransformationDuplicateRegionWithSelection transformation_bad_10 = + TransformationDuplicateRegionWithSelection( + 500, 200, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + + ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(), + transformation_context)); + + // Bad: |condition_id| does not refer to the instruction of type OpTypeBool + TransformationDuplicateRegionWithSelection transformation_bad_11 = + TransformationDuplicateRegionWithSelection( + 500, 14, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}}, + {{13, 301}, {15, 302}}); + + ASSERT_FALSE(transformation_bad_11.IsApplicable(context.get(), + transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest2) { + // This test handles few cases where the transformation is not applicable + // because of the control flow graph or the layout of the blocks. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %12 "i" + OpName %29 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %19 = OpConstant %8 10 + %20 = OpTypeBool + %26 = OpConstant %8 1 + %28 = OpTypePointer Function %20 + %30 = OpConstantTrue %20 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %29 = OpVariable %28 Function + OpStore %29 %30 + %31 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + %12 = OpVariable %9 Function + OpStore %10 %11 + OpStore %12 %11 + OpBranch %13 + %13 = OpLabel + OpLoopMerge %15 %16 None + OpBranch %17 + %17 = OpLabel + %18 = OpLoad %8 %12 + %21 = OpSLessThan %20 %18 %19 + OpBranchConditional %21 %14 %15 + %14 = OpLabel + %22 = OpLoad %8 %10 + %23 = OpLoad %8 %12 + %24 = OpIAdd %8 %22 %23 + OpStore %10 %24 + OpBranch %16 + %16 = OpLabel + OpBranch %50 + %50 = OpLabel + %25 = OpLoad %8 %12 + %27 = OpIAdd %8 %25 %26 + OpStore %12 %27 + OpBranch %13 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + // Bad: The exit block cannot be a header of a loop, because the region won't + // be a single-entry, single-exit region. + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection(500, 30, 501, 13, 13, + {{13, 100}}, {{}}, {{}}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The block with id 13, the loop header, is in the region. The block + // with id 15, the loop merge block, is not in the region. + TransformationDuplicateRegionWithSelection transformation_bad_2 = + TransformationDuplicateRegionWithSelection( + 500, 30, 501, 13, 17, {{13, 100}, {17, 101}}, {{18, 201}, {21, 202}}, + {{18, 301}, {21, 302}}); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The block with id 13, the loop header, is not in the region. The block + // with id 16, the loop continue target, is in the region. + TransformationDuplicateRegionWithSelection transformation_bad_3 = + TransformationDuplicateRegionWithSelection( + 500, 30, 501, 16, 50, {{16, 100}, {50, 101}}, {{25, 201}, {27, 202}}, + {{25, 301}, {27, 302}}); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest3) { + // This test handles a case where for the block which is not the exit block, + // not all successors are in the region. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %14 "a" + OpName %19 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeBool + %9 = OpConstantTrue %8 + %12 = OpTypeInt 32 1 + %13 = OpTypePointer Function %12 + %15 = OpConstant %12 2 + %17 = OpConstant %12 3 + %18 = OpTypePointer Function %8 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %19 = OpVariable %18 Function + OpStore %19 %9 + %20 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %14 = OpVariable %13 Function + OpSelectionMerge %11 None + OpBranchConditional %9 %10 %16 + %10 = OpLabel + OpStore %14 %15 + OpBranch %11 + %16 = OpLabel + OpStore %14 %17 + OpBranch %11 + %11 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + // Bad: The block with id 7, which is not an exit block, has two successors: + // the block with id 10 and the block with id 16. The block with id 16 is not + // in the region. + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection( + 500, 30, 501, 7, 10, {{13, 100}}, {{14, 201}}, {{14, 301}}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, MultipleBlocksLoopTest) { + // This test handles a case where the region consists of multiple blocks + // (they form a loop). The transformation is applicable and the region is + // duplicated. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %12 "i" + OpName %29 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %19 = OpConstant %8 10 + %20 = OpTypeBool + %26 = OpConstant %8 1 + %28 = OpTypePointer Function %20 + %30 = OpConstantTrue %20 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %29 = OpVariable %28 Function + OpStore %29 %30 + %31 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + %12 = OpVariable %9 Function + OpStore %10 %11 + OpStore %12 %11 + OpBranch %50 + %50 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %15 %16 None + OpBranch %17 + %17 = OpLabel + %18 = OpLoad %8 %12 + %21 = OpSLessThan %20 %18 %19 + OpBranchConditional %21 %14 %15 + %14 = OpLabel + %22 = OpLoad %8 %10 + %23 = OpLoad %8 %12 + %24 = OpIAdd %8 %22 %23 + OpStore %10 %24 + OpBranch %16 + %16 = OpLabel + %25 = OpLoad %8 %12 + %27 = OpIAdd %8 %25 %26 + OpStore %12 %27 + OpBranch %13 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 30, 501, 50, 15, + {{50, 100}, {13, 101}, {14, 102}, {15, 103}, {16, 104}, {17, 105}}, + {{22, 201}, + {23, 202}, + {24, 203}, + {25, 204}, + {27, 205}, + {18, 206}, + {21, 207}}, + {{22, 301}, + {23, 302}, + {24, 303}, + {25, 304}, + {27, 305}, + {18, 306}, + {21, 307}}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %12 "i" + OpName %29 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %19 = OpConstant %8 10 + %20 = OpTypeBool + %26 = OpConstant %8 1 + %28 = OpTypePointer Function %20 + %30 = OpConstantTrue %20 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %29 = OpVariable %28 Function + OpStore %29 %30 + %31 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + %12 = OpVariable %9 Function + OpStore %10 %11 + OpStore %12 %11 + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %30 %50 %100 + %50 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %15 %16 None + OpBranch %17 + %17 = OpLabel + %18 = OpLoad %8 %12 + %21 = OpSLessThan %20 %18 %19 + OpBranchConditional %21 %14 %15 + %14 = OpLabel + %22 = OpLoad %8 %10 + %23 = OpLoad %8 %12 + %24 = OpIAdd %8 %22 %23 + OpStore %10 %24 + OpBranch %16 + %16 = OpLabel + %25 = OpLoad %8 %12 + %27 = OpIAdd %8 %25 %26 + OpStore %12 %27 + OpBranch %13 + %15 = OpLabel + OpBranch %501 + %100 = OpLabel + OpBranch %101 + %101 = OpLabel + OpLoopMerge %103 %104 None + OpBranch %105 + %105 = OpLabel + %206 = OpLoad %8 %12 + %207 = OpSLessThan %20 %206 %19 + OpBranchConditional %207 %102 %103 + %102 = OpLabel + %201 = OpLoad %8 %10 + %202 = OpLoad %8 %12 + %203 = OpIAdd %8 %201 %202 + OpStore %10 %203 + OpBranch %104 + %104 = OpLabel + %204 = OpLoad %8 %12 + %205 = OpIAdd %8 %204 %26 + OpStore %12 %205 + OpBranch %101 + %103 = OpLabel + OpBranch %501 + %501 = OpLabel + %306 = OpPhi %8 %18 %15 %206 %103 + %307 = OpPhi %20 %21 %15 %207 %103 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + ResolvingOpPhiExitBlockTest) { + // This test handles a case where the region under the transformation is + // referenced in OpPhi instructions. Since the new merge block becomes the + // exit of the region, these OpPhi instructions need to be updated. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %26 "b" + OpName %29 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %16 = OpTypeBool + %25 = OpTypePointer Function %16 + %27 = OpConstantTrue %16 + %28 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %26 = OpVariable %25 Function + %29 = OpVariable %7 Function + OpStore %26 %27 + OpStore %29 %28 + %30 = OpFunctionCall %2 %10 %29 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpStore %12 %13 + %14 = OpLoad %6 %9 + %17 = OpSLessThan %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %22 + %18 = OpLabel + %20 = OpLoad %6 %9 + %21 = OpIAdd %6 %20 %15 + OpStore %12 %21 + OpBranch %19 + %22 = OpLabel + %23 = OpLoad %6 %9 + %24 = OpIMul %6 %23 %15 + OpStore %12 %24 + OpBranch %19 + %19 = OpLabel + %40 = OpPhi %6 %21 %18 %24 %22 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 27, 501, 22, 22, {{22, 100}}, {{23, 201}, {24, 202}}, + {{23, 301}, {24, 302}}); + + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %26 "b" + OpName %29 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %16 = OpTypeBool + %25 = OpTypePointer Function %16 + %27 = OpConstantTrue %16 + %28 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %26 = OpVariable %25 Function + %29 = OpVariable %7 Function + OpStore %26 %27 + OpStore %29 %28 + %30 = OpFunctionCall %2 %10 %29 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpStore %12 %13 + %14 = OpLoad %6 %9 + %17 = OpSLessThan %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %500 + %18 = OpLabel + %20 = OpLoad %6 %9 + %21 = OpIAdd %6 %20 %15 + OpStore %12 %21 + OpBranch %19 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %27 %22 %100 + %22 = OpLabel + %23 = OpLoad %6 %9 + %24 = OpIMul %6 %23 %15 + OpStore %12 %24 + OpBranch %501 + %100 = OpLabel + %201 = OpLoad %6 %9 + %202 = OpIMul %6 %201 %15 + OpStore %12 %202 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %6 %23 %22 %201 %100 + %302 = OpPhi %6 %24 %22 %202 %100 + OpBranch %19 + %19 = OpLabel + %40 = OpPhi %6 %21 %18 %302 %501 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableEarlyReturn) { + // This test handles a case where one of the blocks has successor outside of + // the region, which has an early return from the function, so that the + // transformation is not applicable. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %27 "b" + OpName %30 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %16 = OpTypeBool + %26 = OpTypePointer Function %16 + %28 = OpConstantTrue %16 + %29 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %27 = OpVariable %26 Function + %30 = OpVariable %7 Function + OpStore %27 %28 + OpStore %30 %29 + %31 = OpFunctionCall %2 %10 %30 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + OpBranch %50 + %50 = OpLabel + OpStore %12 %13 + %14 = OpLoad %6 %9 + %17 = OpSLessThan %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %22 + %18 = OpLabel + %20 = OpLoad %6 %9 + %21 = OpIAdd %6 %20 %15 + OpStore %12 %21 + OpBranch %19 + %22 = OpLabel + OpReturn + %19 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: The block with id 50, which is the entry block, has two successors: + // the block with id 18 and the block with id 22. The block 22 has an early + // return from the function, so that the entry block is not post-dominated by + // the exit block. + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection( + 500, 28, 501, 50, 19, {{50, 100}, {18, 101}, {22, 102}, {19, 103}}, + {{14, 202}, {17, 203}, {20, 204}, {21, 205}}, + {{14, 302}, {17, 303}, {20, 304}, {21, 305}}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + ResolvingOpPhiEntryBlockOnePredecessor) { + // This test handles a case where the entry block has an OpPhi instruction + // referring to its predecessor. After transformation, this instruction needs + // to be updated. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %14 "t" + OpName %20 "b" + OpName %23 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %18 = OpTypeBool + %19 = OpTypePointer Function %18 + %21 = OpConstantTrue %18 + %22 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpVariable %19 Function + %23 = OpVariable %7 Function + OpStore %20 %21 + OpStore %23 %22 + %24 = OpFunctionCall %2 %10 %23 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %14 = OpVariable %7 Function + OpStore %12 %13 + %16 = OpLoad %6 %12 + %17 = OpIMul %6 %15 %16 + OpStore %14 %17 + OpBranch %50 + %50 = OpLabel + %51 = OpPhi %6 %17 %11 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %14 "t" + OpName %20 "b" + OpName %23 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %18 = OpTypeBool + %19 = OpTypePointer Function %18 + %21 = OpConstantTrue %18 + %22 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpVariable %19 Function + %23 = OpVariable %7 Function + OpStore %20 %21 + OpStore %23 %22 + %24 = OpFunctionCall %2 %10 %23 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %14 = OpVariable %7 Function + OpStore %12 %13 + %16 = OpLoad %6 %12 + %17 = OpIMul %6 %15 %16 + OpStore %14 %17 + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %21 %50 %100 + %50 = OpLabel + %51 = OpPhi %6 %17 %500 + OpBranch %501 + %100 = OpLabel + %201 = OpPhi %6 %17 %500 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %6 %51 %50 %201 %100 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + NotApplicableNoVariablePointerCapability) { + // This test handles a case where the transformation would create an OpPhi + // instruction with pointer operands, however there is no cab + // CapabilityVariablePointers. Hence, the transformation is not applicable. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %10 "fun(i1;" + OpName %9 "a" + OpName %12 "s" + OpName %14 "t" + OpName %20 "b" + OpName %23 "param" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %2 %7 + %13 = OpConstant %6 0 + %15 = OpConstant %6 2 + %18 = OpTypeBool + %19 = OpTypePointer Function %18 + %21 = OpConstantTrue %18 + %22 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpVariable %19 Function + %23 = OpVariable %7 Function + OpStore %20 %21 + OpStore %23 %22 + %24 = OpFunctionCall %2 %10 %23 + OpReturn + OpFunctionEnd + %10 = OpFunction %2 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %12 = OpVariable %7 Function + %14 = OpVariable %7 Function + OpStore %12 %13 + %16 = OpLoad %6 %12 + %17 = OpIMul %6 %15 %16 + OpStore %14 %17 + OpBranch %50 + %50 = OpLabel + %51 = OpCopyObject %7 %12 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: There is no required capability CapabilityVariablePointers + TransformationDuplicateRegionWithSelection transformation_bad_1 = + TransformationDuplicateRegionWithSelection( + 500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}}); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + ExitBlockTerminatorOpUnreachable) { + // This test handles a case where the exit block ends with OpUnreachable. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %17 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %13 = OpConstant %8 2 + %15 = OpTypeBool + %16 = OpTypePointer Function %15 + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %17 = OpVariable %16 Function + OpStore %17 %18 + %19 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + OpBranch %50 + %50 = OpLabel + OpStore %10 %11 + %12 = OpLoad %8 %10 + %14 = OpIAdd %8 %12 %13 + OpStore %10 %14 + OpUnreachable + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}}, + {{12, 301}, {14, 302}}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %17 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %13 = OpConstant %8 2 + %15 = OpTypeBool + %16 = OpTypePointer Function %15 + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %17 = OpVariable %16 Function + OpStore %17 %18 + %19 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %18 %50 %100 + %50 = OpLabel + OpStore %10 %11 + %12 = OpLoad %8 %10 + %14 = OpIAdd %8 %12 %13 + OpStore %10 %14 + OpBranch %501 + %100 = OpLabel + OpStore %10 %11 + %201 = OpLoad %8 %10 + %202 = OpIAdd %8 %201 %13 + OpStore %10 %202 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %8 %12 %50 %201 %100 + %302 = OpPhi %8 %14 %50 %202 %100 + OpUnreachable + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + ExitBlockTerminatorOpKill) { + // This test handles a case where the exit block ends with OpKill. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %17 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %13 = OpConstant %8 2 + %15 = OpTypeBool + %16 = OpTypePointer Function %15 + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %17 = OpVariable %16 Function + OpStore %17 %18 + %19 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + OpBranch %50 + %50 = OpLabel + OpStore %10 %11 + %12 = OpLoad %8 %10 + %14 = OpIAdd %8 %12 %13 + OpStore %10 %14 + OpKill + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + TransformationDuplicateRegionWithSelection transformation_good_1 = + TransformationDuplicateRegionWithSelection( + 500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}}, + {{12, 301}, {14, 302}}); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string expected_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %6 "fun(" + OpName %10 "s" + OpName %17 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 0 + %13 = OpConstant %8 2 + %15 = OpTypeBool + %16 = OpTypePointer Function %15 + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %17 = OpVariable %16 Function + OpStore %17 %18 + %19 = OpFunctionCall %2 %6 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + OpBranch %500 + %500 = OpLabel + OpSelectionMerge %501 None + OpBranchConditional %18 %50 %100 + %50 = OpLabel + OpStore %10 %11 + %12 = OpLoad %8 %10 + %14 = OpIAdd %8 %12 %13 + OpStore %10 %14 + OpBranch %501 + %100 = OpLabel + OpStore %10 %11 + %201 = OpLoad %8 %10 + %202 = OpIAdd %8 %201 %13 + OpStore %10 %202 + OpBranch %501 + %501 = OpLabel + %301 = OpPhi %8 %12 %50 %201 %100 + %302 = OpPhi %8 %14 %50 %202 %100 + OpKill + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); +} + +TEST(TransformationDuplicateRegionWithSelectionTest, + ContinueExitBlockNotApplicable) { + // This test handles a case where the exit block is the continue target and + // the transformation is not applicable. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "s" + OpName %10 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %24 = OpConstant %6 5 + %30 = OpConstant %6 1 + %50 = OpConstantTrue %18 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %16 = OpLoad %6 %10 + %19 = OpSLessThan %18 %16 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %20 = OpLoad %6 %10 + %21 = OpLoad %6 %8 + %22 = OpIAdd %6 %21 %20 + OpStore %8 %22 + %23 = OpLoad %6 %10 + %25 = OpIEqual %18 %23 %24 + OpSelectionMerge %27 None + OpBranchConditional %25 %26 %27 + %26 = OpLabel + OpBranch %13 + %27 = OpLabel + OpBranch %14 + %14 = OpLabel + %29 = OpLoad %6 %10 + %31 = OpIAdd %6 %29 %30 + OpStore %10 %31 + OpBranch %11 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationDuplicateRegionWithSelection transformation_bad = + TransformationDuplicateRegionWithSelection( + 500, 50, 501, 27, 14, {{27, 101}, {14, 102}}, {{29, 201}, {31, 202}}, + {{29, 301}, {31, 302}}); + + ASSERT_FALSE( + transformation_bad.IsApplicable(context.get(), transformation_context)); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_equation_instruction_test.cpp b/test/fuzz/transformation_equation_instruction_test.cpp index 1f61c3dc..e6d8b817 100644 --- a/test/fuzz/transformation_equation_instruction_test.cpp +++ b/test/fuzz/transformation_equation_instruction_test.cpp @@ -48,7 +48,7 @@ TEST(TransformationEquationInstructionTest, SignedNegate) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -166,7 +166,7 @@ TEST(TransformationEquationInstructionTest, LogicalNot) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -258,7 +258,7 @@ TEST(TransformationEquationInstructionTest, AddSubNegate1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -382,7 +382,7 @@ TEST(TransformationEquationInstructionTest, AddSubNegate2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -524,7 +524,7 @@ TEST(TransformationEquationInstructionTest, Bitcast) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -649,7 +649,7 @@ TEST(TransformationEquationInstructionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -698,7 +698,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -743,7 +743,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -818,7 +818,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist3) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -892,7 +892,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist4) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -963,7 +963,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist5) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1036,7 +1036,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist6) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1115,7 +1115,7 @@ TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist7) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1189,7 +1189,7 @@ TEST(TransformationEquationInstructionTest, Miscellaneous1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1259,7 +1259,7 @@ TEST(TransformationEquationInstructionTest, Miscellaneous2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1343,7 +1343,7 @@ TEST(TransformationEquationInstructionTest, ConversionInstructions) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1511,7 +1511,7 @@ TEST(TransformationEquationInstructionTest, FloatResultTypeDoesNotExist) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1536,6 +1536,56 @@ TEST(TransformationEquationInstructionTest, FloatResultTypeDoesNotExist) { .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationEquationInstructionTest, HandlesIrrelevantIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %12 "main" + OpExecutionMode %12 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %30 = OpTypeVector %6 3 + %15 = OpConstant %6 24 + %16 = OpConstant %6 37 + %31 = OpConstantComposite %30 %15 %16 %15 + %33 = OpTypeBool + %32 = OpConstantTrue %33 + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto return_instruction = MakeInstructionDescriptor(13, SpvOpReturn, 0); + + // Applicable. + TransformationEquationInstruction transformation(14, SpvOpIAdd, {15, 16}, + return_instruction); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Handles irrelevant ids. + fact_manager.AddFactIdIsIrrelevant(16); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + fact_manager.AddFactIdIsIrrelevant(15); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_flatten_conditional_branch_test.cpp b/test/fuzz/transformation_flatten_conditional_branch_test.cpp new file mode 100644 index 00000000..3327dd6f --- /dev/null +++ b/test/fuzz/transformation_flatten_conditional_branch_test.cpp @@ -0,0 +1,793 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_flatten_conditional_branch.h" + +#include "source/fuzz/counter_overflow_id_source.h" +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +protobufs::SideEffectWrapperInfo MakeSideEffectWrapperInfo( + const protobufs::InstructionDescriptor& instruction, + uint32_t merge_block_id, uint32_t execute_block_id, + uint32_t actual_result_id = 0, uint32_t alternative_block_id = 0, + uint32_t placeholder_result_id = 0, uint32_t value_to_copy_id = 0) { + protobufs::SideEffectWrapperInfo result; + *result.mutable_instruction() = instruction; + result.set_merge_block_id(merge_block_id); + result.set_execute_block_id(execute_block_id); + result.set_actual_result_id(actual_result_id); + result.set_alternative_block_id(alternative_block_id); + result.set_placeholder_result_id(placeholder_result_id); + result.set_value_to_copy_id(value_to_copy_id); + return result; +} + +TEST(TransformationFlattenConditionalBranchTest, Inapplicable) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpTypeInt 32 0 + %8 = OpConstant %7 0 + %9 = OpTypeBool + %10 = OpConstantTrue %9 + %11 = OpTypePointer Function %6 + %12 = OpTypePointer Workgroup %6 + %3 = OpVariable %12 Workgroup + %13 = OpConstant %6 2 + %2 = OpFunction %4 None %5 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + OpSelectionMerge %16 None + OpSwitch %13 %17 2 %18 + %17 = OpLabel + OpBranch %16 + %18 = OpLabel + OpBranch %16 + %16 = OpLabel + OpLoopMerge %19 %16 None + OpBranchConditional %10 %16 %19 + %19 = OpLabel + OpSelectionMerge %20 None + OpBranchConditional %10 %21 %20 + %21 = OpLabel + OpReturn + %20 = OpLabel + OpSelectionMerge %22 None + OpBranchConditional %10 %23 %22 + %23 = OpLabel + OpSelectionMerge %24 None + OpBranchConditional %10 %25 %24 + %25 = OpLabel + OpBranch %24 + %24 = OpLabel + OpBranch %22 + %22 = OpLabel + OpSelectionMerge %26 None + OpBranchConditional %10 %26 %27 + %27 = OpLabel + OpBranch %28 + %28 = OpLabel + OpLoopMerge %29 %28 None + OpBranchConditional %10 %28 %29 + %29 = OpLabel + OpBranch %26 + %26 = OpLabel + OpSelectionMerge %30 None + OpBranchConditional %10 %30 %31 + %31 = OpLabel + OpBranch %32 + %32 = OpLabel + %33 = OpAtomicLoad %6 %3 %8 %8 + OpBranch %30 + %30 = OpLabel + OpSelectionMerge %34 None + OpBranchConditional %10 %35 %34 + %35 = OpLabel + OpMemoryBarrier %8 %8 + OpBranch %34 + %34 = OpLabel + OpLoopMerge %40 %39 None + OpBranchConditional %10 %36 %40 + %36 = OpLabel + OpSelectionMerge %38 None + OpBranchConditional %10 %37 %38 + %37 = OpLabel + OpBranch %40 + %38 = OpLabel + OpBranch %39 + %39 = OpLabel + OpBranch %34 + %40 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Block %15 does not end with OpBranchConditional. + ASSERT_FALSE(TransformationFlattenConditionalBranch(15, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %17 is not a selection header. + ASSERT_FALSE(TransformationFlattenConditionalBranch(17, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %16 is a loop header, not a selection header. + ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %19 and the corresponding merge block do not describe a single-entry, + // single-exit region, because there is a return instruction in %21. + ASSERT_FALSE(TransformationFlattenConditionalBranch(19, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %20 is the header of a construct containing an inner selection + // construct. + ASSERT_FALSE(TransformationFlattenConditionalBranch(20, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %22 is the header of a construct containing an inner loop. + ASSERT_FALSE(TransformationFlattenConditionalBranch(22, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %30 is the header of a construct containing a barrier instruction. + ASSERT_FALSE(TransformationFlattenConditionalBranch(30, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // %33 is not a block. + ASSERT_FALSE(TransformationFlattenConditionalBranch(33, true, {}) + .IsApplicable(context.get(), transformation_context)); + + // Block %36 and the corresponding merge block do not describe a single-entry, + // single-exit region, because block %37 breaks out of the outer loop. + ASSERT_FALSE(TransformationFlattenConditionalBranch(36, true, {}) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationFlattenConditionalBranchTest, Simple) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeBool + %4 = OpConstantTrue %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %2 = OpFunction %5 None %6 + %7 = OpLabel + OpSelectionMerge %8 None + OpBranchConditional %4 %9 %10 + %10 = OpLabel + %26 = OpPhi %3 %4 %7 + OpBranch %8 + %9 = OpLabel + %27 = OpPhi %3 %4 %7 + %11 = OpCopyObject %3 %4 + OpBranch %8 + %8 = OpLabel + %12 = OpPhi %3 %11 %9 %4 %10 + %23 = OpPhi %3 %4 %9 %4 %10 + OpBranch %13 + %13 = OpLabel + %14 = OpCopyObject %3 %4 + OpSelectionMerge %15 None + OpBranchConditional %4 %16 %17 + %16 = OpLabel + %28 = OpPhi %3 %4 %13 + OpBranch %18 + %18 = OpLabel + OpBranch %19 + %17 = OpLabel + %29 = OpPhi %3 %4 %13 + %20 = OpCopyObject %3 %4 + OpBranch %19 + %19 = OpLabel + %21 = OpPhi %3 %4 %18 %20 %17 + OpBranch %15 + %15 = OpLabel + OpSelectionMerge %22 None + OpBranchConditional %4 %22 %22 + %22 = OpLabel + %30 = OpPhi %3 %4 %15 + OpSelectionMerge %25 None + OpBranchConditional %4 %24 %24 + %24 = OpLabel + OpBranch %25 + %25 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation1 = TransformationFlattenConditionalBranch(7, true, {}); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + auto transformation2 = TransformationFlattenConditionalBranch(13, false, {}); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + auto transformation3 = TransformationFlattenConditionalBranch(15, true, {}); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + + auto transformation4 = TransformationFlattenConditionalBranch(22, false, {}); + ASSERT_TRUE( + transformation4.IsApplicable(context.get(), transformation_context)); + transformation4.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeBool + %4 = OpConstantTrue %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %2 = OpFunction %5 None %6 + %7 = OpLabel + OpBranch %9 + %9 = OpLabel + %27 = OpPhi %3 %4 %7 + %11 = OpCopyObject %3 %4 + OpBranch %10 + %10 = OpLabel + %26 = OpPhi %3 %4 %9 + OpBranch %8 + %8 = OpLabel + %12 = OpSelect %3 %4 %11 %4 + %23 = OpSelect %3 %4 %4 %4 + OpBranch %13 + %13 = OpLabel + %14 = OpCopyObject %3 %4 + OpBranch %17 + %17 = OpLabel + %29 = OpPhi %3 %4 %13 + %20 = OpCopyObject %3 %4 + OpBranch %16 + %16 = OpLabel + %28 = OpPhi %3 %4 %17 + OpBranch %18 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %21 = OpSelect %3 %4 %4 %20 + OpBranch %15 + %15 = OpLabel + OpBranch %22 + %22 = OpLabel + %30 = OpPhi %3 %4 %15 + OpBranch %24 + %24 = OpLabel + OpBranch %25 + %25 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %9 = OpTypeVoid + %10 = OpTypeFunction %9 + %11 = OpTypeInt 32 1 + %12 = OpTypeVector %11 4 + %13 = OpTypeFunction %11 + %70 = OpConstant %11 0 + %14 = OpConstant %11 1 + %15 = OpTypeFloat 32 + %16 = OpTypeVector %15 2 + %17 = OpConstant %15 1 + %18 = OpConstantComposite %16 %17 %17 + %19 = OpTypeBool + %20 = OpConstantTrue %19 + %21 = OpTypePointer Function %11 + %22 = OpTypeSampler + %23 = OpTypeImage %9 2D 2 0 0 1 Unknown + %24 = OpTypeSampledImage %23 + %25 = OpTypePointer Function %23 + %26 = OpTypePointer Function %22 + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 2 + %29 = OpTypeArray %11 %28 + %30 = OpTypePointer Function %29 + %2 = OpFunction %9 None %10 + %31 = OpLabel + %4 = OpVariable %21 Function + %5 = OpVariable %30 Function + %32 = OpVariable %25 Function + %33 = OpVariable %26 Function + %34 = OpLoad %23 %32 + %35 = OpLoad %22 %33 + OpSelectionMerge %36 None + OpBranchConditional %20 %37 %36 + %37 = OpLabel + %6 = OpLoad %11 %4 + %7 = OpIAdd %11 %6 %14 + OpStore %4 %7 + OpBranch %36 + %36 = OpLabel + %42 = OpPhi %11 %14 %37 %14 %31 + OpSelectionMerge %43 None + OpBranchConditional %20 %44 %45 + %44 = OpLabel + %8 = OpFunctionCall %11 %3 + OpStore %4 %8 + OpBranch %46 + %45 = OpLabel + %47 = OpAccessChain %21 %5 %14 + OpStore %47 %14 + OpBranch %46 + %46 = OpLabel + OpStore %4 %14 + OpBranch %43 + %43 = OpLabel + OpStore %4 %14 + OpSelectionMerge %48 None + OpBranchConditional %20 %49 %48 + %49 = OpLabel + OpBranch %48 + %48 = OpLabel + OpSelectionMerge %50 None + OpBranchConditional %20 %51 %50 + %51 = OpLabel + %52 = OpSampledImage %24 %34 %35 + %53 = OpLoad %11 %4 + %54 = OpImageSampleImplicitLod %12 %52 %18 + OpBranch %50 + %50 = OpLabel + OpReturn + OpFunctionEnd + %3 = OpFunction %11 None %13 + %55 = OpLabel + OpReturnValue %14 + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + +#ifndef NDEBUG + // The following checks lead to assertion failures, since some entries + // requiring fresh ids are not present in the map, and the transformation + // context does not have a source overflow ids. + + ASSERT_DEATH(TransformationFlattenConditionalBranch(31, true, {}) + .IsApplicable(context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); + + ASSERT_DEATH(TransformationFlattenConditionalBranch( + 31, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101, + 102, 103, 104, 14)}}) + .IsApplicable(context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); +#endif + + // The map maps from an instruction to a list with not enough fresh ids. + ASSERT_FALSE( + TransformationFlattenConditionalBranch( + 31, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101, 102, 103)}}) + .IsApplicable(context.get(), transformation_context)); + + // Not all fresh ids given are distinct. + ASSERT_FALSE(TransformationFlattenConditionalBranch( + 31, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 100, + 102, 103, 104)}}) + .IsApplicable(context.get(), transformation_context)); + + // %48 heads a construct containing an OpSampledImage instruction. + ASSERT_FALSE(TransformationFlattenConditionalBranch( + 48, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(53, SpvOpLoad, 0), 100, 101, + 102, 103, 104)}}) + .IsApplicable(context.get(), transformation_context)); + + // %0 is not a valid id. + ASSERT_FALSE( + TransformationFlattenConditionalBranch( + 31, true, + {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0), + 104, 100, 101, 102, 103, 0), + MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(6, SpvOpStore, 0), 106, 105)}) + .IsApplicable(context.get(), transformation_context)); + + // %17 is a float constant, while %6 has int type. + ASSERT_FALSE( + TransformationFlattenConditionalBranch( + 31, true, + {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0), + 104, 100, 101, 102, 103, 17), + MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(6, SpvOpStore, 0), 106, 105)}) + .IsApplicable(context.get(), transformation_context)); + + auto transformation1 = TransformationFlattenConditionalBranch( + 31, true, + {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0), + 104, 100, 101, 102, 103, 70), + MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpStore, 0), + 106, 105)}); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + // Check that the placeholder id was marked as irrelevant. + ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(103)); + + // Make a new transformation context with a source of overflow ids. + TransformationContext new_transformation_context( + &fact_manager, validator_options, + MakeUnique<CounterOverflowIdSource>(1000)); + + auto transformation2 = TransformationFlattenConditionalBranch( + 36, false, + {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(8, SpvOpStore, 0), + 114, 113)}); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), new_transformation_context)); + transformation2.Apply(context.get(), &new_transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %9 = OpTypeVoid + %10 = OpTypeFunction %9 + %11 = OpTypeInt 32 1 + %12 = OpTypeVector %11 4 + %13 = OpTypeFunction %11 + %70 = OpConstant %11 0 + %14 = OpConstant %11 1 + %15 = OpTypeFloat 32 + %16 = OpTypeVector %15 2 + %17 = OpConstant %15 1 + %18 = OpConstantComposite %16 %17 %17 + %19 = OpTypeBool + %20 = OpConstantTrue %19 + %21 = OpTypePointer Function %11 + %22 = OpTypeSampler + %23 = OpTypeImage %9 2D 2 0 0 1 Unknown + %24 = OpTypeSampledImage %23 + %25 = OpTypePointer Function %23 + %26 = OpTypePointer Function %22 + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 2 + %29 = OpTypeArray %11 %28 + %30 = OpTypePointer Function %29 + %2 = OpFunction %9 None %10 + %31 = OpLabel + %4 = OpVariable %21 Function + %5 = OpVariable %30 Function + %32 = OpVariable %25 Function + %33 = OpVariable %26 Function + %34 = OpLoad %23 %32 + %35 = OpLoad %22 %33 + OpBranch %37 + %37 = OpLabel + OpSelectionMerge %104 None + OpBranchConditional %20 %100 %102 + %100 = OpLabel + %101 = OpLoad %11 %4 + OpBranch %104 + %102 = OpLabel + %103 = OpCopyObject %11 %70 + OpBranch %104 + %104 = OpLabel + %6 = OpPhi %11 %101 %100 %103 %102 + %7 = OpIAdd %11 %6 %14 + OpSelectionMerge %106 None + OpBranchConditional %20 %105 %106 + %105 = OpLabel + OpStore %4 %7 + OpBranch %106 + %106 = OpLabel + OpBranch %36 + %36 = OpLabel + %42 = OpSelect %11 %20 %14 %14 + OpBranch %45 + %45 = OpLabel + %47 = OpAccessChain %21 %5 %14 + OpSelectionMerge %1005 None + OpBranchConditional %20 %1005 %1006 + %1006 = OpLabel + OpStore %47 %14 + OpBranch %1005 + %1005 = OpLabel + OpBranch %44 + %44 = OpLabel + OpSelectionMerge %1000 None + OpBranchConditional %20 %1001 %1003 + %1001 = OpLabel + %1002 = OpFunctionCall %11 %3 + OpBranch %1000 + %1003 = OpLabel + %1004 = OpCopyObject %11 %70 + OpBranch %1000 + %1000 = OpLabel + %8 = OpPhi %11 %1002 %1001 %1004 %1003 + OpSelectionMerge %114 None + OpBranchConditional %20 %113 %114 + %113 = OpLabel + OpStore %4 %8 + OpBranch %114 + %114 = OpLabel + OpBranch %46 + %46 = OpLabel + OpStore %4 %14 + OpBranch %43 + %43 = OpLabel + OpStore %4 %14 + OpSelectionMerge %48 None + OpBranchConditional %20 %49 %48 + %49 = OpLabel + OpBranch %48 + %48 = OpLabel + OpSelectionMerge %50 None + OpBranchConditional %20 %51 %50 + %51 = OpLabel + %52 = OpSampledImage %24 %34 %35 + %53 = OpLoad %11 %4 + %54 = OpImageSampleImplicitLod %12 %52 %18 + OpBranch %50 + %50 = OpLabel + OpReturn + OpFunctionEnd + %3 = OpFunction %11 None %13 + %55 = OpLabel + OpReturnValue %14 + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} // namespace + +TEST(TransformationFlattenConditionalBranchTest, EdgeCases) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeBool + %5 = OpConstantTrue %4 + %6 = OpTypeFunction %3 + %2 = OpFunction %3 None %6 + %7 = OpLabel + OpSelectionMerge %8 None + OpBranchConditional %5 %9 %8 + %9 = OpLabel + %10 = OpFunctionCall %3 %11 + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %5 %13 %12 + %13 = OpLabel + %14 = OpFunctionCall %3 %11 + %15 = OpCopyObject %3 %14 + OpBranch %12 + %12 = OpLabel + OpReturn + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %5 %18 %17 + %18 = OpLabel + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + %11 = OpFunction %3 None %6 + %19 = OpLabel + OpBranch %20 + %20 = OpLabel + OpSelectionMerge %25 None + OpBranchConditional %5 %21 %22 + %21 = OpLabel + OpBranch %22 + %22 = OpLabel + OpSelectionMerge %24 None + OpBranchConditional %5 %24 %23 + %23 = OpLabel + OpBranch %24 + %24 = OpLabel + OpBranch %25 + %25 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + +#ifndef NDEBUG + // The selection construct headed by %7 requires fresh ids because it contains + // a function call. This causes an assertion failure because transformation + // context does not have a source of overflow ids. + ASSERT_DEATH(TransformationFlattenConditionalBranch(7, true, {}) + .IsApplicable(context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); +#endif + + auto transformation1 = TransformationFlattenConditionalBranch( + 7, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(10, SpvOpFunctionCall, 0), 100, 101)}}); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + // The selection construct headed by %8 cannot be flattened because it + // contains a function call returning void, whose result id is used. + ASSERT_FALSE( + TransformationFlattenConditionalBranch( + 7, true, + {{MakeSideEffectWrapperInfo( + MakeInstructionDescriptor(14, SpvOpFunctionCall, 0), 102, 103)}}) + .IsApplicable(context.get(), transformation_context)); + + // Block %16 is unreachable. + ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, {}) + .IsApplicable(context.get(), transformation_context)); + + auto transformation2 = TransformationFlattenConditionalBranch(20, false, {}); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeBool + %5 = OpConstantTrue %4 + %6 = OpTypeFunction %3 + %2 = OpFunction %3 None %6 + %7 = OpLabel + OpBranch %9 + %9 = OpLabel + OpSelectionMerge %100 None + OpBranchConditional %5 %101 %100 + %101 = OpLabel + %10 = OpFunctionCall %3 %11 + OpBranch %100 + %100 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %5 %13 %12 + %13 = OpLabel + %14 = OpFunctionCall %3 %11 + %15 = OpCopyObject %3 %14 + OpBranch %12 + %12 = OpLabel + OpReturn + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %5 %18 %17 + %18 = OpLabel + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd + %11 = OpFunction %3 None %6 + %19 = OpLabel + OpBranch %20 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + OpBranch %22 + %22 = OpLabel + OpSelectionMerge %24 None + OpBranchConditional %5 %24 %23 + %23 = OpLabel + OpBranch %24 + %24 = OpLabel + OpBranch %25 + %25 = OpLabel + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_function_call_test.cpp b/test/fuzz/transformation_function_call_test.cpp index d7305f87..6174d766 100644 --- a/test/fuzz/transformation_function_call_test.cpp +++ b/test/fuzz/transformation_function_call_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_function_call.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -133,7 +134,7 @@ TEST(TransformationFunctionCallTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -446,7 +447,7 @@ TEST(TransformationFunctionCallTest, DoNotInvokeEntryPoint) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_inline_function_test.cpp b/test/fuzz/transformation_inline_function_test.cpp new file mode 100644 index 00000000..f988de98 --- /dev/null +++ b/test/fuzz/transformation_inline_function_test.cpp @@ -0,0 +1,832 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_inline_function.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationInlineFunctionTest, IsApplicable) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %52 "main" + OpExecutionMode %52 OriginUpperLeft + OpName %56 "function_with_void_return" + +; Types + %2 = OpTypeBool + %3 = OpTypeFloat 32 + %4 = OpTypeVector %3 4 + %5 = OpTypePointer Function %4 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %3 %5 %5 + +; Constant scalars + %9 = OpConstant %3 1 + %10 = OpConstant %3 2 + %11 = OpConstant %3 3 + %12 = OpConstant %3 4 + %13 = OpConstant %3 5 + %14 = OpConstant %3 6 + %15 = OpConstant %3 7 + %16 = OpConstant %3 8 + %17 = OpConstantTrue %2 + +; Constant vectors + %18 = OpConstantComposite %4 %9 %10 %11 %12 + %19 = OpConstantComposite %4 %13 %14 %15 %16 + +; function with void return + %20 = OpFunction %6 None %7 + %21 = OpLabel + OpReturn + OpFunctionEnd + +; function with early return + %22 = OpFunction %6 None %7 + %23 = OpLabel + OpSelectionMerge %26 None + OpBranchConditional %17 %24 %25 + %24 = OpLabel + OpReturn + %25 = OpLabel + OpBranch %26 + %26 = OpLabel + OpReturn + OpFunctionEnd + +; function containing an OpKill instruction + %27 = OpFunction %6 None %7 + %28 = OpLabel + OpKill + OpFunctionEnd + +; function containing an OpUnreachable instruction + %29 = OpFunction %6 None %7 + %30 = OpLabel + OpUnreachable + OpFunctionEnd + +; dot product function + %31 = OpFunction %3 None %8 + %32 = OpFunctionParameter %5 + %33 = OpFunctionParameter %5 + %34 = OpLabel + %35 = OpLoad %4 %32 + %36 = OpLoad %4 %33 + %37 = OpCompositeExtract %3 %35 0 + %38 = OpCompositeExtract %3 %36 0 + %39 = OpFMul %3 %37 %38 + %40 = OpCompositeExtract %3 %35 1 + %41 = OpCompositeExtract %3 %36 1 + %42 = OpFMul %3 %40 %41 + %43 = OpCompositeExtract %3 %35 2 + %44 = OpCompositeExtract %3 %36 2 + %45 = OpFMul %3 %43 %44 + %46 = OpCompositeExtract %3 %35 3 + %47 = OpCompositeExtract %3 %36 3 + %48 = OpFMul %3 %46 %47 + %49 = OpFAdd %3 %39 %42 + %50 = OpFAdd %3 %45 %49 + %51 = OpFAdd %3 %48 %50 + OpReturnValue %51 + OpFunctionEnd + +; main function + %52 = OpFunction %6 None %7 + %53 = OpLabel + %54 = OpVariable %5 Function + %55 = OpVariable %5 Function + %56 = OpFunctionCall %6 %20 ; function with void return + OpBranch %57 + %57 = OpLabel + %59 = OpFunctionCall %6 %22 ; function with early return + OpBranch %60 + %60 = OpLabel + %61 = OpFunctionCall %6 %27 ; function containing OpKill + OpBranch %62 + %62 = OpLabel + %63 = OpFunctionCall %6 %29 ; function containing OpUnreachable + OpBranch %64 + %64 = OpLabel + OpStore %54 %18 + OpStore %55 %19 + %65 = OpFunctionCall %3 %31 %54 %55 ; dot product function + OpBranch %66 + %66 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests undefined OpFunctionCall instruction. + auto transformation = TransformationInlineFunction(67, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests false OpFunctionCall instruction. + transformation = TransformationInlineFunction(42, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests use of called function with void return. + transformation = TransformationInlineFunction(56, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function having an early return. + transformation = + TransformationInlineFunction(59, {{24, 67}, {25, 68}, {26, 69}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function containing an OpKill instruction. + transformation = TransformationInlineFunction(61, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests called function containing an OpUnreachable instruction. + transformation = TransformationInlineFunction(63, {}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests applicable transformation. + transformation = TransformationInlineFunction(65, {{35, 67}, + {36, 68}, + {37, 69}, + {38, 70}, + {39, 71}, + {40, 72}, + {41, 73}, + {42, 74}, + {43, 75}, + {44, 76}, + {45, 77}, + {46, 78}, + {47, 79}, + {48, 80}, + {49, 81}, + {50, 82}, + {51, 83}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationInlineFunctionTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %39 "main" + +; Types + %2 = OpTypeFloat 32 + %3 = OpTypeVector %2 4 + %4 = OpTypePointer Function %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeFunction %2 %4 %4 + +; Constant scalars + %8 = OpConstant %2 1 + %9 = OpConstant %2 2 + %10 = OpConstant %2 3 + %11 = OpConstant %2 4 + %12 = OpConstant %2 5 + %13 = OpConstant %2 6 + %14 = OpConstant %2 7 + %15 = OpConstant %2 8 + +; Constant vectors + %16 = OpConstantComposite %3 %8 %9 %10 %11 + %17 = OpConstantComposite %3 %12 %13 %14 %15 + +; dot product function + %18 = OpFunction %2 None %7 + %19 = OpFunctionParameter %4 + %20 = OpFunctionParameter %4 + %21 = OpLabel + %22 = OpLoad %3 %19 + %23 = OpLoad %3 %20 + %24 = OpCompositeExtract %2 %22 0 + %25 = OpCompositeExtract %2 %23 0 + %26 = OpFMul %2 %24 %25 + %27 = OpCompositeExtract %2 %22 1 + %28 = OpCompositeExtract %2 %23 1 + %29 = OpFMul %2 %27 %28 + %30 = OpCompositeExtract %2 %22 2 + %31 = OpCompositeExtract %2 %23 2 + %32 = OpFMul %2 %30 %31 + %33 = OpCompositeExtract %2 %22 3 + %34 = OpCompositeExtract %2 %23 3 + %35 = OpFMul %2 %33 %34 + %36 = OpFAdd %2 %26 %29 + %37 = OpFAdd %2 %32 %36 + %38 = OpFAdd %2 %35 %37 + OpReturnValue %38 + OpFunctionEnd + +; main function + %39 = OpFunction %5 None %6 + %40 = OpLabel + %41 = OpVariable %4 Function + %42 = OpVariable %4 Function + OpStore %41 %16 + OpStore %42 %17 + %43 = OpFunctionCall %2 %18 %41 %42 ; dot product function call + OpBranch %44 + %44 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationInlineFunction(43, {{22, 45}, + {23, 46}, + {24, 47}, + {25, 48}, + {26, 49}, + {27, 50}, + {28, 51}, + {29, 52}, + {30, 53}, + {31, 54}, + {32, 55}, + {33, 56}, + {34, 57}, + {35, 58}, + {36, 59}, + {37, 60}, + {38, 61}}); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %39 "main" + +; Types + %2 = OpTypeFloat 32 + %3 = OpTypeVector %2 4 + %4 = OpTypePointer Function %3 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeFunction %2 %4 %4 + +; Constant scalars + %8 = OpConstant %2 1 + %9 = OpConstant %2 2 + %10 = OpConstant %2 3 + %11 = OpConstant %2 4 + %12 = OpConstant %2 5 + %13 = OpConstant %2 6 + %14 = OpConstant %2 7 + %15 = OpConstant %2 8 + +; Constant vectors + %16 = OpConstantComposite %3 %8 %9 %10 %11 + %17 = OpConstantComposite %3 %12 %13 %14 %15 + +; dot product function + %18 = OpFunction %2 None %7 + %19 = OpFunctionParameter %4 + %20 = OpFunctionParameter %4 + %21 = OpLabel + %22 = OpLoad %3 %19 + %23 = OpLoad %3 %20 + %24 = OpCompositeExtract %2 %22 0 + %25 = OpCompositeExtract %2 %23 0 + %26 = OpFMul %2 %24 %25 + %27 = OpCompositeExtract %2 %22 1 + %28 = OpCompositeExtract %2 %23 1 + %29 = OpFMul %2 %27 %28 + %30 = OpCompositeExtract %2 %22 2 + %31 = OpCompositeExtract %2 %23 2 + %32 = OpFMul %2 %30 %31 + %33 = OpCompositeExtract %2 %22 3 + %34 = OpCompositeExtract %2 %23 3 + %35 = OpFMul %2 %33 %34 + %36 = OpFAdd %2 %26 %29 + %37 = OpFAdd %2 %32 %36 + %38 = OpFAdd %2 %35 %37 + OpReturnValue %38 + OpFunctionEnd + +; main function + %39 = OpFunction %5 None %6 + %40 = OpLabel + %41 = OpVariable %4 Function + %42 = OpVariable %4 Function + OpStore %41 %16 + OpStore %42 %17 + %45 = OpLoad %3 %41 + %46 = OpLoad %3 %42 + %47 = OpCompositeExtract %2 %45 0 + %48 = OpCompositeExtract %2 %46 0 + %49 = OpFMul %2 %47 %48 + %50 = OpCompositeExtract %2 %45 1 + %51 = OpCompositeExtract %2 %46 1 + %52 = OpFMul %2 %50 %51 + %53 = OpCompositeExtract %2 %45 2 + %54 = OpCompositeExtract %2 %46 2 + %55 = OpFMul %2 %53 %54 + %56 = OpCompositeExtract %2 %45 3 + %57 = OpCompositeExtract %2 %46 3 + %58 = OpFMul %2 %56 %57 + %59 = OpFAdd %2 %49 %52 + %60 = OpFAdd %2 %55 %59 + %61 = OpFAdd %2 %58 %60 + %43 = OpCopyObject %2 %61 + OpBranch %44 + %44 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + +TEST(TransformationInlineFunctionTest, ApplyToMultipleFunctions) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %15 "main" + +; Types + %2 = OpTypeInt 32 1 + %3 = OpTypeBool + %4 = OpTypePointer Private %2 + %5 = OpTypePointer Function %2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %2 %5 + %9 = OpTypeFunction %2 %2 + +; Constants + %10 = OpConstant %2 0 + %11 = OpConstant %2 1 + %12 = OpConstant %2 2 + %13 = OpConstant %2 3 + +; Global variable + %14 = OpVariable %4 Private + +; main function + %15 = OpFunction %6 None %7 + %16 = OpLabel + %17 = OpVariable %5 Function + %18 = OpVariable %5 Function + %19 = OpVariable %5 Function + OpStore %17 %13 + %20 = OpLoad %2 %17 + OpStore %18 %20 + %21 = OpFunctionCall %2 %36 %18 + OpBranch %22 + %22 = OpLabel + %23 = OpFunctionCall %2 %36 %18 + OpStore %17 %21 + %24 = OpLoad %2 %17 + %25 = OpFunctionCall %2 %54 %24 + OpBranch %26 + %26 = OpLabel + %27 = OpFunctionCall %2 %54 %24 + %28 = OpLoad %2 %17 + %29 = OpIAdd %2 %28 %25 + OpStore %17 %29 + %30 = OpFunctionCall %6 %67 + OpBranch %31 + %31 = OpLabel + %32 = OpFunctionCall %6 %67 + %33 = OpLoad %2 %14 + %34 = OpLoad %2 %17 + %35 = OpIAdd %2 %34 %33 + OpStore %17 %35 + OpReturn + OpFunctionEnd + +; Function %36 + %36 = OpFunction %2 None %8 + %37 = OpFunctionParameter %5 + %38 = OpLabel + %39 = OpVariable %5 Function + %40 = OpVariable %5 Function + OpStore %39 %10 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %52 %49 None + OpBranch %42 + %42 = OpLabel + %43 = OpLoad %2 %39 + %44 = OpLoad %2 %37 + %45 = OpSLessThan %3 %43 %44 + OpBranchConditional %45 %46 %52 + %46 = OpLabel + %47 = OpLoad %2 %40 + %48 = OpIAdd %2 %47 %11 + OpStore %40 %48 + OpBranch %49 + %49 = OpLabel + %50 = OpLoad %2 %39 + %51 = OpIAdd %2 %50 %12 + OpStore %39 %51 + OpBranch %41 + %52 = OpLabel + %53 = OpLoad %2 %40 + OpReturnValue %53 + OpFunctionEnd + +; Function %54 + %54 = OpFunction %2 None %9 + %55 = OpFunctionParameter %2 + %56 = OpLabel + %57 = OpVariable %5 Function + OpStore %57 %10 + %58 = OpSGreaterThan %3 %55 %10 + OpSelectionMerge %62 None + OpBranchConditional %58 %64 %59 + %59 = OpLabel + %60 = OpLoad %2 %57 + %61 = OpISub %2 %60 %12 + OpStore %57 %61 + OpBranch %62 + %62 = OpLabel + %63 = OpLoad %2 %57 + OpReturnValue %63 + %64 = OpLabel + %65 = OpLoad %2 %57 + %66 = OpIAdd %2 %65 %11 + OpStore %57 %66 + OpBranch %62 + OpFunctionEnd + +; Function %67 + %67 = OpFunction %6 None %7 + %68 = OpLabel + OpStore %14 %12 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationInlineFunction(30, {}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + // Tests a parameter included in the id map. + transformation = TransformationInlineFunction(25, {{55, 69}, + {56, 70}, + {57, 71}, + {58, 72}, + {59, 73}, + {60, 74}, + {61, 75}, + {62, 76}, + {63, 77}, + {64, 78}, + {65, 79}, + {66, 80}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests the id of the returned value not included in the id map. + transformation = TransformationInlineFunction(25, {{56, 69}, + {57, 70}, + {58, 71}, + {59, 72}, + {60, 73}, + {61, 74}, + {62, 75}, + {64, 76}, + {65, 77}, + {66, 78}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationInlineFunction(25, {{57, 69}, + {58, 70}, + {59, 71}, + {60, 72}, + {61, 73}, + {62, 74}, + {63, 75}, + {64, 76}, + {65, 77}, + {66, 78}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationInlineFunction(21, {{39, 79}, + {40, 80}, + {41, 81}, + {42, 82}, + {43, 83}, + {44, 84}, + {45, 85}, + {46, 86}, + {47, 87}, + {48, 88}, + {49, 89}, + {50, 90}, + {51, 91}, + {52, 92}, + {53, 93}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %15 "main" + +; Types + %2 = OpTypeInt 32 1 + %3 = OpTypeBool + %4 = OpTypePointer Private %2 + %5 = OpTypePointer Function %2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeFunction %2 %5 + %9 = OpTypeFunction %2 %2 + +; Constants + %10 = OpConstant %2 0 + %11 = OpConstant %2 1 + %12 = OpConstant %2 2 + %13 = OpConstant %2 3 + +; Global variable + %14 = OpVariable %4 Private + +; main function + %15 = OpFunction %6 None %7 + %16 = OpLabel + %80 = OpVariable %5 Function + %79 = OpVariable %5 Function + %69 = OpVariable %5 Function + %17 = OpVariable %5 Function + %18 = OpVariable %5 Function + %19 = OpVariable %5 Function + OpStore %17 %13 + %20 = OpLoad %2 %17 + OpStore %18 %20 + OpStore %79 %10 + OpBranch %81 + %81 = OpLabel + OpLoopMerge %92 %89 None + OpBranch %82 + %82 = OpLabel + %83 = OpLoad %2 %79 + %84 = OpLoad %2 %18 + %85 = OpSLessThan %3 %83 %84 + OpBranchConditional %85 %86 %92 + %86 = OpLabel + %87 = OpLoad %2 %80 + %88 = OpIAdd %2 %87 %11 + OpStore %80 %88 + OpBranch %89 + %89 = OpLabel + %90 = OpLoad %2 %79 + %91 = OpIAdd %2 %90 %12 + OpStore %79 %91 + OpBranch %81 + %92 = OpLabel + %93 = OpLoad %2 %80 + %21 = OpCopyObject %2 %93 + OpBranch %22 + %22 = OpLabel + %23 = OpFunctionCall %2 %36 %18 + OpStore %17 %21 + %24 = OpLoad %2 %17 + OpStore %69 %10 + %70 = OpSGreaterThan %3 %24 %10 + OpSelectionMerge %74 None + OpBranchConditional %70 %76 %71 + %71 = OpLabel + %72 = OpLoad %2 %69 + %73 = OpISub %2 %72 %12 + OpStore %69 %73 + OpBranch %74 + %74 = OpLabel + %75 = OpLoad %2 %69 + %25 = OpCopyObject %2 %75 + OpBranch %26 + %76 = OpLabel + %77 = OpLoad %2 %69 + %78 = OpIAdd %2 %77 %11 + OpStore %69 %78 + OpBranch %74 + %26 = OpLabel + %27 = OpFunctionCall %2 %54 %24 + %28 = OpLoad %2 %17 + %29 = OpIAdd %2 %28 %25 + OpStore %17 %29 + OpStore %14 %12 + OpBranch %31 + %31 = OpLabel + %32 = OpFunctionCall %6 %67 + %33 = OpLoad %2 %14 + %34 = OpLoad %2 %17 + %35 = OpIAdd %2 %34 %33 + OpStore %17 %35 + OpReturn + OpFunctionEnd + +; Function %36 + %36 = OpFunction %2 None %8 + %37 = OpFunctionParameter %5 + %38 = OpLabel + %39 = OpVariable %5 Function + %40 = OpVariable %5 Function + OpStore %39 %10 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %52 %49 None + OpBranch %42 + %42 = OpLabel + %43 = OpLoad %2 %39 + %44 = OpLoad %2 %37 + %45 = OpSLessThan %3 %43 %44 + OpBranchConditional %45 %46 %52 + %46 = OpLabel + %47 = OpLoad %2 %40 + %48 = OpIAdd %2 %47 %11 + OpStore %40 %48 + OpBranch %49 + %49 = OpLabel + %50 = OpLoad %2 %39 + %51 = OpIAdd %2 %50 %12 + OpStore %39 %51 + OpBranch %41 + %52 = OpLabel + %53 = OpLoad %2 %40 + OpReturnValue %53 + OpFunctionEnd + +; Function %54 + %54 = OpFunction %2 None %9 + %55 = OpFunctionParameter %2 + %56 = OpLabel + %57 = OpVariable %5 Function + OpStore %57 %10 + %58 = OpSGreaterThan %3 %55 %10 + OpSelectionMerge %62 None + OpBranchConditional %58 %64 %59 + %59 = OpLabel + %60 = OpLoad %2 %57 + %61 = OpISub %2 %60 %12 + OpStore %57 %61 + OpBranch %62 + %62 = OpLabel + %63 = OpLoad %2 %57 + OpReturnValue %63 + %64 = OpLabel + %65 = OpLoad %2 %57 + %66 = OpIAdd %2 %65 %11 + OpStore %57 %66 + OpBranch %62 + OpFunctionEnd + +; Function %67 + %67 = OpFunction %6 None %7 + %68 = OpLabel + OpStore %14 %12 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + +TEST(TransformationInlineFunctionTest, HandlesOpPhisInTheSecondBlock) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeInt 32 0 + %11 = OpUndef %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %6 = OpFunctionCall %2 %7 + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + %7 = OpFunction %2 None %3 + %8 = OpLabel + OpBranch %13 + %13 = OpLabel + %12 = OpPhi %10 %11 %8 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationInlineFunction transformation(6, + {{{8, 20}, {13, 21}, {12, 22}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeInt 32 0 + %11 = OpUndef %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %21 + %21 = OpLabel + %22 = OpPhi %10 %11 %5 + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + %7 = OpFunction %2 None %3 + %8 = OpLabel + OpBranch %13 + %13 = OpLabel + %12 = OpPhi %10 %11 %8 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_invert_comparison_operator_test.cpp b/test/fuzz/transformation_invert_comparison_operator_test.cpp index 04684696..83a318ea 100644 --- a/test/fuzz/transformation_invert_comparison_operator_test.cpp +++ b/test/fuzz/transformation_invert_comparison_operator_test.cpp @@ -59,7 +59,7 @@ TEST(TransformationInvertComparisonOperatorTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_load_test.cpp b/test/fuzz/transformation_load_test.cpp index 18ca195e..11d60dd9 100644 --- a/test/fuzz/transformation_load_test.cpp +++ b/test/fuzz/transformation_load_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_load.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -84,7 +85,7 @@ TEST(TransformationLoadTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp b/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp new file mode 100644 index 00000000..b979a398 --- /dev/null +++ b/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp @@ -0,0 +1,365 @@ +// Copyright (c) 2020 André Perez Maselco +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_make_vector_operation_dynamic.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationMakeVectorOperationDynamicTest, IsApplicable) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %22 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeInt 32 0 + %5 = OpTypeFloat 32 + %6 = OpTypeVector %5 2 + %7 = OpTypeVector %5 3 + %8 = OpTypeVector %5 4 + %9 = OpTypeMatrix %6 2 + +; Constant scalars + %10 = OpConstant %4 0 + %11 = OpConstant %4 1 + %12 = OpConstant %4 2 + %13 = OpConstant %5 0 + %14 = OpConstant %5 1 + %15 = OpConstant %5 2 + %16 = OpConstant %5 3 + +; Constant composites + %17 = OpConstantComposite %6 %13 %14 + %18 = OpConstantComposite %6 %15 %16 + %19 = OpConstantComposite %7 %13 %14 %15 + %20 = OpConstantComposite %8 %13 %14 %15 %16 + %21 = OpConstantComposite %9 %17 %18 + +; main function + %22 = OpFunction %2 None %3 + %23 = OpLabel + %24 = OpCompositeExtract %5 %17 0 + %25 = OpCompositeExtract %5 %17 1 + %26 = OpCompositeExtract %5 %18 0 + %27 = OpCompositeExtract %5 %18 1 + %28 = OpCompositeExtract %5 %19 0 + %29 = OpCompositeExtract %5 %19 1 + %30 = OpCompositeExtract %5 %19 2 + %31 = OpCompositeExtract %5 %20 0 + %32 = OpCompositeExtract %5 %20 1 + %33 = OpCompositeExtract %5 %20 2 + %34 = OpCompositeExtract %5 %20 3 + %35 = OpCompositeExtract %6 %21 0 + %36 = OpCompositeExtract %6 %21 1 + %37 = OpCompositeInsert %6 %15 %17 0 + %38 = OpCompositeInsert %6 %16 %17 1 + %39 = OpCompositeInsert %6 %13 %18 0 + %40 = OpCompositeInsert %6 %14 %18 1 + %41 = OpCompositeInsert %7 %13 %19 0 + %42 = OpCompositeInsert %7 %14 %19 1 + %43 = OpCompositeInsert %7 %15 %19 2 + %44 = OpCompositeInsert %8 %13 %20 0 + %45 = OpCompositeInsert %8 %14 %20 1 + %46 = OpCompositeInsert %8 %15 %20 2 + %47 = OpCompositeInsert %8 %16 %20 3 + %48 = OpCompositeInsert %9 %17 %21 0 + %49 = OpCompositeInsert %9 %18 %21 1 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests undefined instruction. + auto transformation = TransformationMakeVectorOperationDynamic(50, 10); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests non-composite instruction. + transformation = TransformationMakeVectorOperationDynamic(23, 11); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests composite being a matrix. + transformation = TransformationMakeVectorOperationDynamic(48, 12); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests literal not defined as constant. + transformation = TransformationMakeVectorOperationDynamic(34, 51); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + // Tests applicable instructions. + transformation = TransformationMakeVectorOperationDynamic(24, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationMakeVectorOperationDynamic(25, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationMakeVectorOperationDynamic(26, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationMakeVectorOperationDynamic(37, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationMakeVectorOperationDynamic(38, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + + transformation = TransformationMakeVectorOperationDynamic(39, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationMakeVectorOperationDynamicTest, Apply) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %20 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeInt 32 0 + %5 = OpTypeFloat 32 + %6 = OpTypeVector %5 2 + %7 = OpTypeVector %5 3 + %8 = OpTypeVector %5 4 + +; Constant scalars + %9 = OpConstant %4 0 + %10 = OpConstant %4 1 + %11 = OpConstant %4 2 + %12 = OpConstant %4 3 + %13 = OpConstant %5 0 + %14 = OpConstant %5 1 + %15 = OpConstant %5 2 + %16 = OpConstant %5 3 + +; Constant vectors + %17 = OpConstantComposite %6 %13 %14 + %18 = OpConstantComposite %7 %13 %14 %15 + %19 = OpConstantComposite %8 %13 %14 %15 %16 + +; main function + %20 = OpFunction %2 None %3 + %21 = OpLabel + %22 = OpCompositeExtract %5 %17 0 + %23 = OpCompositeExtract %5 %17 1 + %24 = OpCompositeExtract %5 %18 0 + %25 = OpCompositeExtract %5 %18 1 + %26 = OpCompositeExtract %5 %18 2 + %27 = OpCompositeExtract %5 %19 0 + %28 = OpCompositeExtract %5 %19 1 + %29 = OpCompositeExtract %5 %19 2 + %30 = OpCompositeExtract %5 %19 3 + %31 = OpCompositeInsert %6 %13 %17 0 + %32 = OpCompositeInsert %6 %14 %17 1 + %33 = OpCompositeInsert %7 %13 %18 0 + %34 = OpCompositeInsert %7 %14 %18 1 + %35 = OpCompositeInsert %7 %15 %18 2 + %36 = OpCompositeInsert %8 %13 %19 0 + %37 = OpCompositeInsert %8 %14 %19 1 + %38 = OpCompositeInsert %8 %15 %19 2 + %39 = OpCompositeInsert %8 %16 %19 3 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = TransformationMakeVectorOperationDynamic(22, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(23, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(24, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(25, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(26, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(27, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(28, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(29, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(30, 12); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(31, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(32, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(33, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(34, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(35, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(36, 9); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(37, 10); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(38, 11); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + transformation = TransformationMakeVectorOperationDynamic(39, 12); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %20 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeInt 32 0 + %5 = OpTypeFloat 32 + %6 = OpTypeVector %5 2 + %7 = OpTypeVector %5 3 + %8 = OpTypeVector %5 4 + +; Constant scalars + %9 = OpConstant %4 0 + %10 = OpConstant %4 1 + %11 = OpConstant %4 2 + %12 = OpConstant %4 3 + %13 = OpConstant %5 0 + %14 = OpConstant %5 1 + %15 = OpConstant %5 2 + %16 = OpConstant %5 3 + +; Constant vectors + %17 = OpConstantComposite %6 %13 %14 + %18 = OpConstantComposite %7 %13 %14 %15 + %19 = OpConstantComposite %8 %13 %14 %15 %16 + +; main function + %20 = OpFunction %2 None %3 + %21 = OpLabel + %22 = OpVectorExtractDynamic %5 %17 %9 + %23 = OpVectorExtractDynamic %5 %17 %10 + %24 = OpVectorExtractDynamic %5 %18 %9 + %25 = OpVectorExtractDynamic %5 %18 %10 + %26 = OpVectorExtractDynamic %5 %18 %11 + %27 = OpVectorExtractDynamic %5 %19 %9 + %28 = OpVectorExtractDynamic %5 %19 %10 + %29 = OpVectorExtractDynamic %5 %19 %11 + %30 = OpVectorExtractDynamic %5 %19 %12 + %31 = OpVectorInsertDynamic %6 %17 %13 %9 + %32 = OpVectorInsertDynamic %6 %17 %14 %10 + %33 = OpVectorInsertDynamic %7 %18 %13 %9 + %34 = OpVectorInsertDynamic %7 %18 %14 %10 + %35 = OpVectorInsertDynamic %7 %18 %15 %11 + %36 = OpVectorInsertDynamic %8 %19 %13 %9 + %37 = OpVectorInsertDynamic %8 %19 %14 %10 + %38 = OpVectorInsertDynamic %8 %19 %15 %11 + %39 = OpVectorInsertDynamic %8 %19 %16 %12 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_merge_blocks_test.cpp b/test/fuzz/transformation_merge_blocks_test.cpp index 4500445b..75e360be 100644 --- a/test/fuzz/transformation_merge_blocks_test.cpp +++ b/test/fuzz/transformation_merge_blocks_test.cpp @@ -44,7 +44,7 @@ TEST(TransformationMergeBlocksTest, BlockDoesNotExist) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -86,7 +86,7 @@ TEST(TransformationMergeBlocksTest, DoNotMergeFirstBlockHasMultipleSuccessors) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -127,7 +127,7 @@ TEST(TransformationMergeBlocksTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -169,7 +169,7 @@ TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockIsSelectionMerge) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -243,7 +243,7 @@ TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockIsLoopMerge) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -322,7 +322,7 @@ TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockIsLoopContinue) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -397,7 +397,7 @@ TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockStartsWithOpPhi) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -478,7 +478,7 @@ TEST(TransformationMergeBlocksTest, BasicMerge) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -570,7 +570,7 @@ TEST(TransformationMergeBlocksTest, MergeWhenSecondBlockIsSelectionHeader) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -661,7 +661,7 @@ TEST(TransformationMergeBlocksTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_move_block_down_test.cpp b/test/fuzz/transformation_move_block_down_test.cpp index 662e88c0..123b5c86 100644 --- a/test/fuzz/transformation_move_block_down_test.cpp +++ b/test/fuzz/transformation_move_block_down_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_move_block_down.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -52,7 +53,7 @@ TEST(TransformationMoveBlockDownTest, NoMovePossible1) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -93,7 +94,7 @@ TEST(TransformationMoveBlockDownTest, NoMovePossible2) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -136,7 +137,7 @@ TEST(TransformationMoveBlockDownTest, NoMovePossible3) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -183,7 +184,7 @@ TEST(TransformationMoveBlockDownTest, NoMovePossible4) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -292,7 +293,7 @@ TEST(TransformationMoveBlockDownTest, ManyMovesPossible) { const auto context = BuildModule(env, consumer, before_transformation, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -709,7 +710,7 @@ TEST(TransformationMoveBlockDownTest, DoNotMoveUnreachable) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_move_instruction_down_test.cpp b/test/fuzz/transformation_move_instruction_down_test.cpp new file mode 100644 index 00000000..7225ee0e --- /dev/null +++ b/test/fuzz/transformation_move_instruction_down_test.cpp @@ -0,0 +1,706 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_move_instruction_down.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationMoveInstructionDownTest, BasicTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %9 = OpConstant %6 0 + %16 = OpTypeBool + %17 = OpConstantFalse %16 + %20 = OpUndef %6 + %13 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %13 Function + %10 = OpIAdd %6 %9 %9 + %11 = OpISub %6 %9 %10 + OpStore %12 %10 + %14 = OpLoad %6 %12 + %15 = OpIMul %6 %9 %14 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %19 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %42 = OpFunctionCall %2 %40 + %22 = OpIAdd %6 %15 %15 + %21 = OpIAdd %6 %15 %15 + OpReturn + OpFunctionEnd + %40 = OpFunction %2 None %3 + %41 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Instruction descriptor is invalid. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(30, SpvOpNop, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Opcode is not supported. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(5, SpvOpLabel, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(12, SpvOpVariable, 0)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(42, SpvOpFunctionCall, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't move the last instruction in the block. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(15, SpvOpBranchConditional, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't move the instruction if the next instruction is the last one in the + // block. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(21, SpvOpIAdd, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't insert instruction's opcode after its successor. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(15, SpvOpIMul, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Instruction's successor depends on the instruction. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(10, SpvOpIAdd, 0)) + .IsApplicable(context.get(), transformation_context)); + + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(11, SpvOpISub, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(22, SpvOpIAdd, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %9 = OpConstant %6 0 + %16 = OpTypeBool + %17 = OpConstantFalse %16 + %20 = OpUndef %6 + %13 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %13 Function + %10 = OpIAdd %6 %9 %9 + OpStore %12 %10 + %11 = OpISub %6 %9 %10 + %14 = OpLoad %6 %12 + %15 = OpIMul %6 %9 %14 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %19 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %42 = OpFunctionCall %2 %40 + %21 = OpIAdd %6 %15 %15 + %22 = OpIAdd %6 %15 %15 + OpReturn + OpFunctionEnd + %40 = OpFunction %2 None %3 + %41 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationMoveInstructionDownTest, HandlesUnsupportedInstructions) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + + ; can swap simple and not supported instructions + %8 = OpCopyObject %6 %7 + %9 = OpFunctionCall %2 %12 + + ; cannot swap memory and not supported instruction + %22 = OpLoad %6 %21 + %23 = OpFunctionCall %2 %12 + + ; cannot swap barrier and not supported instruction + OpMemoryBarrier %7 %7 + %24 = OpFunctionCall %2 %12 + + OpReturn + OpFunctionEnd + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Swap memory instruction with an unsupported one. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(22, SpvOpLoad, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Swap memory barrier with an unsupported one. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(23, SpvOpMemoryBarrier, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Swap simple instruction with an unsupported one. + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(8, SpvOpCopyObject, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + + ; can swap simple and not supported instructions + %9 = OpFunctionCall %2 %12 + %8 = OpCopyObject %6 %7 + + ; cannot swap memory and not supported instruction + %22 = OpLoad %6 %21 + %23 = OpFunctionCall %2 %12 + + ; cannot swap barrier and not supported instruction + OpMemoryBarrier %7 %7 + %24 = OpFunctionCall %2 %12 + + OpReturn + OpFunctionEnd + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationMoveInstructionDownTest, HandlesBarrierInstructions) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + + ; cannot swap two barrier instructions + OpMemoryBarrier %7 %7 + OpMemoryBarrier %7 %7 + + ; cannot swap barrier and memory instructions + OpMemoryBarrier %7 %7 + %22 = OpLoad %6 %21 + OpMemoryBarrier %7 %7 + + ; can swap barrier and simple instructions + %23 = OpCopyObject %6 %7 + OpMemoryBarrier %7 %7 + + OpReturn + OpFunctionEnd + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Swap two barrier instructions. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Swap barrier and memory instructions. + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 2)) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationMoveInstructionDown( + MakeInstructionDescriptor(22, SpvOpLoad, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Swap barrier and simple instructions. + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(23, SpvOpCopyObject, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(22, SpvOpMemoryBarrier, 1)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + ASSERT_TRUE(IsEqual(env, shader, context.get())); +} + +TEST(TransformationMoveInstructionDownTest, HandlesSimpleInstructions) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + + ; can swap simple and barrier instructions + %40 = OpCopyObject %6 %7 + OpMemoryBarrier %7 %7 + + ; can swap simple and memory instructions + %41 = OpCopyObject %6 %7 + %22 = OpLoad %6 %21 + + ; can swap two simple instructions + %23 = OpCopyObject %6 %7 + %42 = OpCopyObject %6 %7 + + OpReturn + OpFunctionEnd + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Swap simple and barrier instructions. + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(40, SpvOpCopyObject, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + // Swap simple and memory instructions. + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(41, SpvOpCopyObject, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(22, SpvOpLoad, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + // Swap two simple instructions. + { + TransformationMoveInstructionDown transformation( + MakeInstructionDescriptor(23, SpvOpCopyObject, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + + ; can swap simple and barrier instructions + %40 = OpCopyObject %6 %7 + OpMemoryBarrier %7 %7 + + ; can swap simple and memory instructions + %41 = OpCopyObject %6 %7 + %22 = OpLoad %6 %21 + + ; can swap two simple instructions + %42 = OpCopyObject %6 %7 + %23 = OpCopyObject %6 %7 + + OpReturn + OpFunctionEnd + %12 = OpFunction %2 None %3 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationMoveInstructionDownTest, HandlesMemoryInstructions) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" + OpExecutionMode %4 LocalSize 16 1 1 + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpConstant %6 2 + %20 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %21 = OpVariable %20 Function %7 + %22 = OpVariable %20 Function %7 + + ; swap R and R instructions + %23 = OpLoad %6 %21 + %24 = OpLoad %6 %22 + + ; swap R and RW instructions + + ; can't swap + %25 = OpLoad %6 %21 + OpCopyMemory %21 %22 + + ; can swap + %26 = OpLoad %6 %21 + OpCopyMemory %22 %21 + + %27 = OpLoad %6 %22 + OpCopyMemory %21 %22 + + %28 = OpLoad %6 %22 + OpCopyMemory %22 %21 + + ; swap R and W instructions + + ; can't swap + %29 = OpLoad %6 %21 + OpStore %21 %7 + + ; can swap + %30 = OpLoad %6 %22 + OpStore %21 %7 + + %31 = OpLoad %6 %21 + OpStore %22 %7 + + %32 = OpLoad %6 %22 + OpStore %22 %7 + + ; swap RW and RW instructions + + ; can't swap + OpCopyMemory %21 %21 + OpCopyMemory %21 %21 + + OpCopyMemory %21 %22 + OpCopyMemory %21 %21 + + OpCopyMemory %21 %21 + OpCopyMemory %21 %22 + + ; can swap + OpCopyMemory %22 %21 + OpCopyMemory %21 %22 + + OpCopyMemory %22 %21 + OpCopyMemory %22 %21 + + OpCopyMemory %21 %22 + OpCopyMemory %21 %22 + + ; swap RW and W instructions + + ; can't swap + OpCopyMemory %21 %21 + OpStore %21 %7 + + OpStore %21 %7 + OpCopyMemory %21 %21 + + ; can swap + OpCopyMemory %22 %21 + OpStore %21 %7 + + OpCopyMemory %21 %22 + OpStore %21 %7 + + OpCopyMemory %21 %21 + OpStore %22 %7 + + ; swap W and W instructions + + ; can't swap + OpStore %21 %7 + OpStore %21 %7 + + ; can swap + OpStore %22 %7 + OpStore %21 %7 + + OpStore %22 %7 + OpStore %22 %7 + + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactValueOfPointeeIsIrrelevant(22); + + // Invalid swaps. + + protobufs::InstructionDescriptor invalid_swaps[] = { + // R and RW + MakeInstructionDescriptor(25, SpvOpLoad, 0), + + // R and W + MakeInstructionDescriptor(29, SpvOpLoad, 0), + + // RW and RW + MakeInstructionDescriptor(32, SpvOpCopyMemory, 0), + MakeInstructionDescriptor(32, SpvOpCopyMemory, 2), + MakeInstructionDescriptor(32, SpvOpCopyMemory, 4), + + // RW and W + MakeInstructionDescriptor(32, SpvOpCopyMemory, 12), + MakeInstructionDescriptor(32, SpvOpStore, 1), + + // W and W + MakeInstructionDescriptor(32, SpvOpStore, 6), + }; + + for (const auto& descriptor : invalid_swaps) { + ASSERT_FALSE(TransformationMoveInstructionDown(descriptor) + .IsApplicable(context.get(), transformation_context)); + } + + // Valid swaps. + protobufs::InstructionDescriptor valid_swaps[] = { + // R and R + MakeInstructionDescriptor(23, SpvOpLoad, 0), + MakeInstructionDescriptor(24, SpvOpLoad, 0), + + // R and RW + MakeInstructionDescriptor(26, SpvOpLoad, 0), + MakeInstructionDescriptor(25, SpvOpCopyMemory, 1), + + MakeInstructionDescriptor(27, SpvOpLoad, 0), + MakeInstructionDescriptor(26, SpvOpCopyMemory, 1), + + MakeInstructionDescriptor(28, SpvOpLoad, 0), + MakeInstructionDescriptor(27, SpvOpCopyMemory, 1), + + // R and W + MakeInstructionDescriptor(30, SpvOpLoad, 0), + MakeInstructionDescriptor(29, SpvOpStore, 1), + + MakeInstructionDescriptor(31, SpvOpLoad, 0), + MakeInstructionDescriptor(30, SpvOpStore, 1), + + MakeInstructionDescriptor(32, SpvOpLoad, 0), + MakeInstructionDescriptor(31, SpvOpStore, 1), + + // RW and RW + MakeInstructionDescriptor(32, SpvOpCopyMemory, 6), + MakeInstructionDescriptor(32, SpvOpCopyMemory, 6), + + MakeInstructionDescriptor(32, SpvOpCopyMemory, 8), + MakeInstructionDescriptor(32, SpvOpCopyMemory, 8), + + MakeInstructionDescriptor(32, SpvOpCopyMemory, 10), + MakeInstructionDescriptor(32, SpvOpCopyMemory, 10), + + // RW and W + MakeInstructionDescriptor(32, SpvOpCopyMemory, 14), + MakeInstructionDescriptor(32, SpvOpStore, 3), + + MakeInstructionDescriptor(32, SpvOpCopyMemory, 15), + MakeInstructionDescriptor(32, SpvOpStore, 4), + + MakeInstructionDescriptor(32, SpvOpCopyMemory, 16), + MakeInstructionDescriptor(32, SpvOpStore, 5), + + // W and W + MakeInstructionDescriptor(32, SpvOpStore, 8), + MakeInstructionDescriptor(32, SpvOpStore, 8), + + MakeInstructionDescriptor(32, SpvOpStore, 10), + MakeInstructionDescriptor(32, SpvOpStore, 10), + }; + + for (const auto& descriptor : valid_swaps) { + TransformationMoveInstructionDown transformation(descriptor); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + ASSERT_TRUE(IsEqual(env, shader, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_mutate_pointer_test.cpp b/test/fuzz/transformation_mutate_pointer_test.cpp new file mode 100644 index 00000000..5c649dfe --- /dev/null +++ b/test/fuzz/transformation_mutate_pointer_test.cpp @@ -0,0 +1,326 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_mutate_pointer.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationMutatePointerTest, BasicTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFloat 32 + %34 = OpConstant %7 0 + %36 = OpConstant %6 0 + %14 = OpTypeVector %7 3 + %35 = OpConstantComposite %14 %34 %34 %34 + %15 = OpTypeMatrix %14 2 + %8 = OpConstant %6 5 + %9 = OpTypeArray %7 %8 + %37 = OpConstantComposite %9 %34 %34 %34 %34 %34 + %11 = OpTypeStruct + %38 = OpConstantComposite %11 + %39 = OpConstantComposite %15 %35 %35 + %31 = OpTypePointer Function %14 + %10 = OpTypeStruct %7 %6 %9 %11 %15 %14 + %40 = OpConstantComposite %10 %34 %36 %37 %38 %39 %35 + %13 = OpTypePointer Function %10 + %16 = OpTypePointer Private %10 + %17 = OpTypePointer Workgroup %10 + %18 = OpTypeStruct %16 + %19 = OpTypePointer Private %18 + %20 = OpVariable %16 Private + %21 = OpVariable %17 Workgroup + %22 = OpVariable %19 Private + %23 = OpTypePointer Output %6 + %24 = OpVariable %23 Output + %27 = OpTypeFunction %2 %13 + %32 = OpUndef %16 + %33 = OpConstantNull %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %28 = OpFunction %2 None %27 + %29 = OpFunctionParameter %13 + %30 = OpLabel + %25 = OpVariable %13 Function + %26 = OpAccessChain %31 %25 %8 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(35); + fact_manager.AddFactIdIsIrrelevant(39); + + const auto insert_before = MakeInstructionDescriptor(26, SpvOpReturn, 0); + + // 20 is not a fresh id. + ASSERT_FALSE(TransformationMutatePointer(20, 20, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |insert_before| instruction descriptor is invalid. + ASSERT_FALSE(TransformationMutatePointer( + 20, 70, MakeInstructionDescriptor(26, SpvOpStore, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Can't insert OpLoad before OpVariable. + ASSERT_FALSE(TransformationMutatePointer( + 20, 70, MakeInstructionDescriptor(26, SpvOpVariable, 0)) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| doesn't exist in the module. + ASSERT_FALSE(TransformationMutatePointer(70, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| doesn't have a type id. + ASSERT_FALSE(TransformationMutatePointer(11, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| is a result id of OpUndef. + ASSERT_FALSE(TransformationMutatePointer(32, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| is a result id of OpConstantNull. + ASSERT_FALSE(TransformationMutatePointer(33, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| is not a pointer instruction. + ASSERT_FALSE(TransformationMutatePointer(8, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| has invalid storage class + ASSERT_FALSE(TransformationMutatePointer(24, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id|'s pointee contains non-scalar and non-composite constituents. + ASSERT_FALSE(TransformationMutatePointer(22, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // There is no irrelevant zero constant to insert into the |pointer_id|. + ASSERT_FALSE(TransformationMutatePointer(20, 70, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // |pointer_id| is not available before |insert_before|. + ASSERT_FALSE(TransformationMutatePointer( + 26, 70, MakeInstructionDescriptor(26, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + fact_manager.AddFactIdIsIrrelevant(40); + + uint32_t fresh_id = 70; + uint32_t pointer_ids[] = { + 20, // Mutate Private variable. + 21, // Mutate Workgroup variable. + 25, // Mutate Function variable. + 29, // Mutate function parameter. + 26, // Mutate OpAccessChain. + }; + + for (auto pointer_id : pointer_ids) { + TransformationMutatePointer transformation(pointer_id, fresh_id++, + insert_before); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFloat 32 + %34 = OpConstant %7 0 + %36 = OpConstant %6 0 + %14 = OpTypeVector %7 3 + %35 = OpConstantComposite %14 %34 %34 %34 + %15 = OpTypeMatrix %14 2 + %8 = OpConstant %6 5 + %9 = OpTypeArray %7 %8 + %37 = OpConstantComposite %9 %34 %34 %34 %34 %34 + %11 = OpTypeStruct + %38 = OpConstantComposite %11 + %39 = OpConstantComposite %15 %35 %35 + %31 = OpTypePointer Function %14 + %10 = OpTypeStruct %7 %6 %9 %11 %15 %14 + %40 = OpConstantComposite %10 %34 %36 %37 %38 %39 %35 + %13 = OpTypePointer Function %10 + %16 = OpTypePointer Private %10 + %17 = OpTypePointer Workgroup %10 + %18 = OpTypeStruct %16 + %19 = OpTypePointer Private %18 + %20 = OpVariable %16 Private + %21 = OpVariable %17 Workgroup + %22 = OpVariable %19 Private + %23 = OpTypePointer Output %6 + %24 = OpVariable %23 Output + %27 = OpTypeFunction %2 %13 + %32 = OpUndef %16 + %33 = OpConstantNull %16 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %28 = OpFunction %2 None %27 + %29 = OpFunctionParameter %13 + %30 = OpLabel + %25 = OpVariable %13 Function + %26 = OpAccessChain %31 %25 %8 + + ; modified Private variable + %70 = OpLoad %10 %20 + OpStore %20 %40 + OpStore %20 %70 + + ; modified Workgroup variable + %71 = OpLoad %10 %21 + OpStore %21 %40 + OpStore %21 %71 + + ; modified Function variable + %72 = OpLoad %10 %25 + OpStore %25 %40 + OpStore %25 %72 + + ; modified function parameter + %73 = OpLoad %10 %29 + OpStore %29 %40 + OpStore %29 %73 + + ; modified OpAccessChain + %74 = OpLoad %14 %26 + OpStore %26 %35 + OpStore %26 %74 + + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationMutatePointerTest, HandlesUnreachableBlocks) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 0 + %8 = OpTypePointer Function %6 + %11 = OpTypePointer Private %6 + %12 = OpVariable %11 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %9 = OpVariable %8 Function + OpReturn + %10 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(7); + + ASSERT_FALSE( + context->GetDominatorAnalysis(context->GetFunction(4))->IsReachable(10)); + + const auto insert_before = MakeInstructionDescriptor(10, SpvOpReturn, 0); + + // Local variable doesn't dominate an unreachable block. + ASSERT_FALSE(TransformationMutatePointer(9, 50, insert_before) + .IsApplicable(context.get(), transformation_context)); + + // Can mutate a global variable in an unreachable block. + TransformationMutatePointer transformation(12, 50, insert_before); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 0 + %8 = OpTypePointer Function %6 + %11 = OpTypePointer Private %6 + %12 = OpVariable %11 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %9 = OpVariable %8 Function + OpReturn + %10 = OpLabel + %50 = OpLoad %6 %12 + OpStore %12 %7 + OpStore %12 %50 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_outline_function_test.cpp b/test/fuzz/transformation_outline_function_test.cpp index ed4fd15e..abf0be3c 100644 --- a/test/fuzz/transformation_outline_function_test.cpp +++ b/test/fuzz/transformation_outline_function_test.cpp @@ -13,6 +13,8 @@ // limitations under the License. #include "source/fuzz/transformation_outline_function.h" + +#include "source/fuzz/counter_overflow_id_source.h" #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -43,7 +45,7 @@ TEST(TransformationOutlineFunctionTest, TrivialOutline) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -108,7 +110,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -165,7 +167,7 @@ TEST(TransformationOutlineFunctionTest, OutlineInterestingControlFlowNoState) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -254,7 +256,7 @@ TEST(TransformationOutlineFunctionTest, OutlineCodeThatGeneratesUnusedIds) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -332,7 +334,7 @@ TEST(TransformationOutlineFunctionTest, OutlineCodeThatGeneratesSingleUsedId) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -431,7 +433,7 @@ TEST(TransformationOutlineFunctionTest, OutlineDiamondThatGeneratesSeveralIds) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -531,7 +533,7 @@ TEST(TransformationOutlineFunctionTest, OutlineCodeThatUsesASingleId) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -609,7 +611,7 @@ TEST(TransformationOutlineFunctionTest, OutlineCodeThatUsesAVariable) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -697,7 +699,7 @@ TEST(TransformationOutlineFunctionTest, OutlineCodeThatUsesAParameter) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -787,7 +789,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -837,7 +839,7 @@ TEST(TransformationOutlineFunctionTest, DoNotOutlineIfRegionInvolvesReturn) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -888,7 +890,7 @@ TEST(TransformationOutlineFunctionTest, DoNotOutlineIfRegionInvolvesKill) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -940,7 +942,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -984,7 +986,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1028,7 +1030,7 @@ TEST(TransformationOutlineFunctionTest, DoNotOutlineIfLoopHeadIsOutsideRegion) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1071,7 +1073,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1116,7 +1118,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1161,7 +1163,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1203,7 +1205,7 @@ TEST(TransformationOutlineFunctionTest, OutlineRegionEndingWithReturnVoid) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1294,7 +1296,7 @@ TEST(TransformationOutlineFunctionTest, OutlineRegionEndingWithReturnValue) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1389,7 +1391,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1479,7 +1481,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1565,7 +1567,7 @@ TEST(TransformationOutlineFunctionTest, DoNotOutlineRegionThatStartsWithOpPhi) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1622,7 +1624,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1679,7 +1681,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1739,7 +1741,7 @@ TEST(TransformationOutlineFunctionTest, DoNotOutlineRegionThatUsesAccessChain) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1801,7 +1803,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1868,7 +1870,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2024,7 +2026,7 @@ TEST(TransformationOutlineFunctionTest, OutlineLivesafe) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2253,7 +2255,7 @@ TEST(TransformationOutlineFunctionTest, OutlineWithDeadBlocks1) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2337,7 +2339,7 @@ TEST(TransformationOutlineFunctionTest, OutlineWithDeadBlocks2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2422,7 +2424,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2498,7 +2500,7 @@ TEST(TransformationOutlineFunctionTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2556,7 +2558,7 @@ TEST(TransformationOutlineFunctionTest, ExitBlockHeadsLoop) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2579,9 +2581,10 @@ TEST(TransformationOutlineFunctionTest, ExitBlockHeadsLoop) { } TEST(TransformationOutlineFunctionTest, Miscellaneous1) { - // This tests outlining of some non-trivial code. + // This tests outlining of some non-trivial code, and also tests the way + // overflow ids are used by the transformation. - std::string shader = R"( + std::string reference_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 @@ -2682,13 +2685,7 @@ TEST(TransformationOutlineFunctionTest, Miscellaneous1) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - ASSERT_TRUE(IsValid(env, context.get())); - - FactManager fact_manager; spvtools::ValidatorOptions validator_options; - TransformationContext transformation_context(&fact_manager, - validator_options); TransformationOutlineFunction transformation( /*entry_block*/ 150, @@ -2702,123 +2699,317 @@ TEST(TransformationOutlineFunctionTest, Miscellaneous1) { /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}}, /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}}); - ASSERT_TRUE( - transformation.IsApplicable(context.get(), transformation_context)); - transformation.Apply(context.get(), &transformation_context); - ASSERT_TRUE(IsValid(env, context.get())); + TransformationOutlineFunction transformation_with_missing_input_id( + /*entry_block*/ 150, + /*exit_block*/ 1001, + /*new_function_struct_return_type_id*/ 200, + /*new_function_type_id*/ 201, + /*new_function_id*/ 202, + /*new_function_region_entry_block*/ 203, + /*new_caller_result_id*/ 204, + /*new_callee_result_id*/ 205, + /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}}, + /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}}); - std::string after_transformation = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" %85 - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 310 - OpName %4 "main" - OpName %28 "buf" - OpMemberName %28 0 "u1" - OpMemberName %28 1 "u2" - OpName %30 "" - OpName %85 "color" - OpMemberDecorate %28 0 Offset 0 - OpMemberDecorate %28 1 Offset 4 - OpDecorate %28 Block - OpDecorate %30 DescriptorSet 0 - OpDecorate %30 Binding 0 - OpDecorate %85 Location 0 - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeFloat 32 - %7 = OpTypeVector %6 4 - %10 = OpConstant %6 1 - %11 = OpConstant %6 2 - %12 = OpConstant %6 3 - %13 = OpConstant %6 4 - %14 = OpConstantComposite %7 %10 %11 %12 %13 - %15 = OpTypeInt 32 1 - %18 = OpConstant %15 0 - %28 = OpTypeStruct %6 %6 - %29 = OpTypePointer Uniform %28 - %30 = OpVariable %29 Uniform - %31 = OpTypePointer Uniform %6 - %35 = OpTypeBool - %39 = OpConstant %15 1 - %84 = OpTypePointer Output %7 - %85 = OpVariable %84 Output - %114 = OpConstant %15 8 - %200 = OpTypeStruct %7 %15 - %201 = OpTypeFunction %200 %15 %7 %15 - %4 = OpFunction %2 None %3 - %5 = OpLabel - OpBranch %22 - %22 = OpLabel - %103 = OpPhi %15 %18 %5 %106 %43 - %102 = OpPhi %7 %14 %5 %107 %43 - %101 = OpPhi %15 %18 %5 %40 %43 - %32 = OpAccessChain %31 %30 %18 - %33 = OpLoad %6 %32 - %34 = OpConvertFToS %15 %33 - %36 = OpSLessThan %35 %101 %34 - OpLoopMerge %24 %43 None - OpBranchConditional %36 %23 %24 - %23 = OpLabel - %40 = OpIAdd %15 %101 %39 - OpBranch %150 - %150 = OpLabel - %204 = OpFunctionCall %200 %202 %103 %102 %40 - %107 = OpCompositeExtract %7 %204 0 - %106 = OpCompositeExtract %15 %204 1 - OpBranch %43 - %43 = OpLabel - OpBranch %22 - %24 = OpLabel - %87 = OpCompositeExtract %6 %102 0 - %91 = OpConvertSToF %6 %103 - %92 = OpCompositeConstruct %7 %87 %11 %91 %10 - OpStore %85 %92 - OpReturn - OpFunctionEnd - %202 = OpFunction %200 None %201 - %301 = OpFunctionParameter %15 - %300 = OpFunctionParameter %7 - %302 = OpFunctionParameter %15 - %203 = OpLabel - OpBranch %41 - %41 = OpLabel - %401 = OpPhi %7 %300 %203 %111 %65 - %400 = OpPhi %15 %301 %203 %110 %65 - %104 = OpPhi %15 %302 %203 %81 %65 - %47 = OpAccessChain %31 %30 %39 - %48 = OpLoad %6 %47 - %49 = OpConvertFToS %15 %48 - %50 = OpSLessThan %35 %104 %49 - OpLoopMerge %1000 %65 None - OpBranchConditional %50 %42 %1000 - %42 = OpLabel - %60 = OpIAdd %15 %400 %114 - %63 = OpSGreaterThan %35 %104 %60 - OpBranchConditional %63 %64 %65 - %64 = OpLabel - %71 = OpCompositeExtract %6 %401 0 - %72 = OpFAdd %6 %71 %11 - %97 = OpCompositeInsert %7 %72 %401 0 - %76 = OpCompositeExtract %6 %401 3 - %77 = OpConvertFToS %15 %76 - %79 = OpIAdd %15 %60 %77 - OpBranch %65 - %65 = OpLabel - %111 = OpPhi %7 %401 %42 %97 %64 - %110 = OpPhi %15 %60 %42 %79 %64 - %81 = OpIAdd %15 %104 %39 - OpBranch %41 - %1000 = OpLabel - OpBranch %1001 - %1001 = OpLabel - %205 = OpCompositeConstruct %200 %401 %400 - OpReturnValue %205 - OpFunctionEnd - )"; - ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + TransformationOutlineFunction transformation_with_missing_output_id( + /*entry_block*/ 150, + /*exit_block*/ 1001, + /*new_function_struct_return_type_id*/ 200, + /*new_function_type_id*/ 201, + /*new_function_id*/ 202, + /*new_function_region_entry_block*/ 203, + /*new_caller_result_id*/ 204, + /*new_callee_result_id*/ 205, + /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}}, + /*output_id_to_fresh_id*/ {{106, 400}}); + + TransformationOutlineFunction + transformation_with_missing_input_and_output_ids( + /*entry_block*/ 150, + /*exit_block*/ 1001, + /*new_function_struct_return_type_id*/ 200, + /*new_function_type_id*/ 201, + /*new_function_id*/ 202, + /*new_function_region_entry_block*/ 203, + /*new_caller_result_id*/ 204, + /*new_callee_result_id*/ 205, + /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}}, + /*output_id_to_fresh_id*/ {{106, 400}}); + + { + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + TransformationContext transformation_context(&fact_manager, + validator_options); + +#ifndef NDEBUG + // We expect the following applicability checks to lead to assertion + // failures since the transformations are missing input or output ids, and + // the transformation context does not have a source of overflow ids. + ASSERT_DEATH(transformation_with_missing_input_id.IsApplicable( + context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); + ASSERT_DEATH(transformation_with_missing_output_id.IsApplicable( + context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); + ASSERT_DEATH(transformation_with_missing_input_and_output_ids.IsApplicable( + context.get(), transformation_context), + "Bad attempt to query whether overflow ids are available."); +#endif + + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %85 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %28 "buf" + OpMemberName %28 0 "u1" + OpMemberName %28 1 "u2" + OpName %30 "" + OpName %85 "color" + OpMemberDecorate %28 0 Offset 0 + OpMemberDecorate %28 1 Offset 4 + OpDecorate %28 Block + OpDecorate %30 DescriptorSet 0 + OpDecorate %30 Binding 0 + OpDecorate %85 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %10 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpConstant %6 3 + %13 = OpConstant %6 4 + %14 = OpConstantComposite %7 %10 %11 %12 %13 + %15 = OpTypeInt 32 1 + %18 = OpConstant %15 0 + %28 = OpTypeStruct %6 %6 + %29 = OpTypePointer Uniform %28 + %30 = OpVariable %29 Uniform + %31 = OpTypePointer Uniform %6 + %35 = OpTypeBool + %39 = OpConstant %15 1 + %84 = OpTypePointer Output %7 + %85 = OpVariable %84 Output + %114 = OpConstant %15 8 + %200 = OpTypeStruct %7 %15 + %201 = OpTypeFunction %200 %15 %7 %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %22 + %22 = OpLabel + %103 = OpPhi %15 %18 %5 %106 %43 + %102 = OpPhi %7 %14 %5 %107 %43 + %101 = OpPhi %15 %18 %5 %40 %43 + %32 = OpAccessChain %31 %30 %18 + %33 = OpLoad %6 %32 + %34 = OpConvertFToS %15 %33 + %36 = OpSLessThan %35 %101 %34 + OpLoopMerge %24 %43 None + OpBranchConditional %36 %23 %24 + %23 = OpLabel + %40 = OpIAdd %15 %101 %39 + OpBranch %150 + %150 = OpLabel + %204 = OpFunctionCall %200 %202 %103 %102 %40 + %107 = OpCompositeExtract %7 %204 0 + %106 = OpCompositeExtract %15 %204 1 + OpBranch %43 + %43 = OpLabel + OpBranch %22 + %24 = OpLabel + %87 = OpCompositeExtract %6 %102 0 + %91 = OpConvertSToF %6 %103 + %92 = OpCompositeConstruct %7 %87 %11 %91 %10 + OpStore %85 %92 + OpReturn + OpFunctionEnd + %202 = OpFunction %200 None %201 + %301 = OpFunctionParameter %15 + %300 = OpFunctionParameter %7 + %302 = OpFunctionParameter %15 + %203 = OpLabel + OpBranch %41 + %41 = OpLabel + %401 = OpPhi %7 %300 %203 %111 %65 + %400 = OpPhi %15 %301 %203 %110 %65 + %104 = OpPhi %15 %302 %203 %81 %65 + %47 = OpAccessChain %31 %30 %39 + %48 = OpLoad %6 %47 + %49 = OpConvertFToS %15 %48 + %50 = OpSLessThan %35 %104 %49 + OpLoopMerge %1000 %65 None + OpBranchConditional %50 %42 %1000 + %42 = OpLabel + %60 = OpIAdd %15 %400 %114 + %63 = OpSGreaterThan %35 %104 %60 + OpBranchConditional %63 %64 %65 + %64 = OpLabel + %71 = OpCompositeExtract %6 %401 0 + %72 = OpFAdd %6 %71 %11 + %97 = OpCompositeInsert %7 %72 %401 0 + %76 = OpCompositeExtract %6 %401 3 + %77 = OpConvertFToS %15 %76 + %79 = OpIAdd %15 %60 %77 + OpBranch %65 + %65 = OpLabel + %111 = OpPhi %7 %401 %42 %97 %64 + %110 = OpPhi %15 %60 %42 %79 %64 + %81 = OpIAdd %15 %104 %39 + OpBranch %41 + %1000 = OpLabel + OpBranch %1001 + %1001 = OpLabel + %205 = OpCompositeConstruct %200 %401 %400 + OpReturnValue %205 + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); + } + + { + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager(context.get()); + TransformationContext new_transformation_context( + &fact_manager, validator_options, + MakeUnique<CounterOverflowIdSource>(2000)); + ASSERT_TRUE(transformation_with_missing_input_id.IsApplicable( + context.get(), new_transformation_context)); + ASSERT_TRUE(transformation_with_missing_output_id.IsApplicable( + context.get(), new_transformation_context)); + ASSERT_TRUE(transformation_with_missing_input_and_output_ids.IsApplicable( + context.get(), new_transformation_context)); + transformation_with_missing_input_and_output_ids.Apply( + context.get(), &new_transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %85 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %28 "buf" + OpMemberName %28 0 "u1" + OpMemberName %28 1 "u2" + OpName %30 "" + OpName %85 "color" + OpMemberDecorate %28 0 Offset 0 + OpMemberDecorate %28 1 Offset 4 + OpDecorate %28 Block + OpDecorate %30 DescriptorSet 0 + OpDecorate %30 Binding 0 + OpDecorate %85 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %10 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpConstant %6 3 + %13 = OpConstant %6 4 + %14 = OpConstantComposite %7 %10 %11 %12 %13 + %15 = OpTypeInt 32 1 + %18 = OpConstant %15 0 + %28 = OpTypeStruct %6 %6 + %29 = OpTypePointer Uniform %28 + %30 = OpVariable %29 Uniform + %31 = OpTypePointer Uniform %6 + %35 = OpTypeBool + %39 = OpConstant %15 1 + %84 = OpTypePointer Output %7 + %85 = OpVariable %84 Output + %114 = OpConstant %15 8 + %200 = OpTypeStruct %7 %15 + %201 = OpTypeFunction %200 %15 %7 %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %22 + %22 = OpLabel + %103 = OpPhi %15 %18 %5 %106 %43 + %102 = OpPhi %7 %14 %5 %107 %43 + %101 = OpPhi %15 %18 %5 %40 %43 + %32 = OpAccessChain %31 %30 %18 + %33 = OpLoad %6 %32 + %34 = OpConvertFToS %15 %33 + %36 = OpSLessThan %35 %101 %34 + OpLoopMerge %24 %43 None + OpBranchConditional %36 %23 %24 + %23 = OpLabel + %40 = OpIAdd %15 %101 %39 + OpBranch %150 + %150 = OpLabel + %204 = OpFunctionCall %200 %202 %103 %102 %40 + %107 = OpCompositeExtract %7 %204 0 + %106 = OpCompositeExtract %15 %204 1 + OpBranch %43 + %43 = OpLabel + OpBranch %22 + %24 = OpLabel + %87 = OpCompositeExtract %6 %102 0 + %91 = OpConvertSToF %6 %103 + %92 = OpCompositeConstruct %7 %87 %11 %91 %10 + OpStore %85 %92 + OpReturn + OpFunctionEnd + %202 = OpFunction %200 None %201 + %2000 = OpFunctionParameter %15 + %300 = OpFunctionParameter %7 + %302 = OpFunctionParameter %15 + %203 = OpLabel + OpBranch %41 + %41 = OpLabel + %2001 = OpPhi %7 %300 %203 %111 %65 + %400 = OpPhi %15 %2000 %203 %110 %65 + %104 = OpPhi %15 %302 %203 %81 %65 + %47 = OpAccessChain %31 %30 %39 + %48 = OpLoad %6 %47 + %49 = OpConvertFToS %15 %48 + %50 = OpSLessThan %35 %104 %49 + OpLoopMerge %1000 %65 None + OpBranchConditional %50 %42 %1000 + %42 = OpLabel + %60 = OpIAdd %15 %400 %114 + %63 = OpSGreaterThan %35 %104 %60 + OpBranchConditional %63 %64 %65 + %64 = OpLabel + %71 = OpCompositeExtract %6 %2001 0 + %72 = OpFAdd %6 %71 %11 + %97 = OpCompositeInsert %7 %72 %2001 0 + %76 = OpCompositeExtract %6 %2001 3 + %77 = OpConvertFToS %15 %76 + %79 = OpIAdd %15 %60 %77 + OpBranch %65 + %65 = OpLabel + %111 = OpPhi %7 %2001 %42 %97 %64 + %110 = OpPhi %15 %60 %42 %79 %64 + %81 = OpIAdd %15 %104 %39 + OpBranch %41 + %1000 = OpLabel + OpBranch %1001 + %1001 = OpLabel + %205 = OpCompositeConstruct %200 %2001 %400 + OpReturnValue %205 + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); + } } TEST(TransformationOutlineFunctionTest, Miscellaneous2) { @@ -2854,7 +3045,7 @@ TEST(TransformationOutlineFunctionTest, Miscellaneous2) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -2913,7 +3104,7 @@ TEST(TransformationOutlineFunctionTest, Miscellaneous3) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -3006,7 +3197,7 @@ TEST(TransformationOutlineFunctionTest, Miscellaneous4) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp index 0439a0f0..dafc6971 100644 --- a/test/fuzz/transformation_permute_function_parameters_test.cpp +++ b/test/fuzz/transformation_permute_function_parameters_test.cpp @@ -253,7 +253,7 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -378,7 +378,6 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) { %17 = OpTypePointer Function %16 %18 = OpTypeFunction %8 %7 %15 %17 %24 = OpTypeBool - %25 = OpTypeFunction %24 %7 %15 %31 = OpConstant %6 255 %33 = OpConstant %6 0 %34 = OpConstant %6 1 @@ -399,7 +398,7 @@ TEST(TransformationPermuteFunctionParametersTest, BasicTest) { %223 = OpTypeFunction %2 %6 %8 %224 = OpTypeFunction %2 %8 %6 %233 = OpTypeFunction %2 %42 %24 - %234 = OpTypeFunction %2 %24 %42 + %25 = OpTypeFunction %24 %7 %15 %107 = OpTypeFunction %2 %16 %14 %4 = OpFunction %2 None %3 %5 = OpLabel diff --git a/test/fuzz/transformation_permute_phi_operands_test.cpp b/test/fuzz/transformation_permute_phi_operands_test.cpp index c0a428a8..99fb9908 100644 --- a/test/fuzz/transformation_permute_phi_operands_test.cpp +++ b/test/fuzz/transformation_permute_phi_operands_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_permute_phi_operands.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -67,7 +68,7 @@ TEST(TransformationPermutePhiOperandsTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_propagate_instruction_up_test.cpp b/test/fuzz/transformation_propagate_instruction_up_test.cpp new file mode 100644 index 00000000..23f0482f --- /dev/null +++ b/test/fuzz/transformation_propagate_instruction_up_test.cpp @@ -0,0 +1,895 @@ +// Copyright (c) 2020 Vasyl Teliman +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_propagate_instruction_up.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationPropagateInstructionUpTest, BasicTest) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %27 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %26 = OpVariable %27 Function + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + OpBranch %15 + + %15 = OpLabel + %21 = OpPhi %6 %18 %14 %22 %19 + %23 = OpFMul %6 %21 %21 + %24 = OpFDiv %6 %21 %23 + OpBranch %25 + + %25 = OpLabel + %28 = OpPhi %6 %20 %15 + OpStore %26 %28 + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // |block_id| is invalid. + ASSERT_FALSE(TransformationPropagateInstructionUp(40, {{}}).IsApplicable( + context.get(), transformation_context)); + ASSERT_FALSE(TransformationPropagateInstructionUp(26, {{}}).IsApplicable( + context.get(), transformation_context)); + + // |block_id| has no predecessors. + ASSERT_FALSE(TransformationPropagateInstructionUp(5, {{}}).IsApplicable( + context.get(), transformation_context)); + + // |block_id| has no valid instructions to propagate. + ASSERT_FALSE(TransformationPropagateInstructionUp(25, {{{15, 40}}}) + .IsApplicable(context.get(), transformation_context)); + + // Not all predecessors have fresh ids. + ASSERT_FALSE(TransformationPropagateInstructionUp(15, {{{19, 40}, {40, 41}}}) + .IsApplicable(context.get(), transformation_context)); + + // Not all ids are fresh. + ASSERT_FALSE( + TransformationPropagateInstructionUp(15, {{{19, 40}, {14, 14}, {40, 42}}}) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationPropagateInstructionUp(15, {{{19, 19}, {14, 40}, {40, 42}}}) + .IsApplicable(context.get(), transformation_context)); + + // Fresh ids have duplicates. + ASSERT_FALSE( + TransformationPropagateInstructionUp(15, {{{19, 40}, {14, 40}, {19, 41}}}) + .IsApplicable(context.get(), transformation_context)); + + // Valid transformations. + { + TransformationPropagateInstructionUp transformation(14, {{{5, 40}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + { + TransformationPropagateInstructionUp transformation(19, {{{5, 41}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %27 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %26 = OpVariable %27 Function + %13 = OpFOrdEqual %12 %9 %11 + %40 = OpFMod %6 %9 %17 ; propagated from %14 + %41 = OpFAdd %6 %11 %20 ; propagated from %19 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpPhi %6 %40 %5 ; propagated into %5 + OpBranch %15 + + %19 = OpLabel + %22 = OpPhi %6 %41 %5 ; propagated into %5 + OpBranch %15 + + %15 = OpLabel + %21 = OpPhi %6 %18 %14 %22 %19 + %23 = OpFMul %6 %21 %21 + %24 = OpFDiv %6 %21 %23 + OpBranch %25 + + %25 = OpLabel + %28 = OpPhi %6 %20 %15 + OpStore %26 %28 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + { + TransformationPropagateInstructionUp transformation(15, + {{{14, 43}, {19, 44}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %27 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %26 = OpVariable %27 Function + %13 = OpFOrdEqual %12 %9 %11 + %40 = OpFMod %6 %9 %17 + %41 = OpFAdd %6 %11 %20 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpPhi %6 %40 %5 + %43 = OpFMul %6 %18 %18 ; propagated from %15 + OpBranch %15 + + %19 = OpLabel + %22 = OpPhi %6 %41 %5 + %44 = OpFMul %6 %22 %22 ; propagated from %15 + OpBranch %15 + + %15 = OpLabel + %23 = OpPhi %6 %43 %14 %44 %19 ; propagated into %14 and %19 + %21 = OpPhi %6 %18 %14 %22 %19 + %24 = OpFDiv %6 %21 %23 + OpBranch %25 + + %25 = OpLabel + %28 = OpPhi %6 %20 %15 + OpStore %26 %28 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + { + TransformationPropagateInstructionUp transformation(15, + {{{14, 45}, {19, 46}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %27 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %26 = OpVariable %27 Function + %13 = OpFOrdEqual %12 %9 %11 + %40 = OpFMod %6 %9 %17 + %41 = OpFAdd %6 %11 %20 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpPhi %6 %40 %5 + %43 = OpFMul %6 %18 %18 + %45 = OpFDiv %6 %18 %43 ; propagated from %15 + OpBranch %15 + + %19 = OpLabel + %22 = OpPhi %6 %41 %5 + %44 = OpFMul %6 %22 %22 + %46 = OpFDiv %6 %22 %44 ; propagated from %15 + OpBranch %15 + + %15 = OpLabel + %24 = OpPhi %6 %45 %14 %46 %19 ; propagated into %14 and %19 + %23 = OpPhi %6 %43 %14 %44 %19 + %21 = OpPhi %6 %18 %14 %22 %19 + OpBranch %25 + + %25 = OpLabel + %28 = OpPhi %6 %20 %15 + OpStore %26 %28 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor1) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + OpBranch %15 + + %15 = OpLabel ; dominates %26 + %21 = OpPhi %6 %18 %14 %22 %19 %28 %26 + %23 = OpFMul %6 %21 %21 + %24 = OpFDiv %6 %21 %23 + OpLoopMerge %27 %26 None + OpBranch %26 + + %26 = OpLabel + %28 = OpFAdd %6 %24 %23 + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationPropagateInstructionUp transformation( + 15, {{{14, 40}, {19, 41}, {26, 42}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + %40 = OpFMul %6 %18 %18 ; propagated from %15 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + %41 = OpFMul %6 %22 %22 ; propagated from %15 + OpBranch %15 + + %15 = OpLabel + %23 = OpPhi %6 %40 %14 %41 %19 %42 %26 ; propagated into %14, %19, %26 + %21 = OpPhi %6 %18 %14 %22 %19 %28 %26 + %24 = OpFDiv %6 %21 %23 + OpLoopMerge %27 %26 None + OpBranch %26 + + %26 = OpLabel + %28 = OpFAdd %6 %24 %23 + %42 = OpFMul %6 %28 %28 ; propagated from %15 + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor2) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + OpBranch %15 + + %15 = OpLabel ; doesn't dominate %26 + %21 = OpPhi %6 %18 %14 %22 %19 %20 %26 + %23 = OpFMul %6 %21 %21 + %24 = OpFDiv %6 %21 %23 + OpLoopMerge %27 %26 None + OpBranch %27 + + %26 = OpLabel + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationPropagateInstructionUp transformation( + 15, {{{14, 40}, {19, 41}, {26, 42}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + %40 = OpFMul %6 %18 %18 ; propagated from %15 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + %41 = OpFMul %6 %22 %22 ; propagated from %15 + OpBranch %15 + + %15 = OpLabel + %23 = OpPhi %6 %40 %14 %41 %19 %42 %26 ; propagated into %14, %19, %26 + %21 = OpPhi %6 %18 %14 %22 %19 %20 %26 + %24 = OpFDiv %6 %21 %23 + OpLoopMerge %27 %26 None + OpBranch %27 + + %26 = OpLabel + %42 = OpFMul %6 %20 %20 ; propagated from %15 + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor3) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + OpBranch %15 + + %15 = OpLabel ; branches to itself + %21 = OpPhi %6 %18 %14 %22 %19 %24 %15 + %23 = OpFMul %6 %21 %21 + %24 = OpFDiv %6 %21 %23 + OpLoopMerge %27 %15 None + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationPropagateInstructionUp transformation( + 15, {{{14, 40}, {19, 41}, {15, 42}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3.5 + %11 = OpConstant %6 3.4000001 + %12 = OpTypeBool + %17 = OpConstant %6 4 + %20 = OpConstant %6 45 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %13 = OpFOrdEqual %12 %9 %11 + OpSelectionMerge %15 None + OpBranchConditional %13 %14 %19 + + %14 = OpLabel + %18 = OpFMod %6 %9 %17 + %40 = OpFMul %6 %18 %18 ; propagated from %15 + OpBranch %15 + + %19 = OpLabel + %22 = OpFAdd %6 %11 %20 + %41 = OpFMul %6 %22 %22 ; propagated from %15 + OpBranch %15 + + %15 = OpLabel + %23 = OpPhi %6 %40 %14 %41 %19 %42 %15 ; propagated into %14, %19, %15 + %21 = OpPhi %6 %18 %14 %22 %19 %24 %15 + %24 = OpFDiv %6 %21 %23 + %42 = OpFMul %6 %24 %24 ; propagated from %15 + OpLoopMerge %27 %15 None + OpBranch %15 + + %27 = OpLabel + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, + HandlesVariablePointersCapability) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %7 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %9 + + %9 = OpLabel + %10 = OpCopyObject %7 %8 + OpStore %10 %11 + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Required capabilities haven't yet been specified. + TransformationPropagateInstructionUp transformation(9, {{{5, 40}}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + context->AddCapability(SpvCapabilityVariablePointers); + + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + OpCapability VariablePointers + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %7 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %8 = OpVariable %7 Function + %40 = OpCopyObject %7 %8 ; propagated from %9 + OpBranch %9 + + %9 = OpLabel + %10 = OpPhi %7 %40 %5 ; propagated into %5 + OpStore %10 %11 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, + HandlesVariablePointersStorageBufferCapability) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %7 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %8 = OpVariable %7 Function + OpBranch %9 + + %9 = OpLabel + %10 = OpCopyObject %7 %8 + OpStore %10 %11 + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Required capabilities haven't yet been specified + TransformationPropagateInstructionUp transformation(9, {{{5, 40}}}); + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + + context->AddCapability(SpvCapabilityVariablePointersStorageBuffer); + + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + OpCapability VariablePointersStorageBuffer + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %7 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %8 = OpVariable %7 Function + %40 = OpCopyObject %7 %8 ; propagated from %9 + OpBranch %9 + + %9 = OpLabel + %10 = OpPhi %7 %40 %5 ; propagated into %5 + OpStore %10 %11 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationPropagateInstructionUpTest, MultipleIdenticalPredecessors) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %12 = OpTypeBool + %13 = OpConstantTrue %12 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %13 %9 %9 + + %9 = OpLabel + %14 = OpPhi %6 %11 %5 + %10 = OpCopyObject %6 %14 + OpReturn + + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationPropagateInstructionUp transformation(9, {{{5, 40}}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %11 = OpConstant %6 23 + %12 = OpTypeBool + %13 = OpConstantTrue %12 + %4 = OpFunction %2 None %3 + + %5 = OpLabel + %40 = OpCopyObject %6 %11 + OpSelectionMerge %9 None + OpBranchConditional %13 %9 %9 + + %9 = OpLabel + %10 = OpPhi %6 %40 %5 + %14 = OpPhi %6 %11 %5 + OpReturn + + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_push_id_through_variable_test.cpp b/test/fuzz/transformation_push_id_through_variable_test.cpp index 9e4c0fd7..cc003a61 100644 --- a/test/fuzz/transformation_push_id_through_variable_test.cpp +++ b/test/fuzz/transformation_push_id_through_variable_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_push_id_through_variable.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -95,7 +96,7 @@ TEST(TransformationPushIdThroughVariableTest, IsApplicable) { const auto context = BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -326,7 +327,7 @@ TEST(TransformationPushIdThroughVariableTest, Apply) { const auto context = BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -497,6 +498,210 @@ TEST(TransformationPushIdThroughVariableTest, Apply) { MakeDataDescriptor(108, {}))); } +TEST(TransformationPushIdThroughVariableTest, AddSynonymsForRelevantIds) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %92 %52 %53 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpDecorate %92 BuiltIn FragCoord + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFloat 32 + %8 = OpTypeStruct %6 %7 + %9 = OpTypePointer Function %8 + %10 = OpTypeFunction %6 %9 + %14 = OpConstant %6 0 + %15 = OpTypePointer Function %6 + %51 = OpTypePointer Private %6 + %21 = OpConstant %6 2 + %23 = OpConstant %6 1 + %24 = OpConstant %7 1 + %25 = OpTypePointer Function %7 + %50 = OpTypePointer Private %7 + %34 = OpTypeBool + %35 = OpConstantFalse %34 + %60 = OpConstantNull %50 + %61 = OpUndef %51 + %52 = OpVariable %50 Private + %53 = OpVariable %51 Private + %80 = OpConstantComposite %8 %21 %24 + %90 = OpTypeVector %7 4 + %91 = OpTypePointer Input %90 + %92 = OpVariable %91 Input + %93 = OpConstantComposite %90 %24 %24 %24 %24 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpVariable %9 Function + %27 = OpVariable %9 Function + %22 = OpAccessChain %15 %20 %14 + %44 = OpCopyObject %9 %20 + %26 = OpAccessChain %25 %20 %23 + %29 = OpFunctionCall %6 %12 %27 + %30 = OpAccessChain %15 %20 %14 + %45 = OpCopyObject %15 %30 + %81 = OpCopyObject %9 %27 + %33 = OpAccessChain %15 %20 %14 + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %37 + %36 = OpLabel + %38 = OpAccessChain %15 %20 %14 + %40 = OpAccessChain %15 %20 %14 + %43 = OpAccessChain %15 %20 %14 + %82 = OpCopyObject %9 %27 + OpBranch %37 + %37 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %6 None %10 + %11 = OpFunctionParameter %9 + %13 = OpLabel + %46 = OpCopyObject %9 %11 + %16 = OpAccessChain %15 %11 %14 + %95 = OpCopyObject %8 %80 + OpReturnValue %21 + %100 = OpLabel + OpUnreachable + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests the reference shader validity. + ASSERT_TRUE(IsValid(env, context.get())); + + uint32_t value_id = 21; + uint32_t value_synonym_id = 62; + uint32_t variable_id = 63; + uint32_t initializer_id = 23; + uint32_t variable_storage_class = SpvStorageClassPrivate; + auto instruction_descriptor = + MakeInstructionDescriptor(95, SpvOpReturnValue, 0); + auto transformation = TransformationPushIdThroughVariable( + value_id, value_synonym_id, variable_id, variable_storage_class, + initializer_id, instruction_descriptor); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}), + MakeDataDescriptor(62, {}))); +} + +TEST(TransformationPushIdThroughVariableTest, DontAddSynonymsForIrrelevantIds) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %92 %52 %53 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpDecorate %92 BuiltIn FragCoord + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFloat 32 + %8 = OpTypeStruct %6 %7 + %9 = OpTypePointer Function %8 + %10 = OpTypeFunction %6 %9 + %14 = OpConstant %6 0 + %15 = OpTypePointer Function %6 + %51 = OpTypePointer Private %6 + %21 = OpConstant %6 2 + %23 = OpConstant %6 1 + %24 = OpConstant %7 1 + %25 = OpTypePointer Function %7 + %50 = OpTypePointer Private %7 + %34 = OpTypeBool + %35 = OpConstantFalse %34 + %60 = OpConstantNull %50 + %61 = OpUndef %51 + %52 = OpVariable %50 Private + %53 = OpVariable %51 Private + %80 = OpConstantComposite %8 %21 %24 + %90 = OpTypeVector %7 4 + %91 = OpTypePointer Input %90 + %92 = OpVariable %91 Input + %93 = OpConstantComposite %90 %24 %24 %24 %24 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpVariable %9 Function + %27 = OpVariable %9 Function + %22 = OpAccessChain %15 %20 %14 + %44 = OpCopyObject %9 %20 + %26 = OpAccessChain %25 %20 %23 + %29 = OpFunctionCall %6 %12 %27 + %30 = OpAccessChain %15 %20 %14 + %45 = OpCopyObject %15 %30 + %81 = OpCopyObject %9 %27 + %33 = OpAccessChain %15 %20 %14 + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %37 + %36 = OpLabel + %38 = OpAccessChain %15 %20 %14 + %40 = OpAccessChain %15 %20 %14 + %43 = OpAccessChain %15 %20 %14 + %82 = OpCopyObject %9 %27 + OpBranch %37 + %37 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %6 None %10 + %11 = OpFunctionParameter %9 + %13 = OpLabel + %46 = OpCopyObject %9 %11 + %16 = OpAccessChain %15 %11 %14 + %95 = OpCopyObject %8 %80 + OpReturnValue %21 + %100 = OpLabel + OpUnreachable + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Tests the reference shader validity. + ASSERT_TRUE(IsValid(env, context.get())); + + fact_manager.AddFactIdIsIrrelevant(21); + + uint32_t value_id = 21; + uint32_t value_synonym_id = 62; + uint32_t variable_id = 63; + uint32_t initializer_id = 23; + uint32_t variable_storage_class = SpvStorageClassPrivate; + auto instruction_descriptor = + MakeInstructionDescriptor(95, SpvOpReturnValue, 0); + auto transformation = TransformationPushIdThroughVariable( + value_id, value_synonym_id, variable_id, variable_storage_class, + initializer_id, instruction_descriptor); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}), + MakeDataDescriptor(62, {}))); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_record_synonymous_constants_test.cpp b/test/fuzz/transformation_record_synonymous_constants_test.cpp index e50a63d1..ea46774d 100644 --- a/test/fuzz/transformation_record_synonymous_constants_test.cpp +++ b/test/fuzz/transformation_record_synonymous_constants_test.cpp @@ -84,19 +84,25 @@ TEST(TransformationRecordSynonymousConstantsTest, IntConstants) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); ASSERT_TRUE(IsValid(env, context.get())); +#ifndef NDEBUG // %3 is not a constant declaration - ASSERT_FALSE(TransformationRecordSynonymousConstants(3, 9).IsApplicable( - context.get(), transformation_context)); + ASSERT_DEATH(TransformationRecordSynonymousConstants(3, 9).IsApplicable( + context.get(), transformation_context), + "The ids must refer to constants."); +#endif - // Swapping the ids gives the same result - ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 3).IsApplicable( - context.get(), transformation_context)); +#ifndef NDEBUG + // %3 is not a constant declaration + ASSERT_DEATH(TransformationRecordSynonymousConstants(9, 3).IsApplicable( + context.get(), transformation_context), + "The ids must refer to constants."); +#endif // The two constants must be different ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 9).IsApplicable( @@ -129,14 +135,12 @@ TEST(TransformationRecordSynonymousConstantsTest, IntConstants) { ApplyTransformationAndCheckFactManager(13, 22, context.get(), &transformation_context); - // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3536): - // Relax type check for integers. Uncomment this code once the issue is fixed. - // // %13 and %20 are equal even if %13 is signed and %20 is unsigned - // ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 20).IsApplicable( - // context.get(), transformation_context)); - // - // ApplyTransformationAndCheckFactManager(13, 20, context.get(), - // &transformation_context); + // %13 and %20 are equal even if %13 is signed and %20 is unsigned + ASSERT_TRUE(TransformationRecordSynonymousConstants(13, 20).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(13, 20, context.get(), + &transformation_context); // %9 and %11 are equivalent (OpConstant with value 0 and OpConstantNull) ASSERT_TRUE(TransformationRecordSynonymousConstants(9, 11).IsApplicable( @@ -197,7 +201,7 @@ TEST(TransformationRecordSynonymousConstantsTest, BoolConstants) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -289,7 +293,7 @@ TEST(TransformationRecordSynonymousConstantsTest, FloatConstants) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -395,7 +399,7 @@ TEST(TransformationRecordSynonymousConstantsTest, const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -532,7 +536,7 @@ TEST(TransformationRecordSynonymousConstantsTest, StructCompositeConstants) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -630,7 +634,7 @@ TEST(TransformationRecordSynonymousConstantsTest, ArrayCompositeConstants) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -680,6 +684,213 @@ TEST(TransformationRecordSynonymousConstantsTest, ArrayCompositeConstants) { context.get(), transformation_context)); } +TEST(TransformationRecordSynonymousConstantsTest, IntVectors) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpDecorate %3 Location 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpTypeInt 32 0 + %8 = OpTypeVector %6 4 + %9 = OpTypeVector %7 4 + %10 = OpTypePointer Function %8 + %11 = OpTypePointer Function %8 + %12 = OpConstant %6 0 + %13 = OpConstant %7 0 + %14 = OpConstant %6 1 + %25 = OpConstant %7 1 + %15 = OpConstantComposite %8 %12 %12 %12 %12 + %16 = OpConstantComposite %9 %13 %13 %13 %13 + %17 = OpConstantComposite %8 %14 %12 %12 %14 + %18 = OpConstantComposite %9 %25 %13 %13 %25 + %19 = OpConstantNull %8 + %20 = OpConstantNull %9 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %2 = OpFunction %4 None %5 + %24 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(IsValid(env, context.get())); + + // %15 and %17 are not equivalent (having non-equivalent components) + ASSERT_FALSE(TransformationRecordSynonymousConstants(15, 17).IsApplicable( + context.get(), transformation_context)); + + // %17 and %19 are not equivalent (%19 is null, %17 is non-zero) + ASSERT_FALSE(TransformationRecordSynonymousConstants(17, 19).IsApplicable( + context.get(), transformation_context)); + + // %17 and %20 are not equivalent (%19 is null, %20 is non-zero) + ASSERT_FALSE(TransformationRecordSynonymousConstants(17, 20).IsApplicable( + context.get(), transformation_context)); + + // %15 and %16 are equivalent (having pairwise equivalent components) + ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 16).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(15, 16, context.get(), + &transformation_context); + + // %17 and %18 are equivalent (having pairwise equivalent components) + ASSERT_TRUE(TransformationRecordSynonymousConstants(17, 18).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(17, 18, context.get(), + &transformation_context); + + // %19 and %20 are equivalent (both null vectors with compatible types) + ASSERT_TRUE(TransformationRecordSynonymousConstants(19, 20).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(19, 20, context.get(), + &transformation_context); + + // %15 and %19 are equivalent (they have compatible types, %15 is zero-like + // and %19 is null) + ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 19).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(15, 19, context.get(), + &transformation_context); + + // %15 and %20 are equivalent (they have compatible types, %15 is zero-like + // and %20 is null) + ASSERT_TRUE(TransformationRecordSynonymousConstants(15, 20).IsApplicable( + context.get(), transformation_context)); + + ApplyTransformationAndCheckFactManager(15, 20, context.get(), + &transformation_context); +} + +TEST(TransformationRecordSynonymousConstantsTest, FirstIrrelevantConstant) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpConstant %6 23 + %8 = OpConstant %6 23 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(TransformationRecordSynonymousConstants(7, 8).IsApplicable( + context.get(), transformation_context)); + + fact_manager.AddFactIdIsIrrelevant(7); + ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable( + context.get(), transformation_context)); +} + +TEST(TransformationRecordSynonymousConstantsTest, SecondIrrelevantConstant) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpConstant %6 23 + %8 = OpConstant %6 23 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_TRUE(TransformationRecordSynonymousConstants(7, 8).IsApplicable( + context.get(), transformation_context)); + + fact_manager.AddFactIdIsIrrelevant(8); + ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable( + context.get(), transformation_context)); +} + +TEST(TransformationRecordSynonymousConstantsTest, InvalidIds) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpConstant %6 23 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable( + context.get(), transformation_context)); + + ASSERT_FALSE(TransformationRecordSynonymousConstants(8, 7).IsApplicable( + context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp new file mode 100644 index 00000000..c4e8da23 --- /dev/null +++ b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp @@ -0,0 +1,609 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h" + +#include "source/fuzz/fuzzer_util.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest, + NotApplicableBasicChecks) { + // First conditions in IsApplicable() are checked. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %12 "i3" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + %13 = OpLoad %6 %10 + %14 = OpLoad %6 %8 + %15 = OpSDiv %6 %13 %14 + OpStore %12 %15 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: |struct_fresh_id| must be fresh. + auto transformation_bad_1 = + TransformationReplaceAddSubMulWithCarryingExtended(14, 15); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to an instruction OpSDiv. + auto transformation_bad_2 = + TransformationReplaceAddSubMulWithCarryingExtended(20, 15); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to an nonexistent instruction. + auto transformation_bad_3 = + TransformationReplaceAddSubMulWithCarryingExtended(20, 21); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest, + NotApplicableDifferingSignedTypes) { + // Operand types and result types do not match. Not applicable to an operation + // on vectors with signed integers and operation on signed integers. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "i1" + OpName %10 "i2" + OpName %16 "v1" + OpName %20 "v2" + OpName %25 "v3" + OpName %31 "u1" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %14 = OpTypeVector %6 3 + %15 = OpTypePointer Function %14 + %17 = OpConstant %6 0 + %18 = OpConstant %6 2 + %19 = OpConstantComposite %14 %17 %9 %18 + %21 = OpConstant %6 3 + %22 = OpConstant %6 4 + %23 = OpConstant %6 5 + %24 = OpConstantComposite %14 %21 %22 %23 + %29 = OpTypeInt 32 0 + %30 = OpTypePointer Function %29 + %32 = OpConstant %29 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %16 = OpVariable %15 Function + %20 = OpVariable %15 Function + %25 = OpVariable %15 Function + %31 = OpVariable %30 Function + OpStore %8 %9 + %11 = OpLoad %6 %8 + %12 = OpLoad %6 %8 + %13 = OpISub %6 %11 %12 + OpStore %10 %13 + OpStore %16 %19 + OpStore %20 %24 + %26 = OpLoad %14 %16 + %27 = OpLoad %14 %20 + %28 = OpIAdd %14 %26 %27 + OpStore %25 %28 + OpStore %31 %32 + %40 = OpIMul %6 %32 %18 + %41 = OpIAdd %6 %32 %32 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Bad: The transformation cannot be applied to an instruction OpIMul that has + // different signedness of the types of operands. + auto transformation_bad_1 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 40); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to an instruction OpIAdd that has + // different signedness of the result type than the signedness of the types of + // the operands. + auto transformation_bad_2 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 41); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to the instruction OpIAdd of two + // vectors that have signed components. + auto transformation_bad_3 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 28); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to the instruction OpISub of two + // signed integers + auto transformation_bad_4 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 13); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest, + NotApplicableMissingStructTypes) { + // In all cases the required struct types are missing. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "u1" + OpName %10 "u2" + OpName %12 "u3" + OpName %24 "i1" + OpName %26 "i2" + OpName %28 "i3" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %22 = OpTypeInt 32 1 + %23 = OpTypePointer Function %22 + %25 = OpConstant %22 1 + %27 = OpConstant %22 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + %24 = OpVariable %23 Function + %26 = OpVariable %23 Function + %28 = OpVariable %23 Function + OpStore %8 %9 + OpStore %10 %11 + %13 = OpLoad %6 %8 + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + %16 = OpLoad %6 %8 + %17 = OpLoad %6 %10 + %18 = OpISub %6 %16 %17 + OpStore %12 %18 + %19 = OpLoad %6 %8 + %20 = OpLoad %6 %10 + %21 = OpIMul %6 %19 %20 + OpStore %12 %21 + OpStore %24 %25 + OpStore %26 %27 + %29 = OpLoad %22 %24 + %30 = OpLoad %22 %26 + %31 = OpIMul %22 %29 %30 + OpStore %28 %31 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation_bad_1 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 15); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + auto transformation_bad_2 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 18); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to the instruction OpIAdd of two + // vectors that have signed components. + auto transformation_bad_3 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 21); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: The transformation cannot be applied to the instruction OpISub of two + // signed integers + auto transformation_bad_4 = + TransformationReplaceAddSubMulWithCarryingExtended(50, 31); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest, + ApplicableScenarios) { + // In this test all of the transformations can be applied. The required struct + // types are provided. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "u1" + OpName %10 "u2" + OpName %12 "u3" + OpName %24 "i1" + OpName %26 "i2" + OpName %28 "i3" + OpName %34 "uv1" + OpName %36 "uv2" + OpName %39 "uv3" + OpName %51 "v1" + OpName %53 "v2" + OpName %56 "v3" + OpName %60 "pair_uint" + OpMemberName %60 0 "u_1" + OpMemberName %60 1 "u_2" + OpName %62 "p_uint" + OpName %63 "pair_uvec2" + OpMemberName %63 0 "uv_1" + OpMemberName %63 1 "uv_2" + OpName %65 "p_uvec2" + OpName %66 "pair_ivec2" + OpMemberName %66 0 "v_1" + OpMemberName %66 1 "v_2" + OpName %68 "p_ivec2" + OpName %69 "pair_int" + OpMemberName %69 0 "i_1" + OpMemberName %69 1 "i_2" + OpName %71 "p_int" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %22 = OpTypeInt 32 1 + %23 = OpTypePointer Function %22 + %25 = OpConstant %22 1 + %27 = OpConstant %22 2 + %32 = OpTypeVector %6 2 + %33 = OpTypePointer Function %32 + %35 = OpConstantComposite %32 %9 %11 + %37 = OpConstant %6 3 + %38 = OpConstantComposite %32 %11 %37 + %49 = OpTypeVector %22 2 + %50 = OpTypePointer Function %49 + %52 = OpConstantComposite %49 %25 %27 + %54 = OpConstant %22 3 + %55 = OpConstantComposite %49 %27 %54 + %60 = OpTypeStruct %6 %6 + %61 = OpTypePointer Private %60 + %62 = OpVariable %61 Private + %63 = OpTypeStruct %32 %32 + %64 = OpTypePointer Private %63 + %65 = OpVariable %64 Private + %66 = OpTypeStruct %49 %49 + %67 = OpTypePointer Private %66 + %68 = OpVariable %67 Private + %69 = OpTypeStruct %22 %22 + %70 = OpTypePointer Private %69 + %71 = OpVariable %70 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + %24 = OpVariable %23 Function + %26 = OpVariable %23 Function + %28 = OpVariable %23 Function + %34 = OpVariable %33 Function + %36 = OpVariable %33 Function + %39 = OpVariable %33 Function + %51 = OpVariable %50 Function + %53 = OpVariable %50 Function + %56 = OpVariable %50 Function + OpStore %8 %9 + OpStore %10 %11 + %13 = OpLoad %6 %8 + %14 = OpLoad %6 %10 + %15 = OpIAdd %6 %13 %14 + OpStore %12 %15 + %16 = OpLoad %6 %8 + %17 = OpLoad %6 %10 + %18 = OpISub %6 %16 %17 + OpStore %12 %18 + %19 = OpLoad %6 %8 + %20 = OpLoad %6 %10 + %21 = OpIMul %6 %19 %20 + OpStore %12 %21 + OpStore %24 %25 + OpStore %26 %27 + %29 = OpLoad %22 %24 + %30 = OpLoad %22 %26 + %31 = OpIMul %22 %29 %30 + OpStore %28 %31 + OpStore %34 %35 + OpStore %36 %38 + %40 = OpLoad %32 %34 + %41 = OpLoad %32 %36 + %42 = OpIAdd %32 %40 %41 + OpStore %39 %42 + %43 = OpLoad %32 %34 + %44 = OpLoad %32 %36 + %45 = OpISub %32 %43 %44 + OpStore %39 %45 + %46 = OpLoad %32 %34 + %47 = OpLoad %32 %36 + %48 = OpIMul %32 %46 %47 + OpStore %39 %48 + OpStore %51 %52 + OpStore %53 %55 + %57 = OpLoad %49 %51 + %58 = OpLoad %49 %53 + %59 = OpIMul %49 %57 %58 + OpStore %56 %59 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation_good_1 = + TransformationReplaceAddSubMulWithCarryingExtended(80, 15); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_2 = + TransformationReplaceAddSubMulWithCarryingExtended(81, 18); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_3 = + TransformationReplaceAddSubMulWithCarryingExtended(82, 21); + ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(), + transformation_context)); + transformation_good_3.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_4 = + TransformationReplaceAddSubMulWithCarryingExtended(83, 31); + ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(), + transformation_context)); + transformation_good_4.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_5 = + TransformationReplaceAddSubMulWithCarryingExtended(84, 42); + ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(), + transformation_context)); + transformation_good_5.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_6 = + TransformationReplaceAddSubMulWithCarryingExtended(85, 45); + ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(), + transformation_context)); + transformation_good_6.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_7 = + TransformationReplaceAddSubMulWithCarryingExtended(86, 48); + ASSERT_TRUE(transformation_good_7.IsApplicable(context.get(), + transformation_context)); + transformation_good_7.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_8 = + TransformationReplaceAddSubMulWithCarryingExtended(87, 59); + ASSERT_TRUE(transformation_good_8.IsApplicable(context.get(), + transformation_context)); + transformation_good_8.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "u1" + OpName %10 "u2" + OpName %12 "u3" + OpName %24 "i1" + OpName %26 "i2" + OpName %28 "i3" + OpName %34 "uv1" + OpName %36 "uv2" + OpName %39 "uv3" + OpName %51 "v1" + OpName %53 "v2" + OpName %56 "v3" + OpName %60 "pair_uint" + OpMemberName %60 0 "u_1" + OpMemberName %60 1 "u_2" + OpName %62 "p_uint" + OpName %63 "pair_uvec2" + OpMemberName %63 0 "uv_1" + OpMemberName %63 1 "uv_2" + OpName %65 "p_uvec2" + OpName %66 "pair_ivec2" + OpMemberName %66 0 "v_1" + OpMemberName %66 1 "v_2" + OpName %68 "p_ivec2" + OpName %69 "pair_int" + OpMemberName %69 0 "i_1" + OpMemberName %69 1 "i_2" + OpName %71 "p_int" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 1 + %11 = OpConstant %6 2 + %22 = OpTypeInt 32 1 + %23 = OpTypePointer Function %22 + %25 = OpConstant %22 1 + %27 = OpConstant %22 2 + %32 = OpTypeVector %6 2 + %33 = OpTypePointer Function %32 + %35 = OpConstantComposite %32 %9 %11 + %37 = OpConstant %6 3 + %38 = OpConstantComposite %32 %11 %37 + %49 = OpTypeVector %22 2 + %50 = OpTypePointer Function %49 + %52 = OpConstantComposite %49 %25 %27 + %54 = OpConstant %22 3 + %55 = OpConstantComposite %49 %27 %54 + %60 = OpTypeStruct %6 %6 + %61 = OpTypePointer Private %60 + %62 = OpVariable %61 Private + %63 = OpTypeStruct %32 %32 + %64 = OpTypePointer Private %63 + %65 = OpVariable %64 Private + %66 = OpTypeStruct %49 %49 + %67 = OpTypePointer Private %66 + %68 = OpVariable %67 Private + %69 = OpTypeStruct %22 %22 + %70 = OpTypePointer Private %69 + %71 = OpVariable %70 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + %24 = OpVariable %23 Function + %26 = OpVariable %23 Function + %28 = OpVariable %23 Function + %34 = OpVariable %33 Function + %36 = OpVariable %33 Function + %39 = OpVariable %33 Function + %51 = OpVariable %50 Function + %53 = OpVariable %50 Function + %56 = OpVariable %50 Function + OpStore %8 %9 + OpStore %10 %11 + %13 = OpLoad %6 %8 + %14 = OpLoad %6 %10 + %80 = OpIAddCarry %60 %13 %14 + %15 = OpCompositeExtract %6 %80 0 + OpStore %12 %15 + %16 = OpLoad %6 %8 + %17 = OpLoad %6 %10 + %81 = OpISubBorrow %60 %16 %17 + %18 = OpCompositeExtract %6 %81 0 + OpStore %12 %18 + %19 = OpLoad %6 %8 + %20 = OpLoad %6 %10 + %82 = OpUMulExtended %60 %19 %20 + %21 = OpCompositeExtract %6 %82 0 + OpStore %12 %21 + OpStore %24 %25 + OpStore %26 %27 + %29 = OpLoad %22 %24 + %30 = OpLoad %22 %26 + %83 = OpSMulExtended %69 %29 %30 + %31 = OpCompositeExtract %22 %83 0 + OpStore %28 %31 + OpStore %34 %35 + OpStore %36 %38 + %40 = OpLoad %32 %34 + %41 = OpLoad %32 %36 + %84 = OpIAddCarry %63 %40 %41 + %42 = OpCompositeExtract %32 %84 0 + OpStore %39 %42 + %43 = OpLoad %32 %34 + %44 = OpLoad %32 %36 + %85 = OpISubBorrow %63 %43 %44 + %45 = OpCompositeExtract %32 %85 0 + OpStore %39 %45 + %46 = OpLoad %32 %34 + %47 = OpLoad %32 %36 + %86 = OpUMulExtended %63 %46 %47 + %48 = OpCompositeExtract %32 %86 0 + OpStore %39 %48 + OpStore %51 %52 + OpStore %53 %55 + %57 = OpLoad %49 %51 + %58 = OpLoad %49 %53 + %87 = OpSMulExtended %66 %57 %58 + %59 = OpCompositeExtract %49 %87 0 + OpStore %56 %59 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp index 22815e67..ecffd296 100644 --- a/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp +++ b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp @@ -162,7 +162,7 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -537,7 +537,7 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -653,7 +653,7 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, OpPhi) { BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -732,7 +732,7 @@ TEST(TransformationReplaceBooleanConstantWithConstantBinaryTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp index 8cbba465..ef77231a 100644 --- a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp +++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_replace_constant_with_uniform.h" + #include "source/fuzz/instruction_descriptor.h" #include "source/fuzz/uniform_buffer_element_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -22,8 +23,7 @@ namespace fuzz { namespace { bool AddFactHelper( - TransformationContext* transformation_context, opt::IRContext* context, - uint32_t word, + TransformationContext* transformation_context, uint32_t word, const protobufs::UniformBufferElementDescriptor& descriptor) { protobufs::FactConstantUniform constant_uniform_fact; constant_uniform_fact.add_constant_word(word); @@ -31,7 +31,7 @@ bool AddFactHelper( descriptor; protobufs::Fact fact; *fact.mutable_constant_uniform_fact() = constant_uniform_fact; - return transformation_context->GetFactManager()->AddFact(fact, context); + return transformation_context->GetFactManager()->AddFact(fact); } TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) { @@ -104,7 +104,7 @@ TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -116,12 +116,9 @@ TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) { protobufs::UniformBufferElementDescriptor blockname_c = MakeUniformBufferElementDescriptor(0, 0, {2}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 1, blockname_a)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 2, blockname_b)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 3, blockname_c)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 1, blockname_a)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 2, blockname_b)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 3, blockname_c)); // The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively. protobufs::IdUseDescriptor use_of_9_in_store = @@ -470,7 +467,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NestedStruct) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -484,14 +481,10 @@ TEST(TransformationReplaceConstantWithUniformTest, NestedStruct) { protobufs::UniformBufferElementDescriptor blockname_4 = MakeUniformBufferElementDescriptor(0, 0, {1, 0, 1, 0}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 1, blockname_1)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 2, blockname_2)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 3, blockname_3)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 4, blockname_4)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 1, blockname_1)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 2, blockname_2)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 3, blockname_3)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 4, blockname_4)); // The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively. protobufs::IdUseDescriptor use_of_13_in_store = @@ -715,7 +708,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoUniformIntPointerPresent) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -723,8 +716,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoUniformIntPointerPresent) { protobufs::UniformBufferElementDescriptor blockname_0 = MakeUniformBufferElementDescriptor(0, 0, {0}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 0, blockname_0)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 0, blockname_0)); // The constant id is 9 for 0. protobufs::IdUseDescriptor use_of_9_in_store = @@ -793,7 +785,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -803,8 +795,7 @@ TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) { protobufs::UniformBufferElementDescriptor blockname_9 = MakeUniformBufferElementDescriptor(0, 0, {1}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 9, blockname_9)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 9, blockname_9)); // The constant id is 9 for 9. protobufs::IdUseDescriptor use_of_9_in_store = @@ -870,7 +861,7 @@ TEST(TransformationReplaceConstantWithUniformTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -881,8 +872,8 @@ TEST(TransformationReplaceConstantWithUniformTest, uint32_t float_data[1]; float temp = 3.0; memcpy(&float_data[0], &temp, sizeof(float)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_data[0], blockname_3)); + ASSERT_TRUE( + AddFactHelper(&transformation_context, float_data[0], blockname_3)); // The constant id is 9 for 3.0. protobufs::IdUseDescriptor use_of_9_in_store = @@ -960,7 +951,7 @@ TEST(TransformationReplaceConstantWithUniformTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -970,10 +961,8 @@ TEST(TransformationReplaceConstantWithUniformTest, protobufs::UniformBufferElementDescriptor blockname_10 = MakeUniformBufferElementDescriptor(0, 0, {1}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 9, blockname_9)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 10, blockname_10)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 9, blockname_9)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 10, blockname_10)); // The constant ids for 9 and 10 are 9 and 11 respectively protobufs::IdUseDescriptor use_of_9_in_store = @@ -1179,7 +1168,7 @@ TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1230,43 +1219,35 @@ TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) { protobufs::UniformBufferElementDescriptor uniform_h_y = MakeUniformBufferElementDescriptor(0, 0, {2, 1}); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_array_data[0], uniform_f_a_0)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_array_data[1], uniform_f_a_1)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_array_data[2], uniform_f_a_2)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_array_data[3], uniform_f_a_3)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_array_data[4], uniform_f_a_4)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[0], + uniform_f_a_0)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[1], + uniform_f_a_1)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[2], + uniform_f_a_2)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[3], + uniform_f_a_3)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[4], + uniform_f_a_4)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 1, uniform_f_b_x)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 2, uniform_f_b_y)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 3, uniform_f_b_z)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 4, uniform_f_b_w)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 1, uniform_f_b_x)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 2, uniform_f_b_y)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 3, uniform_f_b_z)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 4, uniform_f_b_w)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_vector_data[0], uniform_f_c_x)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_vector_data[1], uniform_f_c_y)); - ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(), - float_vector_data[2], uniform_f_c_z)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[0], + uniform_f_c_x)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[1], + uniform_f_c_y)); + ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[2], + uniform_f_c_z)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 42, uniform_f_d)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 42, uniform_f_d)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 22, uniform_g)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 22, uniform_g)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 100, uniform_h_x)); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 200, uniform_h_y)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 100, uniform_h_x)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 200, uniform_h_y)); std::vector<TransformationReplaceConstantWithUniform> transformations; @@ -1530,7 +1511,7 @@ TEST(TransformationReplaceConstantWithUniformTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1538,8 +1519,7 @@ TEST(TransformationReplaceConstantWithUniformTest, protobufs::UniformBufferElementDescriptor blockname_a = MakeUniformBufferElementDescriptor(0, 0, {0}); - ASSERT_TRUE( - AddFactHelper(&transformation_context, context.get(), 0, blockname_a)); + ASSERT_TRUE(AddFactHelper(&transformation_context, 0, blockname_a)); ASSERT_FALSE(TransformationReplaceConstantWithUniform( MakeIdUseDescriptor( @@ -1548,6 +1528,110 @@ TEST(TransformationReplaceConstantWithUniformTest, .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationReplaceConstantWithUniformTest, ReplaceOpPhiOperand) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + OpDecorate %32 DescriptorSet 0 + OpDecorate %32 Binding 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 2 + %13 = OpConstant %6 4 + %21 = OpConstant %6 1 + %34 = OpConstant %6 0 + %10 = OpTypeBool + %30 = OpTypeStruct %6 + %31 = OpTypePointer Uniform %30 + %32 = OpVariable %31 Uniform + %33 = OpTypePointer Uniform %6 + %4 = OpFunction %2 None %3 + %11 = OpLabel + OpBranch %5 + %5 = OpLabel + %23 = OpPhi %6 %7 %11 %20 %15 + %9 = OpSLessThan %10 %23 %13 + OpLoopMerge %8 %15 None + OpBranchConditional %9 %15 %8 + %15 = OpLabel + %20 = OpIAdd %6 %23 %21 + OpBranch %5 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto int_descriptor = MakeUniformBufferElementDescriptor(0, 0, {0}); + + ASSERT_TRUE(AddFactHelper(&transformation_context, 2, int_descriptor)); + + { + TransformationReplaceConstantWithUniform transformation( + MakeIdUseDescriptor(7, MakeInstructionDescriptor(23, SpvOpPhi, 0), 0), + int_descriptor, 50, 51); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + OpDecorate %32 DescriptorSet 0 + OpDecorate %32 Binding 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 2 + %13 = OpConstant %6 4 + %21 = OpConstant %6 1 + %34 = OpConstant %6 0 + %10 = OpTypeBool + %30 = OpTypeStruct %6 + %31 = OpTypePointer Uniform %30 + %32 = OpVariable %31 Uniform + %33 = OpTypePointer Uniform %6 + %4 = OpFunction %2 None %3 + %11 = OpLabel + %50 = OpAccessChain %33 %32 %34 + %51 = OpLoad %6 %50 + OpBranch %5 + %5 = OpLabel + %23 = OpPhi %6 %51 %11 %20 %15 + %9 = OpSLessThan %10 %23 %13 + OpLoopMerge %8 %15 None + OpBranchConditional %9 %15 %8 + %15 = OpLabel + %20 = OpIAdd %6 %23 %21 + OpBranch %5 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp new file mode 100644 index 00000000..baa7a9dd --- /dev/null +++ b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationReplaceCopyMemoryWithLoadStoreTest, BasicScenarios) { + // This is a simple transformation and this test handles the main cases. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %14 "c" + OpName %16 "d" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpTypeFloat 32 + %13 = OpTypePointer Function %12 + %15 = OpConstant %12 2 + %17 = OpConstant %12 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %16 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %14 %15 + OpStore %16 %17 + OpCopyMemory %8 %10 + OpCopyMemory %16 %14 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + auto instruction_descriptor_invalid_1 = + MakeInstructionDescriptor(5, SpvOpStore, 0); + auto instruction_descriptor_valid_1 = + MakeInstructionDescriptor(5, SpvOpCopyMemory, 0); + auto instruction_descriptor_valid_2 = + MakeInstructionDescriptor(5, SpvOpCopyMemory, 0); + + // Invalid: |source_id| is not a fresh id. + auto transformation_invalid_1 = TransformationReplaceCopyMemoryWithLoadStore( + 15, instruction_descriptor_valid_1); + ASSERT_FALSE(transformation_invalid_1.IsApplicable(context.get(), + transformation_context)); + + // Invalid: |instruction_descriptor_invalid| refers to an instruction OpStore. + auto transformation_invalid_2 = TransformationReplaceCopyMemoryWithLoadStore( + 20, instruction_descriptor_invalid_1); + ASSERT_FALSE(transformation_invalid_2.IsApplicable(context.get(), + transformation_context)); + + auto transformation_valid_1 = TransformationReplaceCopyMemoryWithLoadStore( + 20, instruction_descriptor_valid_1); + ASSERT_TRUE(transformation_valid_1.IsApplicable(context.get(), + transformation_context)); + transformation_valid_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_valid_2 = TransformationReplaceCopyMemoryWithLoadStore( + 21, instruction_descriptor_valid_2); + ASSERT_TRUE(transformation_valid_2.IsApplicable(context.get(), + transformation_context)); + transformation_valid_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %14 "c" + OpName %16 "d" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpTypeFloat 32 + %13 = OpTypePointer Function %12 + %15 = OpConstant %12 2 + %17 = OpConstant %12 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %16 = OpVariable %13 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %14 %15 + OpStore %16 %17 + %20 = OpLoad %6 %10 + OpStore %8 %20 + %21 = OpLoad %12 %14 + OpStore %16 %21 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp b/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp new file mode 100644 index 00000000..5b78dee0 --- /dev/null +++ b/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_copy_object_with_store_load.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationReplaceCopyObjectWithStoreLoad, BasicScenarios) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %23 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %14 "c" + OpName %16 "d" + OpName %18 "e" + OpName %23 "f" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpTypeFloat 32 + %13 = OpTypePointer Function %12 + %15 = OpConstant %12 2 + %17 = OpConstant %12 3 + %22 = OpTypePointer Private %12 + %23 = OpVariable %22 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %16 = OpVariable %13 Function + %18 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %14 %15 + OpStore %16 %17 + %19 = OpLoad %6 %8 + %20 = OpLoad %6 %10 + %21 = OpIAdd %6 %19 %20 + OpStore %18 %21 + %24 = OpLoad %12 %14 + %25 = OpLoad %12 %16 + %26 = OpFMul %12 %24 %25 + OpStore %23 %26 + %27 = OpCopyObject %6 %21 + %28 = OpCopyObject %12 %26 + %40 = OpCopyObject %13 %14 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + // Invalid: fresh_variable_id=10 is not fresh. + auto transformation_invalid_1 = TransformationReplaceCopyObjectWithStoreLoad( + 27, 10, SpvStorageClassFunction, 9); + ASSERT_FALSE(transformation_invalid_1.IsApplicable(context.get(), + transformation_context)); + + // Invalid: copy_object_result_id=26 is not a CopyObject instruction. + auto transformation_invalid_2 = TransformationReplaceCopyObjectWithStoreLoad( + 26, 30, SpvStorageClassFunction, 9); + ASSERT_FALSE(transformation_invalid_2.IsApplicable(context.get(), + transformation_context)); + + // Invalid: copy_object_result_id=40 is of type pointer. + auto transformation_invalid_3 = TransformationReplaceCopyObjectWithStoreLoad( + 40, 30, SpvStorageClassFunction, 9); + ASSERT_FALSE(transformation_invalid_3.IsApplicable(context.get(), + transformation_context)); + + // Invalid: Pointer type instruction in this storage class pointing to the + // value type is not defined. + auto transformation_invalid_4 = TransformationReplaceCopyObjectWithStoreLoad( + 40, 30, SpvStorageClassPrivate, 9); + ASSERT_FALSE(transformation_invalid_4.IsApplicable(context.get(), + transformation_context)); + + // Invalid: initializer_id=15 has the wrong type relative to the OpCopyObject + // instruction. + auto transformation_invalid_5 = TransformationReplaceCopyObjectWithStoreLoad( + 27, 30, SpvStorageClassFunction, 15); + ASSERT_FALSE(transformation_invalid_5.IsApplicable(context.get(), + transformation_context)); + + // Invalid: SpvStorageClassUniform is not applicable to the transformation. + auto transformation_invalid_6 = TransformationReplaceCopyObjectWithStoreLoad( + 27, 30, SpvStorageClassUniform, 9); + ASSERT_FALSE(transformation_invalid_6.IsApplicable(context.get(), + transformation_context)); + + auto transformation_valid_1 = TransformationReplaceCopyObjectWithStoreLoad( + 27, 30, SpvStorageClassFunction, 9); + ASSERT_TRUE(transformation_valid_1.IsApplicable(context.get(), + transformation_context)); + transformation_valid_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_valid_2 = TransformationReplaceCopyObjectWithStoreLoad( + 28, 32, SpvStorageClassPrivate, 15); + ASSERT_TRUE(transformation_valid_2.IsApplicable(context.get(), + transformation_context)); + transformation_valid_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %23 %32 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %14 "c" + OpName %16 "d" + OpName %18 "e" + OpName %23 "f" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %12 = OpTypeFloat 32 + %13 = OpTypePointer Function %12 + %15 = OpConstant %12 2 + %17 = OpConstant %12 3 + %22 = OpTypePointer Private %12 + %23 = OpVariable %22 Private + %32 = OpVariable %22 Private %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %30 = OpVariable %7 Function %9 + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %14 = OpVariable %13 Function + %16 = OpVariable %13 Function + %18 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %14 %15 + OpStore %16 %17 + %19 = OpLoad %6 %8 + %20 = OpLoad %6 %10 + %21 = OpIAdd %6 %19 %20 + OpStore %18 %21 + %24 = OpLoad %12 %14 + %25 = OpLoad %12 %16 + %26 = OpFMul %12 %24 %25 + OpStore %23 %26 + OpStore %30 %21 + %27 = OpLoad %6 %30 + OpStore %32 %26 + %28 = OpLoad %12 %32 + %40 = OpCopyObject %13 %14 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_id_with_synonym_test.cpp b/test/fuzz/transformation_replace_id_with_synonym_test.cpp index 37e9510a..61de95c8 100644 --- a/test/fuzz/transformation_replace_id_with_synonym_test.cpp +++ b/test/fuzz/transformation_replace_id_with_synonym_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_replace_id_with_synonym.h" + #include "source/fuzz/data_descriptor.h" #include "source/fuzz/id_use_descriptor.h" #include "source/fuzz/instruction_descriptor.h" @@ -198,18 +199,18 @@ protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) { } // Equips the fact manager with synonym facts for the above shader. -void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) { - fact_manager->AddFact(MakeSynonymFact(15, 200), context); - fact_manager->AddFact(MakeSynonymFact(15, 201), context); - fact_manager->AddFact(MakeSynonymFact(15, 202), context); - fact_manager->AddFact(MakeSynonymFact(55, 203), context); - fact_manager->AddFact(MakeSynonymFact(54, 204), context); - fact_manager->AddFact(MakeSynonymFact(74, 205), context); - fact_manager->AddFact(MakeSynonymFact(78, 206), context); - fact_manager->AddFact(MakeSynonymFact(84, 207), context); - fact_manager->AddFact(MakeSynonymFact(33, 208), context); - fact_manager->AddFact(MakeSynonymFact(12, 209), context); - fact_manager->AddFact(MakeSynonymFact(19, 210), context); +void SetUpIdSynonyms(FactManager* fact_manager) { + fact_manager->AddFact(MakeSynonymFact(15, 200)); + fact_manager->AddFact(MakeSynonymFact(15, 201)); + fact_manager->AddFact(MakeSynonymFact(15, 202)); + fact_manager->AddFact(MakeSynonymFact(55, 203)); + fact_manager->AddFact(MakeSynonymFact(54, 204)); + fact_manager->AddFact(MakeSynonymFact(74, 205)); + fact_manager->AddFact(MakeSynonymFact(78, 206)); + fact_manager->AddFact(MakeSynonymFact(84, 207)); + fact_manager->AddFact(MakeSynonymFact(33, 208)); + fact_manager->AddFact(MakeSynonymFact(12, 209)); + fact_manager->AddFact(MakeSynonymFact(19, 210)); } TEST(TransformationReplaceIdWithSynonymTest, IllegalTransformations) { @@ -219,12 +220,12 @@ TEST(TransformationReplaceIdWithSynonymTest, IllegalTransformations) { BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - SetUpIdSynonyms(transformation_context.GetFactManager(), context.get()); + SetUpIdSynonyms(transformation_context.GetFactManager()); // %202 cannot replace %15 as in-operand 0 of %300, since %202 does not // dominate %300. @@ -294,12 +295,12 @@ TEST(TransformationReplaceIdWithSynonymTest, LegalTransformations) { BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - SetUpIdSynonyms(transformation_context.GetFactManager(), context.get()); + SetUpIdSynonyms(transformation_context.GetFactManager()); auto global_constant_synonym = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor(19, MakeInstructionDescriptor(47, SpvOpStore, 0), 1), @@ -517,15 +518,13 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfVariables) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 100), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 101), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 100)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 101)); // Replace %10 with %100 in: // %11 = OpLoad %6 %10 @@ -651,13 +650,12 @@ TEST(TransformationReplaceIdWithSynonymTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 100), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 100)); // Replace %14 with %100 in: // %16 = OpFunctionCall %2 %10 %14 @@ -806,7 +804,7 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) { OpStore %53 %32 %56 = OpAccessChain %23 %50 %17 %21 %21 %55 OpStore %56 %54 - %58 = OpAccessChain %26 %50 %57 %21 %17 + %58 = OpInBoundsAccessChain %26 %50 %57 %21 %17 OpStore %58 %45 OpReturn OpFunctionEnd @@ -817,39 +815,26 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Add synonym facts corresponding to the OpCopyObject operations that have // been applied to all constants in the module. - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(16, 100), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(21, 101), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(17, 102), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(57, 103), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(18, 104), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(40, 105), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(32, 106), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(43, 107), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(55, 108), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 109), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(47, 110), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(28, 111), - context.get()); - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(45, 112), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(16, 100)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(21, 101)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(17, 102)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(57, 103)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(18, 104)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(40, 105)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(32, 106)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(43, 107)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(55, 108)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 109)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(47, 110)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(28, 111)); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(45, 112)); // Replacements of the form %16 -> %100 @@ -1031,12 +1016,12 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) { ASSERT_FALSE( replacement17.IsApplicable(context.get(), transformation_context)); - // %58 = OpAccessChain %26 %50 %57 *%21* %17 + // %58 = OpInBoundsAccessChain %26 %50 %57 *%21* %17 // Corresponds to i[3].*g*.c // The index %24 used for g cannot be replaced auto replacement18 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor( - 21, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 2), + 21, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 2), 101); ASSERT_FALSE( replacement18.IsApplicable(context.get(), transformation_context)); @@ -1087,24 +1072,24 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) { replacement22.Apply(context.get(), &transformation_context); ASSERT_TRUE(IsValid(env, context.get())); - // %58 = OpAccessChain %26 %50 %57 %21 %17 + // %58 = OpInBoundsAccessChain %26 %50 %57 %21 %17 // Corresponds to i[3].g.*c* // The index %17 used for c cannot be replaced auto replacement23 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor( - 17, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 3), + 17, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 3), 102); ASSERT_FALSE( replacement23.IsApplicable(context.get(), transformation_context)); // Replacements of the form %57 -> %103 - // %58 = OpAccessChain %26 %50 *%57* %21 %17 + // %58 = OpInBoundsAccessChain %26 %50 *%57* %21 %17 // Corresponds to i[*3*].g.c // The index %57 used for 3 *can* be replaced auto replacement24 = TransformationReplaceIdWithSynonym( MakeIdUseDescriptor( - 57, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 1), + 57, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 1), 103); ASSERT_TRUE( replacement24.IsApplicable(context.get(), transformation_context)); @@ -1268,7 +1253,7 @@ TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) { OpStore %53 %32 %56 = OpAccessChain %23 %50 %102 %21 %21 %108 OpStore %56 %54 - %58 = OpAccessChain %26 %50 %103 %21 %17 + %58 = OpInBoundsAccessChain %26 %50 %103 %21 %17 OpStore %58 %45 OpReturn OpFunctionEnd @@ -1318,17 +1303,15 @@ TEST(TransformationReplaceIdWithSynonymTest, RuntimeArrayTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Add synonym fact relating %50 and %12. - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(50, 12), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(50, 12)); // Add synonym fact relating %51 and %14. - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(51, 14), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(51, 14)); // Not legal because the index being replaced is a struct index. ASSERT_FALSE( @@ -1431,14 +1414,13 @@ TEST(TransformationReplaceIdWithSynonymTest, const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); // Add synonym fact relating %100 and %9. - transformation_context.GetFactManager()->AddFact(MakeSynonymFact(100, 9), - context.get()); + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(100, 9)); // Not legal the Sample argument of OpImageTexelPointer needs to be a zero // constant. @@ -1450,6 +1432,332 @@ TEST(TransformationReplaceIdWithSynonymTest, .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationReplaceIdWithSynonymTest, EquivalentIntegerConstants) { + // This checks that replacing an integer constant with an equivalent one with + // different signedness is allowed only when valid. + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpDecorate %3 RelaxedPrecision + %4 = OpTypeVoid + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeFunction %4 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 1 + %11 = OpTypeInt 32 0 + %12 = OpTypePointer Function %11 + %13 = OpConstant %11 1 + %2 = OpFunction %4 None %7 + %14 = OpLabel + %3 = OpVariable %9 Function + %15 = OpSNegate %8 %10 + %16 = OpIAdd %8 %10 %10 + %17 = OpSDiv %8 %10 %10 + %18 = OpUDiv %11 %13 %13 + %19 = OpBitwiseAnd %8 %10 %10 + %20 = OpSelect %8 %6 %10 %17 + %21 = OpIEqual %5 %10 %10 + OpStore %3 %10 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Add synonym fact relating %10 and %13 (equivalent integer constant with + // different signedness). + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 13)); + + // Legal because OpSNegate always considers the integer as signed + auto replacement1 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor(10, MakeInstructionDescriptor(15, SpvOpSNegate, 0), + 0), + 13); + ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context)); + replacement1.Apply(context.get(), &transformation_context); + + // Legal because OpIAdd does not care about the signedness of the operands + auto replacement2 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor(10, MakeInstructionDescriptor(16, SpvOpIAdd, 0), 0), + 13); + ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context)); + replacement2.Apply(context.get(), &transformation_context); + + // Legal because OpSDiv does not care about the signedness of the operands + auto replacement3 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor(10, MakeInstructionDescriptor(17, SpvOpSDiv, 0), 0), + 13); + ASSERT_TRUE(replacement3.IsApplicable(context.get(), transformation_context)); + replacement3.Apply(context.get(), &transformation_context); + + // Not legal because OpUDiv requires unsigned integers + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor( + 13, MakeInstructionDescriptor(18, SpvOpUDiv, 0), 0), + 10) + .IsApplicable(context.get(), transformation_context)); + + // Legal because OpSDiv does not care about the signedness of the operands + auto replacement4 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor(10, MakeInstructionDescriptor(19, SpvOpBitwiseAnd, 0), + 0), + 13); + ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context)); + replacement4.Apply(context.get(), &transformation_context); + + // Not legal because OpSelect requires both operands to have the same type as + // the result type + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor( + 10, MakeInstructionDescriptor(20, SpvOpUDiv, 0), 1), + 13) + .IsApplicable(context.get(), transformation_context)); + + // Not legal because OpStore requires the object to match the type pointed + // to by the pointer. + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor( + 10, MakeInstructionDescriptor(21, SpvOpStore, 0), 1), + 13) + .IsApplicable(context.get(), transformation_context)); + + ASSERT_TRUE(IsValid(env, context.get())); + + const std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpDecorate %3 RelaxedPrecision + %4 = OpTypeVoid + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpTypeFunction %4 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 1 + %11 = OpTypeInt 32 0 + %12 = OpTypePointer Function %11 + %13 = OpConstant %11 1 + %2 = OpFunction %4 None %7 + %14 = OpLabel + %3 = OpVariable %9 Function + %15 = OpSNegate %8 %13 + %16 = OpIAdd %8 %13 %10 + %17 = OpSDiv %8 %13 %10 + %18 = OpUDiv %11 %13 %13 + %19 = OpBitwiseAnd %8 %13 %10 + %20 = OpSelect %8 %6 %10 %17 + %21 = OpIEqual %5 %10 %10 + OpStore %3 %10 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationReplaceIdWithSynonymTest, EquivalentIntegerVectorConstants) { + // This checks that replacing an integer constant with an equivalent one with + // different signedness is allowed only when valid. + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %9 = OpTypeVector %7 4 + %10 = OpTypeVector %8 4 + %11 = OpTypePointer Function %9 + %12 = OpConstant %7 1 + %13 = OpConstant %8 1 + %14 = OpConstantComposite %9 %12 %12 %12 %12 + %15 = OpConstantComposite %10 %13 %13 %13 %13 + %16 = OpTypePointer Function %7 + %2 = OpFunction %5 None %6 + %17 = OpLabel + %3 = OpVariable %11 Function + %18 = OpIAdd %9 %14 %14 + OpStore %3 %14 + %19 = OpAccessChain %16 %3 %13 + %4 = OpLoad %7 %19 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // Add synonym fact relating %10 and %13 (equivalent integer vectors with + // different signedness). + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 15)); + + // Legal because OpIAdd does not consider the signedness of the operands + auto replacement1 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor(14, MakeInstructionDescriptor(18, SpvOpIAdd, 0), 0), + 15); + ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context)); + replacement1.Apply(context.get(), &transformation_context); + + // Not legal because OpStore requires the object to match the type pointed + // to by the pointer. + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor( + 14, MakeInstructionDescriptor(18, SpvOpStore, 0), 1), + 15) + .IsApplicable(context.get(), transformation_context)); + + // Add synonym fact relating %12 and %13 (equivalent integer constants with + // different signedness). + transformation_context.GetFactManager()->AddFact(MakeSynonymFact(12, 13)); + + // Legal because the indices of OpAccessChain are always treated as signed + auto replacement2 = TransformationReplaceIdWithSynonym( + MakeIdUseDescriptor( + 13, MakeInstructionDescriptor(19, SpvOpAccessChain, 0), 1), + 12); + ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context)); + replacement2.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + const std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpDecorate %3 RelaxedPrecision + OpDecorate %4 RelaxedPrecision + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %9 = OpTypeVector %7 4 + %10 = OpTypeVector %8 4 + %11 = OpTypePointer Function %9 + %12 = OpConstant %7 1 + %13 = OpConstant %8 1 + %14 = OpConstantComposite %9 %12 %12 %12 %12 + %15 = OpConstantComposite %10 %13 %13 %13 %13 + %16 = OpTypePointer Function %7 + %2 = OpFunction %5 None %6 + %17 = OpLabel + %3 = OpVariable %11 Function + %18 = OpIAdd %9 %15 %14 + OpStore %3 %14 + %19 = OpAccessChain %16 %3 %12 + %4 = OpLoad %7 %19 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationReplaceIdWithSynonymTest, IncompatibleTypes) { + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypeInt 32 0 + %9 = OpTypeFloat 32 + %12 = OpConstant %7 1 + %13 = OpConstant %8 1 + %10 = OpConstant %9 1 + %2 = OpFunction %5 None %6 + %17 = OpLabel + %18 = OpIAdd %7 %12 %13 + %19 = OpFAdd %9 %10 %10 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto* op_i_add = context->get_def_use_mgr()->GetDef(18); + ASSERT_TRUE(op_i_add); + + auto* op_f_add = context->get_def_use_mgr()->GetDef(19); + ASSERT_TRUE(op_f_add); + + fact_manager.AddFactDataSynonym(MakeDataDescriptor(12, {}), + MakeDataDescriptor(13, {})); + fact_manager.AddFactDataSynonym(MakeDataDescriptor(12, {}), + MakeDataDescriptor(10, {})); + + // Synonym differs only in signedness for OpIAdd. + ASSERT_TRUE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptorFromUse(context.get(), op_i_add, 0), 13) + .IsApplicable(context.get(), transformation_context)); + + // Synonym has wrong type for OpIAdd. + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptorFromUse(context.get(), op_i_add, 0), 10) + .IsApplicable(context.get(), transformation_context)); + + // Synonym has wrong type for OpFAdd. + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptorFromUse(context.get(), op_f_add, 0), 12) + .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE(TransformationReplaceIdWithSynonym( + MakeIdUseDescriptorFromUse(context.get(), op_f_add, 0), 13) + .IsApplicable(context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_replace_irrelevant_id_test.cpp b/test/fuzz/transformation_replace_irrelevant_id_test.cpp new file mode 100644 index 00000000..fc6115ef --- /dev/null +++ b/test/fuzz/transformation_replace_irrelevant_id_test.cpp @@ -0,0 +1,193 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_irrelevant_id.h" + +#include "source/fuzz/id_use_descriptor.h" +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { +const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %9 = OpTypeInt 32 1 + %10 = OpTypePointer Function %9 + %11 = OpConstant %9 2 + %12 = OpTypeStruct %9 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 3 + %15 = OpTypeArray %12 %14 + %16 = OpTypePointer Function %15 + %17 = OpConstant %9 0 + %2 = OpFunction %5 None %6 + %18 = OpLabel + %3 = OpVariable %10 Function + %4 = OpVariable %10 Function + %19 = OpVariable %16 Function + OpStore %3 %11 + %20 = OpLoad %9 %3 + %21 = OpAccessChain %10 %19 %20 %17 + %22 = OpLoad %9 %21 + OpStore %4 %22 + %23 = OpLoad %9 %4 + %24 = OpIAdd %9 %20 %23 + %25 = OpISub %9 %23 %20 + OpReturn + OpFunctionEnd +)"; + +void SetUpIrrelevantIdFacts(FactManager* fact_manager) { + fact_manager->AddFactIdIsIrrelevant(17); + fact_manager->AddFactIdIsIrrelevant(23); + fact_manager->AddFactIdIsIrrelevant(24); + fact_manager->AddFactIdIsIrrelevant(25); +} + +TEST(TransformationReplaceIrrelevantIdTest, Inapplicable) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + SetUpIrrelevantIdFacts(transformation_context.GetFactManager()); + + auto instruction_21_descriptor = + MakeInstructionDescriptor(21, SpvOpAccessChain, 0); + auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0); + + // %20 has not been declared as irrelevant. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(20, instruction_24_descriptor, 0), 23) + .IsApplicable(context.get(), transformation_context)); + + // %22 is not used in %24. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(22, instruction_24_descriptor, 1), 20) + .IsApplicable(context.get(), transformation_context)); + + // Replacement id %50 does not exist. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 50) + .IsApplicable(context.get(), transformation_context)); + + // %25 is not available to use at %24. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 25) + .IsApplicable(context.get(), transformation_context)); + + // %24 is not available to use at %24. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 24) + .IsApplicable(context.get(), transformation_context)); + + // %8 has not the same type as %23. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 8) + .IsApplicable(context.get(), transformation_context)); + + // %17 is an index to a struct in an access chain, so it can't be replaced. + ASSERT_FALSE(TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(17, instruction_21_descriptor, 2), 20) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceIrrelevantIdTest, Apply) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + SetUpIrrelevantIdFacts(transformation_context.GetFactManager()); + + auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0); + + // Replace the use of %23 in %24 with %22. + auto transformation = TransformationReplaceIrrelevantId( + MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 22); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + OpName %3 "a" + OpName %4 "b" + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %9 = OpTypeInt 32 1 + %10 = OpTypePointer Function %9 + %11 = OpConstant %9 2 + %12 = OpTypeStruct %9 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 3 + %15 = OpTypeArray %12 %14 + %16 = OpTypePointer Function %15 + %17 = OpConstant %9 0 + %2 = OpFunction %5 None %6 + %18 = OpLabel + %3 = OpVariable %10 Function + %4 = OpVariable %10 Function + %19 = OpVariable %16 Function + OpStore %3 %11 + %20 = OpLoad %9 %3 + %21 = OpAccessChain %10 %19 %20 %17 + %22 = OpLoad %9 %21 + OpStore %4 %22 + %23 = OpLoad %9 %4 + %24 = OpIAdd %9 %20 %22 + %25 = OpISub %9 %23 %20 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp b/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp index 0b04e965..0f29b000 100644 --- a/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp +++ b/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_replace_linear_algebra_instruction.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -69,7 +70,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, IsApplicable) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -137,6 +138,333 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, IsApplicable) { transformation.IsApplicable(context.get(), transformation_context)); } +TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpTranspose) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %54 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeFloat 32 + %5 = OpTypeVector %4 2 + %6 = OpTypeVector %4 3 + %7 = OpTypeVector %4 4 + %8 = OpTypeMatrix %5 2 + %9 = OpTypeMatrix %5 3 + %10 = OpTypeMatrix %5 4 + %11 = OpTypeMatrix %6 2 + %12 = OpTypeMatrix %6 3 + %13 = OpTypeMatrix %6 4 + %14 = OpTypeMatrix %7 2 + %15 = OpTypeMatrix %7 3 + %16 = OpTypeMatrix %7 4 + +; Constant scalars + %17 = OpConstant %4 1 + %18 = OpConstant %4 2 + %19 = OpConstant %4 3 + %20 = OpConstant %4 4 + %21 = OpConstant %4 5 + %22 = OpConstant %4 6 + %23 = OpConstant %4 7 + %24 = OpConstant %4 8 + %25 = OpConstant %4 9 + %26 = OpConstant %4 10 + %27 = OpConstant %4 11 + %28 = OpConstant %4 12 + %29 = OpConstant %4 13 + %30 = OpConstant %4 14 + %31 = OpConstant %4 15 + %32 = OpConstant %4 16 + +; Constant vectors + %33 = OpConstantComposite %5 %17 %18 + %34 = OpConstantComposite %5 %19 %20 + %35 = OpConstantComposite %5 %21 %22 + %36 = OpConstantComposite %5 %23 %24 + %37 = OpConstantComposite %6 %17 %18 %19 + %38 = OpConstantComposite %6 %20 %21 %22 + %39 = OpConstantComposite %6 %23 %24 %25 + %40 = OpConstantComposite %6 %26 %27 %28 + %41 = OpConstantComposite %7 %17 %18 %19 %20 + %42 = OpConstantComposite %7 %21 %22 %23 %24 + %43 = OpConstantComposite %7 %25 %26 %27 %28 + %44 = OpConstantComposite %7 %29 %30 %31 %32 + +; Constant matrices + %45 = OpConstantComposite %8 %33 %34 + %46 = OpConstantComposite %9 %33 %34 %35 + %47 = OpConstantComposite %10 %33 %34 %35 %36 + %48 = OpConstantComposite %11 %37 %38 + %49 = OpConstantComposite %12 %37 %38 %39 + %50 = OpConstantComposite %13 %37 %38 %39 %40 + %51 = OpConstantComposite %14 %41 %42 + %52 = OpConstantComposite %15 %41 %42 %43 + %53 = OpConstantComposite %16 %41 %42 %43 %44 + +; main function + %54 = OpFunction %2 None %3 + %55 = OpLabel + +; Transposing a 2x2 matrix + %56 = OpTranspose %8 %45 + +; Transposing a 2x3 matrix + %57 = OpTranspose %11 %46 + +; Transposing a 2x4 matrix + %58 = OpTranspose %14 %47 + +; Transposing a 3x2 matrix + %59 = OpTranspose %9 %48 + +; Transposing a 3x3 matrix + %60 = OpTranspose %12 %49 + +; Transposing a 3x4 matrix + %61 = OpTranspose %15 %50 + +; Transposing a 4x2 matrix + %62 = OpTranspose %10 %51 + +; Transposing a 4x3 matrix + %63 = OpTranspose %13 %52 + +; Transposing a 4x4 matrix + %64 = OpTranspose %16 %53 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto instruction_descriptor = + MakeInstructionDescriptor(56, SpvOpTranspose, 0); + auto transformation = TransformationReplaceLinearAlgebraInstruction( + {65, 66, 67, 68, 69, 70, 71, 72, 73, 74}, instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(57, SpvOpTranspose, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(58, SpvOpTranspose, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(59, SpvOpTranspose, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, + 121}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(60, SpvOpTranspose, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %54 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeFloat 32 + %5 = OpTypeVector %4 2 + %6 = OpTypeVector %4 3 + %7 = OpTypeVector %4 4 + %8 = OpTypeMatrix %5 2 + %9 = OpTypeMatrix %5 3 + %10 = OpTypeMatrix %5 4 + %11 = OpTypeMatrix %6 2 + %12 = OpTypeMatrix %6 3 + %13 = OpTypeMatrix %6 4 + %14 = OpTypeMatrix %7 2 + %15 = OpTypeMatrix %7 3 + %16 = OpTypeMatrix %7 4 + +; Constant scalars + %17 = OpConstant %4 1 + %18 = OpConstant %4 2 + %19 = OpConstant %4 3 + %20 = OpConstant %4 4 + %21 = OpConstant %4 5 + %22 = OpConstant %4 6 + %23 = OpConstant %4 7 + %24 = OpConstant %4 8 + %25 = OpConstant %4 9 + %26 = OpConstant %4 10 + %27 = OpConstant %4 11 + %28 = OpConstant %4 12 + %29 = OpConstant %4 13 + %30 = OpConstant %4 14 + %31 = OpConstant %4 15 + %32 = OpConstant %4 16 + +; Constant vectors + %33 = OpConstantComposite %5 %17 %18 + %34 = OpConstantComposite %5 %19 %20 + %35 = OpConstantComposite %5 %21 %22 + %36 = OpConstantComposite %5 %23 %24 + %37 = OpConstantComposite %6 %17 %18 %19 + %38 = OpConstantComposite %6 %20 %21 %22 + %39 = OpConstantComposite %6 %23 %24 %25 + %40 = OpConstantComposite %6 %26 %27 %28 + %41 = OpConstantComposite %7 %17 %18 %19 %20 + %42 = OpConstantComposite %7 %21 %22 %23 %24 + %43 = OpConstantComposite %7 %25 %26 %27 %28 + %44 = OpConstantComposite %7 %29 %30 %31 %32 + +; Constant matrices + %45 = OpConstantComposite %8 %33 %34 + %46 = OpConstantComposite %9 %33 %34 %35 + %47 = OpConstantComposite %10 %33 %34 %35 %36 + %48 = OpConstantComposite %11 %37 %38 + %49 = OpConstantComposite %12 %37 %38 %39 + %50 = OpConstantComposite %13 %37 %38 %39 %40 + %51 = OpConstantComposite %14 %41 %42 + %52 = OpConstantComposite %15 %41 %42 %43 + %53 = OpConstantComposite %16 %41 %42 %43 %44 + +; main function + %54 = OpFunction %2 None %3 + %55 = OpLabel + +; Transposing a 2x2 matrix + %65 = OpCompositeExtract %5 %45 0 + %66 = OpCompositeExtract %4 %65 0 + %67 = OpCompositeExtract %5 %45 1 + %68 = OpCompositeExtract %4 %67 0 + %69 = OpCompositeConstruct %5 %66 %68 + %70 = OpCompositeExtract %5 %45 0 + %71 = OpCompositeExtract %4 %70 1 + %72 = OpCompositeExtract %5 %45 1 + %73 = OpCompositeExtract %4 %72 1 + %74 = OpCompositeConstruct %5 %71 %73 + %56 = OpCompositeConstruct %8 %69 %74 + +; Transposing a 2x3 matrix + %75 = OpCompositeExtract %5 %46 0 + %76 = OpCompositeExtract %4 %75 0 + %77 = OpCompositeExtract %5 %46 1 + %78 = OpCompositeExtract %4 %77 0 + %79 = OpCompositeExtract %5 %46 2 + %80 = OpCompositeExtract %4 %79 0 + %81 = OpCompositeConstruct %6 %76 %78 %80 + %82 = OpCompositeExtract %5 %46 0 + %83 = OpCompositeExtract %4 %82 1 + %84 = OpCompositeExtract %5 %46 1 + %85 = OpCompositeExtract %4 %84 1 + %86 = OpCompositeExtract %5 %46 2 + %87 = OpCompositeExtract %4 %86 1 + %88 = OpCompositeConstruct %6 %83 %85 %87 + %57 = OpCompositeConstruct %11 %81 %88 + +; Transposing a 2x4 matrix + %89 = OpCompositeExtract %5 %47 0 + %90 = OpCompositeExtract %4 %89 0 + %91 = OpCompositeExtract %5 %47 1 + %92 = OpCompositeExtract %4 %91 0 + %93 = OpCompositeExtract %5 %47 2 + %94 = OpCompositeExtract %4 %93 0 + %95 = OpCompositeExtract %5 %47 3 + %96 = OpCompositeExtract %4 %95 0 + %97 = OpCompositeConstruct %7 %90 %92 %94 %96 + %98 = OpCompositeExtract %5 %47 0 + %99 = OpCompositeExtract %4 %98 1 + %100 = OpCompositeExtract %5 %47 1 + %101 = OpCompositeExtract %4 %100 1 + %102 = OpCompositeExtract %5 %47 2 + %103 = OpCompositeExtract %4 %102 1 + %104 = OpCompositeExtract %5 %47 3 + %105 = OpCompositeExtract %4 %104 1 + %106 = OpCompositeConstruct %7 %99 %101 %103 %105 + %58 = OpCompositeConstruct %14 %97 %106 + +; Transposing a 3x2 matrix + %107 = OpCompositeExtract %6 %48 0 + %108 = OpCompositeExtract %4 %107 0 + %109 = OpCompositeExtract %6 %48 1 + %110 = OpCompositeExtract %4 %109 0 + %111 = OpCompositeConstruct %5 %108 %110 + %112 = OpCompositeExtract %6 %48 0 + %113 = OpCompositeExtract %4 %112 1 + %114 = OpCompositeExtract %6 %48 1 + %115 = OpCompositeExtract %4 %114 1 + %116 = OpCompositeConstruct %5 %113 %115 + %117 = OpCompositeExtract %6 %48 0 + %118 = OpCompositeExtract %4 %117 2 + %119 = OpCompositeExtract %6 %48 1 + %120 = OpCompositeExtract %4 %119 2 + %121 = OpCompositeConstruct %5 %118 %120 + %59 = OpCompositeConstruct %9 %111 %116 %121 + +; Transposing a 3x3 matrix + %122 = OpCompositeExtract %6 %49 0 + %123 = OpCompositeExtract %4 %122 0 + %124 = OpCompositeExtract %6 %49 1 + %125 = OpCompositeExtract %4 %124 0 + %126 = OpCompositeExtract %6 %49 2 + %127 = OpCompositeExtract %4 %126 0 + %128 = OpCompositeConstruct %6 %123 %125 %127 + %129 = OpCompositeExtract %6 %49 0 + %130 = OpCompositeExtract %4 %129 1 + %131 = OpCompositeExtract %6 %49 1 + %132 = OpCompositeExtract %4 %131 1 + %133 = OpCompositeExtract %6 %49 2 + %134 = OpCompositeExtract %4 %133 1 + %135 = OpCompositeConstruct %6 %130 %132 %134 + %136 = OpCompositeExtract %6 %49 0 + %137 = OpCompositeExtract %4 %136 2 + %138 = OpCompositeExtract %6 %49 1 + %139 = OpCompositeExtract %4 %138 2 + %140 = OpCompositeExtract %6 %49 2 + %141 = OpCompositeExtract %4 %140 2 + %142 = OpCompositeConstruct %6 %137 %139 %141 + %60 = OpCompositeConstruct %12 %128 %135 %142 + +; Transposing a 3x4 matrix + %61 = OpTranspose %15 %50 + +; Transposing a 4x2 matrix + %62 = OpTranspose %10 %51 + +; Transposing a 4x3 matrix + %63 = OpTranspose %13 %52 + +; Transposing a 4x4 matrix + %64 = OpTranspose %16 %53 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpVectorTimesScalar) { std::string reference_shader = R"( @@ -175,7 +503,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -347,7 +675,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -635,7 +963,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -970,7 +1298,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1357,7 +1685,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -1735,6 +2063,296 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); } +TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpOuterProduct) { + std::string reference_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %45 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeFloat 32 + %5 = OpTypeVector %4 2 + %6 = OpTypeVector %4 3 + %7 = OpTypeVector %4 4 + %8 = OpTypeMatrix %5 2 + %9 = OpTypeMatrix %5 3 + %10 = OpTypeMatrix %5 4 + %11 = OpTypeMatrix %6 2 + %12 = OpTypeMatrix %6 3 + %13 = OpTypeMatrix %6 4 + %14 = OpTypeMatrix %7 2 + %15 = OpTypeMatrix %7 3 + %16 = OpTypeMatrix %7 4 + +; Constant scalars + %17 = OpConstant %4 1 + %18 = OpConstant %4 2 + %19 = OpConstant %4 3 + %20 = OpConstant %4 4 + %21 = OpConstant %4 5 + %22 = OpConstant %4 6 + %23 = OpConstant %4 7 + %24 = OpConstant %4 8 + %25 = OpConstant %4 9 + %26 = OpConstant %4 10 + %27 = OpConstant %4 11 + %28 = OpConstant %4 12 + %29 = OpConstant %4 13 + %30 = OpConstant %4 14 + %31 = OpConstant %4 15 + %32 = OpConstant %4 16 + +; Constant vectors + %33 = OpConstantComposite %5 %17 %18 + %34 = OpConstantComposite %5 %19 %20 + %35 = OpConstantComposite %5 %21 %22 + %36 = OpConstantComposite %5 %23 %24 + %37 = OpConstantComposite %6 %17 %18 %19 + %38 = OpConstantComposite %6 %20 %21 %22 + %39 = OpConstantComposite %6 %23 %24 %25 + %40 = OpConstantComposite %6 %26 %27 %28 + %41 = OpConstantComposite %7 %17 %18 %19 %20 + %42 = OpConstantComposite %7 %21 %22 %23 %24 + %43 = OpConstantComposite %7 %25 %26 %27 %28 + %44 = OpConstantComposite %7 %29 %30 %31 %32 + +; main function + %45 = OpFunction %2 None %3 + %46 = OpLabel + +; Multiplying 2-dimensional vector by 2-dimensional vector + %47 = OpOuterProduct %8 %33 %34 + +; Multiplying 2-dimensional vector by 3-dimensional vector + %48 = OpOuterProduct %9 %35 %37 + +; Multiplying 2-dimensional vector by 4-dimensional vector + %49 = OpOuterProduct %10 %36 %41 + +; Multiplying 3-dimensional vector by 2-dimensional vector + %50 = OpOuterProduct %11 %37 %33 + +; Multiplying 3-dimensional vector by 3-dimensional vector + %51 = OpOuterProduct %12 %38 %39 + +; Multiplying 3-dimensional vector by 4-dimensional vector + %52 = OpOuterProduct %13 %40 %41 + +; Multiplying 4-dimensional vector by 2-dimensional vector + %53 = OpOuterProduct %14 %41 %33 + +; Multiplying 4-dimensional vector by 3-dimensional vector + %54 = OpOuterProduct %15 %42 %37 + +; Multiplying 4-dimensional vector by 4-dimensional vector + %55 = OpOuterProduct %16 %43 %44 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto instruction_descriptor = + MakeInstructionDescriptor(47, SpvOpOuterProduct, 0); + auto transformation = TransformationReplaceLinearAlgebraInstruction( + {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67}, instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(48, SpvOpOuterProduct, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(49, SpvOpOuterProduct, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + instruction_descriptor = MakeInstructionDescriptor(50, SpvOpOuterProduct, 0); + transformation = TransformationReplaceLinearAlgebraInstruction( + {110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, + 124, 125}, + instruction_descriptor); + transformation.Apply(context.get(), &transformation_context); + + std::string variant_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %45 "main" + +; Types + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeFloat 32 + %5 = OpTypeVector %4 2 + %6 = OpTypeVector %4 3 + %7 = OpTypeVector %4 4 + %8 = OpTypeMatrix %5 2 + %9 = OpTypeMatrix %5 3 + %10 = OpTypeMatrix %5 4 + %11 = OpTypeMatrix %6 2 + %12 = OpTypeMatrix %6 3 + %13 = OpTypeMatrix %6 4 + %14 = OpTypeMatrix %7 2 + %15 = OpTypeMatrix %7 3 + %16 = OpTypeMatrix %7 4 + +; Constant scalars + %17 = OpConstant %4 1 + %18 = OpConstant %4 2 + %19 = OpConstant %4 3 + %20 = OpConstant %4 4 + %21 = OpConstant %4 5 + %22 = OpConstant %4 6 + %23 = OpConstant %4 7 + %24 = OpConstant %4 8 + %25 = OpConstant %4 9 + %26 = OpConstant %4 10 + %27 = OpConstant %4 11 + %28 = OpConstant %4 12 + %29 = OpConstant %4 13 + %30 = OpConstant %4 14 + %31 = OpConstant %4 15 + %32 = OpConstant %4 16 + +; Constant vectors + %33 = OpConstantComposite %5 %17 %18 + %34 = OpConstantComposite %5 %19 %20 + %35 = OpConstantComposite %5 %21 %22 + %36 = OpConstantComposite %5 %23 %24 + %37 = OpConstantComposite %6 %17 %18 %19 + %38 = OpConstantComposite %6 %20 %21 %22 + %39 = OpConstantComposite %6 %23 %24 %25 + %40 = OpConstantComposite %6 %26 %27 %28 + %41 = OpConstantComposite %7 %17 %18 %19 %20 + %42 = OpConstantComposite %7 %21 %22 %23 %24 + %43 = OpConstantComposite %7 %25 %26 %27 %28 + %44 = OpConstantComposite %7 %29 %30 %31 %32 + +; main function + %45 = OpFunction %2 None %3 + %46 = OpLabel + +; Multiplying 2-dimensional vector by 2-dimensional vector + %56 = OpCompositeExtract %4 %34 0 + %57 = OpCompositeExtract %4 %33 0 + %58 = OpFMul %4 %56 %57 + %59 = OpCompositeExtract %4 %33 1 + %60 = OpFMul %4 %56 %59 + %61 = OpCompositeConstruct %5 %58 %60 + %62 = OpCompositeExtract %4 %34 1 + %63 = OpCompositeExtract %4 %33 0 + %64 = OpFMul %4 %62 %63 + %65 = OpCompositeExtract %4 %33 1 + %66 = OpFMul %4 %62 %65 + %67 = OpCompositeConstruct %5 %64 %66 + %47 = OpCompositeConstruct %8 %61 %67 + +; Multiplying 2-dimensional vector by 3-dimensional vector + %68 = OpCompositeExtract %4 %37 0 + %69 = OpCompositeExtract %4 %35 0 + %70 = OpFMul %4 %68 %69 + %71 = OpCompositeExtract %4 %35 1 + %72 = OpFMul %4 %68 %71 + %73 = OpCompositeConstruct %5 %70 %72 + %74 = OpCompositeExtract %4 %37 1 + %75 = OpCompositeExtract %4 %35 0 + %76 = OpFMul %4 %74 %75 + %77 = OpCompositeExtract %4 %35 1 + %78 = OpFMul %4 %74 %77 + %79 = OpCompositeConstruct %5 %76 %78 + %80 = OpCompositeExtract %4 %37 2 + %81 = OpCompositeExtract %4 %35 0 + %82 = OpFMul %4 %80 %81 + %83 = OpCompositeExtract %4 %35 1 + %84 = OpFMul %4 %80 %83 + %85 = OpCompositeConstruct %5 %82 %84 + %48 = OpCompositeConstruct %9 %73 %79 %85 + +; Multiplying 2-dimensional vector by 4-dimensional vector + %86 = OpCompositeExtract %4 %41 0 + %87 = OpCompositeExtract %4 %36 0 + %88 = OpFMul %4 %86 %87 + %89 = OpCompositeExtract %4 %36 1 + %90 = OpFMul %4 %86 %89 + %91 = OpCompositeConstruct %5 %88 %90 + %92 = OpCompositeExtract %4 %41 1 + %93 = OpCompositeExtract %4 %36 0 + %94 = OpFMul %4 %92 %93 + %95 = OpCompositeExtract %4 %36 1 + %96 = OpFMul %4 %92 %95 + %97 = OpCompositeConstruct %5 %94 %96 + %98 = OpCompositeExtract %4 %41 2 + %99 = OpCompositeExtract %4 %36 0 + %100 = OpFMul %4 %98 %99 + %101 = OpCompositeExtract %4 %36 1 + %102 = OpFMul %4 %98 %101 + %103 = OpCompositeConstruct %5 %100 %102 + %104 = OpCompositeExtract %4 %41 3 + %105 = OpCompositeExtract %4 %36 0 + %106 = OpFMul %4 %104 %105 + %107 = OpCompositeExtract %4 %36 1 + %108 = OpFMul %4 %104 %107 + %109 = OpCompositeConstruct %5 %106 %108 + %49 = OpCompositeConstruct %10 %91 %97 %103 %109 + +; Multiplying 3-dimensional vector by 2-dimensional vector + %110 = OpCompositeExtract %4 %33 0 + %111 = OpCompositeExtract %4 %37 0 + %112 = OpFMul %4 %110 %111 + %113 = OpCompositeExtract %4 %37 1 + %114 = OpFMul %4 %110 %113 + %115 = OpCompositeExtract %4 %37 2 + %116 = OpFMul %4 %110 %115 + %117 = OpCompositeConstruct %6 %112 %114 %116 + %118 = OpCompositeExtract %4 %33 1 + %119 = OpCompositeExtract %4 %37 0 + %120 = OpFMul %4 %118 %119 + %121 = OpCompositeExtract %4 %37 1 + %122 = OpFMul %4 %118 %121 + %123 = OpCompositeExtract %4 %37 2 + %124 = OpFMul %4 %118 %123 + %125 = OpCompositeConstruct %6 %120 %122 %124 + %50 = OpCompositeConstruct %11 %117 %125 + +; Multiplying 3-dimensional vector by 3-dimensional vector + %51 = OpOuterProduct %12 %38 %39 + +; Multiplying 3-dimensional vector by 4-dimensional vector + %52 = OpOuterProduct %13 %40 %41 + +; Multiplying 4-dimensional vector by 2-dimensional vector + %53 = OpOuterProduct %14 %41 %33 + +; Multiplying 4-dimensional vector by 3-dimensional vector + %54 = OpOuterProduct %15 %42 %37 + +; Multiplying 4-dimensional vector by 4-dimensional vector + %55 = OpOuterProduct %16 %43 %44 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(IsEqual(env, variant_shader, context.get())); +} + TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpDot) { std::string reference_shader = R"( OpCapability Shader @@ -1779,7 +2397,7 @@ TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpDot) { BuildModule(env, consumer, reference_shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp b/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp new file mode 100644 index 00000000..93572c56 --- /dev/null +++ b/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp @@ -0,0 +1,281 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h" + +#include "source/fuzz/instruction_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationReplaceLoadStoreWithCopyMemoryTest, BasicScenarios) { + // This is a simple transformation and this test handles the main cases. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %26 %28 %31 %33 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %12 "c" + OpName %14 "d" + OpName %18 "e" + OpName %20 "f" + OpName %26 "i1" + OpName %28 "i2" + OpName %31 "g1" + OpName %33 "g2" + OpDecorate %26 Location 0 + OpDecorate %28 Location 1 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %13 = OpConstant %6 4 + %15 = OpConstant %6 5 + %16 = OpTypeFloat 32 + %17 = OpTypePointer Function %16 + %19 = OpConstant %16 2 + %21 = OpConstant %16 3 + %25 = OpTypePointer Output %6 + %26 = OpVariable %25 Output + %27 = OpConstant %6 1 + %28 = OpVariable %25 Output + %30 = OpTypePointer Private %6 + %31 = OpVariable %30 Private + %32 = OpConstant %6 0 + %33 = OpVariable %30 Private + %35 = OpTypeBool + %36 = OpConstantTrue %35 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + %14 = OpVariable %7 Function + %18 = OpVariable %17 Function + %20 = OpVariable %17 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %12 %13 + OpStore %14 %15 + OpStore %18 %19 + OpStore %20 %21 + %22 = OpLoad %6 %8 + OpCopyMemory %10 %8 + OpStore %10 %22 + %23 = OpLoad %6 %12 + OpStore %14 %23 + %24 = OpLoad %16 %18 + OpStore %20 %24 + OpStore %26 %27 + OpStore %28 %27 + %29 = OpLoad %6 %26 + OpMemoryBarrier %32 %32 + OpStore %28 %29 + OpStore %31 %32 + OpStore %33 %32 + %34 = OpLoad %6 %33 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + OpStore %31 %34 + OpBranch %38 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + auto bad_instruction_descriptor_1 = + MakeInstructionDescriptor(11, SpvOpConstant, 0); + + auto load_instruction_descriptor_1 = + MakeInstructionDescriptor(22, SpvOpLoad, 0); + auto load_instruction_descriptor_2 = + MakeInstructionDescriptor(23, SpvOpLoad, 0); + auto load_instruction_descriptor_3 = + MakeInstructionDescriptor(24, SpvOpLoad, 0); + auto load_instruction_descriptor_other_block = + MakeInstructionDescriptor(34, SpvOpLoad, 0); + auto load_instruction_descriptor_unsafe = + MakeInstructionDescriptor(29, SpvOpLoad, 0); + + auto store_instruction_descriptor_1 = + MakeInstructionDescriptor(22, SpvOpStore, 0); + auto store_instruction_descriptor_2 = + MakeInstructionDescriptor(23, SpvOpStore, 0); + auto store_instruction_descriptor_3 = + MakeInstructionDescriptor(24, SpvOpStore, 0); + auto store_instruction_descriptor_other_block = + MakeInstructionDescriptor(37, SpvOpStore, 0); + auto store_instruction_descriptor_unsafe = + MakeInstructionDescriptor(29, SpvOpStore, 0); + + // Bad: |load_instruction_descriptor| is incorrect. + auto transformation_bad_1 = TransformationReplaceLoadStoreWithCopyMemory( + bad_instruction_descriptor_1, store_instruction_descriptor_1); + ASSERT_FALSE( + transformation_bad_1.IsApplicable(context.get(), transformation_context)); + + // Bad: |store_instruction_descriptor| is incorrect. + auto transformation_bad_2 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_1, bad_instruction_descriptor_1); + ASSERT_FALSE( + transformation_bad_2.IsApplicable(context.get(), transformation_context)); + + // Bad: Intermediate values of the OpLoad and the OpStore don't match. + auto transformation_bad_3 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_1, store_instruction_descriptor_2); + ASSERT_FALSE( + transformation_bad_3.IsApplicable(context.get(), transformation_context)); + + // Bad: There is an interfering OpCopyMemory instruction between the OpLoad + // and the OpStore. + auto transformation_bad_4 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_1, store_instruction_descriptor_1); + ASSERT_FALSE( + transformation_bad_4.IsApplicable(context.get(), transformation_context)); + + // Bad: There is an interfering OpMemoryBarrier instruction between the OpLoad + // and the OpStore. + auto transformation_bad_5 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_unsafe, store_instruction_descriptor_unsafe); + ASSERT_FALSE( + transformation_bad_5.IsApplicable(context.get(), transformation_context)); + + // Bad: OpLoad and OpStore instructions are in different blocks. + auto transformation_bad_6 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_other_block, + store_instruction_descriptor_other_block); + ASSERT_FALSE( + transformation_bad_6.IsApplicable(context.get(), transformation_context)); + + auto transformation_good_1 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_2, store_instruction_descriptor_2); + ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(), + transformation_context)); + transformation_good_1.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + auto transformation_good_2 = TransformationReplaceLoadStoreWithCopyMemory( + load_instruction_descriptor_3, store_instruction_descriptor_3); + ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(), + transformation_context)); + transformation_good_2.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %26 %28 %31 %33 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "a" + OpName %10 "b" + OpName %12 "c" + OpName %14 "d" + OpName %18 "e" + OpName %20 "f" + OpName %26 "i1" + OpName %28 "i2" + OpName %31 "g1" + OpName %33 "g2" + OpDecorate %26 Location 0 + OpDecorate %28 Location 1 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %11 = OpConstant %6 3 + %13 = OpConstant %6 4 + %15 = OpConstant %6 5 + %16 = OpTypeFloat 32 + %17 = OpTypePointer Function %16 + %19 = OpConstant %16 2 + %21 = OpConstant %16 3 + %25 = OpTypePointer Output %6 + %26 = OpVariable %25 Output + %27 = OpConstant %6 1 + %28 = OpVariable %25 Output + %30 = OpTypePointer Private %6 + %31 = OpVariable %30 Private + %32 = OpConstant %6 0 + %33 = OpVariable %30 Private + %35 = OpTypeBool + %36 = OpConstantTrue %35 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %12 = OpVariable %7 Function + %14 = OpVariable %7 Function + %18 = OpVariable %17 Function + %20 = OpVariable %17 Function + OpStore %8 %9 + OpStore %10 %11 + OpStore %12 %13 + OpStore %14 %15 + OpStore %18 %19 + OpStore %20 %21 + %22 = OpLoad %6 %8 + OpCopyMemory %10 %8 + OpStore %10 %22 + %23 = OpLoad %6 %12 + OpCopyMemory %14 %12 + %24 = OpLoad %16 %18 + OpCopyMemory %20 %18 + OpStore %26 %27 + OpStore %28 %27 + %29 = OpLoad %6 %26 + OpMemoryBarrier %32 %32 + OpStore %28 %29 + OpStore %31 %32 + OpStore %33 %32 + %34 = OpLoad %6 %33 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + OpStore %31 %34 + OpBranch %38 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp new file mode 100644 index 00000000..4b91b6d3 --- /dev/null +++ b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp @@ -0,0 +1,206 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h" + +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpConstantFalse %5 + %8 = OpTypeInt 32 1 + %9 = OpConstant %8 2 + %10 = OpConstant %8 3 + %11 = OpConstant %8 4 + %12 = OpConstant %8 5 + %13 = OpConstant %8 6 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpSelectionMerge %15 None + OpBranchConditional %6 %16 %17 + %16 = OpLabel + %18 = OpCopyObject %8 %9 + OpSelectionMerge %19 None + OpBranchConditional %7 %20 %21 + %20 = OpLabel + %22 = OpCopyObject %8 %10 + %23 = OpCopyObject %8 %12 + OpBranch %19 + %21 = OpLabel + %24 = OpCopyObject %8 %9 + OpBranch %19 + %19 = OpLabel + %25 = OpPhi %8 %22 %20 %24 %21 + OpBranch %15 + %17 = OpLabel + %26 = OpCopyObject %8 %12 + %27 = OpCopyObject %8 %13 + OpBranch %28 + %28 = OpLabel + %29 = OpPhi %8 %27 %17 + OpBranch %15 + %15 = OpLabel + %30 = OpPhi %8 %25 %19 %26 %28 + OpReturn + OpFunctionEnd +)"; + +TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Inapplicable) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + // Record the fact that blocks 20, 17, 28 are dead. + transformation_context.GetFactManager()->AddFactBlockIsDead(20); + transformation_context.GetFactManager()->AddFactBlockIsDead(17); + transformation_context.GetFactManager()->AddFactBlockIsDead(28); + + // %26 is not an OpPhi instruction. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(26, 14, 10) + .IsApplicable(context.get(), transformation_context)); + + // %25 is not a block label. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 25, 10) + .IsApplicable(context.get(), transformation_context)); + + // %14 is not a predecessor of %28 (which contains %29). + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(29, 14, 10) + .IsApplicable(context.get(), transformation_context)); + + // %19 is not a dead block. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 19, 10) + .IsApplicable(context.get(), transformation_context)); + + // %7 does not have the same type id as %25. + ASSERT_FALSE( + TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 7).IsApplicable( + context.get(), transformation_context)); + + // %29 is not available at the end of %20. + ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 29) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Apply) { + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + ASSERT_TRUE(IsValid(env, context.get())); + + // Record the fact that blocks 20, 17, 28 are dead. + transformation_context.GetFactManager()->AddFactBlockIsDead(20); + transformation_context.GetFactManager()->AddFactBlockIsDead(17); + transformation_context.GetFactManager()->AddFactBlockIsDead(28); + + auto transformation1 = + TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 18); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + transformation1.Apply(context.get(), &transformation_context); + + auto transformation2 = + TransformationReplaceOpPhiIdFromDeadPredecessor(30, 28, 29); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + auto transformation3 = + TransformationReplaceOpPhiIdFromDeadPredecessor(29, 17, 10); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformations = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %7 = OpConstantFalse %5 + %8 = OpTypeInt 32 1 + %9 = OpConstant %8 2 + %10 = OpConstant %8 3 + %11 = OpConstant %8 4 + %12 = OpConstant %8 5 + %13 = OpConstant %8 6 + %2 = OpFunction %3 None %4 + %14 = OpLabel + OpSelectionMerge %15 None + OpBranchConditional %6 %16 %17 + %16 = OpLabel + %18 = OpCopyObject %8 %9 + OpSelectionMerge %19 None + OpBranchConditional %7 %20 %21 + %20 = OpLabel + %22 = OpCopyObject %8 %10 + %23 = OpCopyObject %8 %12 + OpBranch %19 + %21 = OpLabel + %24 = OpCopyObject %8 %9 + OpBranch %19 + %19 = OpLabel + %25 = OpPhi %8 %18 %20 %24 %21 + OpBranch %15 + %17 = OpLabel + %26 = OpCopyObject %8 %12 + %27 = OpCopyObject %8 %13 + OpBranch %28 + %28 = OpLabel + %29 = OpPhi %8 %10 %17 + OpBranch %15 + %15 = OpLabel + %30 = OpPhi %8 %25 %19 %29 %28 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformations, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp new file mode 100644 index 00000000..1aa77526 --- /dev/null +++ b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp @@ -0,0 +1,277 @@ +// Copyright (c) 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h" + +#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Inapplicable) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpTypeVector %5 4 + %9 = OpConstantNull %8 + %10 = OpConstantComposite %8 %6 %6 %7 %7 + %11 = OpTypeBool + %12 = OpTypeVector %11 4 + %13 = OpConstantTrue %11 + %14 = OpConstantFalse %11 + %15 = OpConstantComposite %12 %13 %14 %14 %13 + %2 = OpFunction %3 None %4 + %16 = OpLabel + %17 = OpCopyObject %5 %6 + %18 = OpCopyObject %5 %7 + OpBranch %19 + %19 = OpLabel + %20 = OpCopyObject %5 %17 + %21 = OpSelect %5 %13 %17 %18 + OpBranch %22 + %22 = OpLabel + %23 = OpSelect %8 %15 %9 %10 + OpBranch %24 + %24 = OpLabel + OpSelectionMerge %25 None + OpBranchConditional %13 %26 %27 + %26 = OpLabel + %28 = OpSelect %5 %13 %17 %18 + OpBranch %27 + %27 = OpLabel + %29 = OpSelect %5 %13 %17 %18 + OpBranch %25 + %25 = OpLabel + %30 = OpSelect %5 %13 %17 %18 + OpBranch %31 + %31 = OpLabel + OpLoopMerge %32 %33 None + OpBranch %33 + %33 = OpLabel + %34 = OpSelect %5 %13 %17 %18 + OpBranchConditional %13 %31 %32 + %32 = OpLabel + %35 = OpSelect %5 %13 %17 %18 + OpBranch %36 + %36 = OpLabel + %37 = OpSelect %5 %13 %17 %18 + OpReturn + OpFunctionEnd +)"; + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + // %20 is not an OpSelect instruction. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // %21 is not the first instruction in its block. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(21, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The condition for %23 is not a scalar, but a vector of booleans. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(23, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The predecessor (%24) of the block containing %28 is the header of a + // selection construct and does not branch unconditionally. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(24, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The block containing %29 has two predecessors (%24 and %26). + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(29, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The block containing %30 is the merge block for a selection construct. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(30, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The predecessor (%31) of the block containing %34 is a loop header. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(31, 100, 101) + .IsApplicable(context.get(), transformation_context)); + + // The block containing %35 is the merge block for a loop construct. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(35, 100, 101) + .IsApplicable(context.get(), transformation_context)); + +#ifndef NDEBUG + // |true_block_id| and |false_block_id| are both 0. + ASSERT_DEATH( + TransformationReplaceOpSelectWithConditionalBranch(37, 0, 0).IsApplicable( + context.get(), transformation_context), + "At least one of the ids must be non-zero."); +#endif + + // The fresh ids are not distinct. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 100) + .IsApplicable(context.get(), transformation_context)); + + // One of the ids is not fresh. + ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 10) + .IsApplicable(context.get(), transformation_context)); +} + +TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Simple) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpTypeVector %5 4 + %9 = OpConstantNull %8 + %10 = OpConstantComposite %8 %6 %6 %7 %7 + %11 = OpTypeBool + %12 = OpTypeVector %11 4 + %13 = OpConstantTrue %11 + %14 = OpConstantFalse %11 + %15 = OpConstantComposite %12 %13 %14 %14 %13 + %2 = OpFunction %3 None %4 + %16 = OpLabel + %17 = OpCopyObject %5 %6 + %18 = OpCopyObject %5 %7 + OpBranch %19 + %19 = OpLabel + %20 = OpSelect %5 %13 %17 %18 + OpSelectionMerge %21 None + OpBranchConditional %13 %22 %21 + %22 = OpLabel + OpBranch %23 + %23 = OpLabel + %24 = OpSelect %8 %13 %9 %10 + OpBranch %21 + %21 = OpLabel + OpBranch %25 + %25 = OpLabel + %26 = OpSelect %5 %13 %17 %18 + OpReturn + OpFunctionEnd +)"; + + const auto env = SPV_ENV_UNIVERSAL_1_5; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + auto transformation = + TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + + auto transformation2 = + TransformationReplaceOpSelectWithConditionalBranch(24, 0, 102); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + transformation2.Apply(context.get(), &transformation_context); + + auto transformation3 = + TransformationReplaceOpSelectWithConditionalBranch(26, 103, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + transformation3.Apply(context.get(), &transformation_context); + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpConstant %5 1 + %7 = OpConstant %5 2 + %8 = OpTypeVector %5 4 + %9 = OpConstantNull %8 + %10 = OpConstantComposite %8 %6 %6 %7 %7 + %11 = OpTypeBool + %12 = OpTypeVector %11 4 + %13 = OpConstantTrue %11 + %14 = OpConstantFalse %11 + %15 = OpConstantComposite %12 %13 %14 %14 %13 + %2 = OpFunction %3 None %4 + %16 = OpLabel + %17 = OpCopyObject %5 %6 + %18 = OpCopyObject %5 %7 + OpSelectionMerge %19 None + OpBranchConditional %13 %100 %101 + %100 = OpLabel + OpBranch %19 + %101 = OpLabel + OpBranch %19 + %19 = OpLabel + %20 = OpPhi %5 %17 %100 %18 %101 + OpSelectionMerge %21 None + OpBranchConditional %13 %22 %21 + %22 = OpLabel + OpSelectionMerge %23 None + OpBranchConditional %13 %23 %102 + %102 = OpLabel + OpBranch %23 + %23 = OpLabel + %24 = OpPhi %8 %9 %22 %10 %102 + OpBranch %21 + %21 = OpLabel + OpSelectionMerge %25 None + OpBranchConditional %13 %103 %25 + %103 = OpLabel + OpBranch %25 + %25 = OpLabel + %26 = OpPhi %5 %17 %103 %18 %21 + OpReturn + OpFunctionEnd +)"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/test/fuzz/transformation_replace_parameter_with_global_test.cpp b/test/fuzz/transformation_replace_parameter_with_global_test.cpp index 6ee0d697..1e0145ed 100644 --- a/test/fuzz/transformation_replace_parameter_with_global_test.cpp +++ b/test/fuzz/transformation_replace_parameter_with_global_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_replace_parameter_with_global.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -115,7 +116,7 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -218,11 +219,9 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) { %71 = OpTypeFunction %2 %6 %83 = OpTypeFunction %2 %6 %12 %93 = OpTypeFunction %2 %10 - %94 = OpTypeFunction %2 %8 %10 %40 = OpTypePointer Function %12 %13 = OpTypeStruct %6 %8 %14 = OpTypePointer Private %13 - %15 = OpTypeFunction %2 %40 %12 %22 = OpConstant %6 0 %23 = OpConstant %8 0 %26 = OpConstantComposite %10 %23 %23 @@ -232,6 +231,7 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) { %53 = OpVariable %9 Private %23 %55 = OpVariable %11 Private %26 %57 = OpVariable %14 Private %28 + %15 = OpTypeFunction %2 %40 %12 %59 = OpVariable %7 Private %22 %61 = OpVariable %7 Private %22 %60 = OpTypeFunction %2 %12 @@ -294,6 +294,61 @@ TEST(TransformationReplaceParameterWithGlobalTest, BasicTest) { ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); } +TEST(TransformationReplaceParameterWithGlobalTest, + HandlesIrrelevantParameters) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %9 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %7 = OpTypeFunction %2 %9 %9 + %12 = OpTypePointer Private %9 + %13 = OpConstant %9 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %7 + %10 = OpFunctionParameter %9 + %11 = OpFunctionParameter %9 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(10); + + { + TransformationReplaceParameterWithGlobal transformation(20, 10, 21); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(21)); + } + { + TransformationReplaceParameterWithGlobal transformation(22, 11, 23); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(23)); + } +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_replace_params_with_struct_test.cpp b/test/fuzz/transformation_replace_params_with_struct_test.cpp index 6de36e1e..af3d4d3b 100644 --- a/test/fuzz/transformation_replace_params_with_struct_test.cpp +++ b/test/fuzz/transformation_replace_params_with_struct_test.cpp @@ -124,7 +124,7 @@ TEST(TransformationReplaceParamsWithStructTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -255,7 +255,6 @@ TEST(TransformationReplaceParamsWithStructTest, BasicTest) { %65 = OpTypeFunction %2 %6 %75 = OpTypeStruct %8 %76 = OpTypeFunction %2 %75 - %77 = OpTypeFunction %2 %8 %40 = OpTypePointer Function %12 %13 = OpTypeStruct %6 %8 %45 = OpTypeStruct %6 %10 %13 @@ -334,6 +333,147 @@ TEST(TransformationReplaceParamsWithStructTest, BasicTest) { ASSERT_TRUE(IsEqual(env, expected_shader, context.get())); } +TEST(TransformationReplaceParamsWithStructTest, ParametersRemainValid) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %6 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %8 = OpTypeFloat 32 + %10 = OpTypeVector %8 2 + %12 = OpTypeBool + %40 = OpTypePointer Function %12 + %13 = OpTypeStruct %6 %8 + %45 = OpTypeStruct %6 %8 %13 + %47 = OpTypeStruct %45 %12 %10 + %15 = OpTypeFunction %2 %6 %8 %10 %13 %40 %12 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %2 None %15 + %16 = OpFunctionParameter %6 + %17 = OpFunctionParameter %8 + %18 = OpFunctionParameter %10 + %19 = OpFunctionParameter %13 + %42 = OpFunctionParameter %40 + %43 = OpFunctionParameter %12 + %21 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + { + // Try to replace parameters in "increasing" order of their declaration. + TransformationReplaceParamsWithStruct transformation({16, 17, 19}, 70, 71, + {{}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %6 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %8 = OpTypeFloat 32 + %10 = OpTypeVector %8 2 + %12 = OpTypeBool + %40 = OpTypePointer Function %12 + %13 = OpTypeStruct %6 %8 + %45 = OpTypeStruct %6 %8 %13 + %47 = OpTypeStruct %45 %12 %10 + %15 = OpTypeFunction %2 %10 %40 %12 %45 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %2 None %15 + %18 = OpFunctionParameter %10 + %42 = OpFunctionParameter %40 + %43 = OpFunctionParameter %12 + %71 = OpFunctionParameter %45 + %21 = OpLabel + %19 = OpCompositeExtract %13 %71 2 + %17 = OpCompositeExtract %8 %71 1 + %16 = OpCompositeExtract %6 %71 0 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); + + { + // Try to replace parameters in "decreasing" order of their declaration. + TransformationReplaceParamsWithStruct transformation({71, 43, 18}, 72, 73, + {{}}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + } + + after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %6 = OpTypeInt 32 1 + %3 = OpTypeFunction %2 + %8 = OpTypeFloat 32 + %10 = OpTypeVector %8 2 + %12 = OpTypeBool + %40 = OpTypePointer Function %12 + %13 = OpTypeStruct %6 %8 + %45 = OpTypeStruct %6 %8 %13 + %47 = OpTypeStruct %45 %12 %10 + %15 = OpTypeFunction %2 %40 %47 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + %20 = OpFunction %2 None %15 + %42 = OpFunctionParameter %40 + %73 = OpFunctionParameter %47 + %21 = OpLabel + %18 = OpCompositeExtract %10 %73 2 + %43 = OpCompositeExtract %12 %73 1 + %71 = OpCompositeExtract %45 %73 0 + %19 = OpCompositeExtract %13 %71 2 + %17 = OpCompositeExtract %8 %71 1 + %16 = OpCompositeExtract %6 %71 0 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_set_function_control_test.cpp b/test/fuzz/transformation_set_function_control_test.cpp index be7f2be4..901105b5 100644 --- a/test/fuzz/transformation_set_function_control_test.cpp +++ b/test/fuzz/transformation_set_function_control_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_set_function_control.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -117,7 +118,7 @@ TEST(TransformationSetFunctionControlTest, VariousScenarios) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_set_loop_control_test.cpp b/test/fuzz/transformation_set_loop_control_test.cpp index 531aa7ad..671302e5 100644 --- a/test/fuzz/transformation_set_loop_control_test.cpp +++ b/test/fuzz/transformation_set_loop_control_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_set_loop_control.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -255,7 +256,7 @@ TEST(TransformationSetLoopControlTest, VariousScenarios) { ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -930,43 +931,42 @@ TEST(TransformationSetLoopControlTest, CheckSPIRVVersionsRespected) { OpFunctionEnd )"; - const auto consumer = nullptr; - const auto context_1_0 = - BuildModule(SPV_ENV_UNIVERSAL_1_0, consumer, shader, kFuzzAssembleOption); - const auto context_1_1 = - BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, shader, kFuzzAssembleOption); - const auto context_1_2 = - BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer, shader, kFuzzAssembleOption); - const auto context_1_3 = - BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer, shader, kFuzzAssembleOption); - const auto context_1_4 = - BuildModule(SPV_ENV_UNIVERSAL_1_4, consumer, shader, kFuzzAssembleOption); - const auto context_1_5 = - BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption); - - FactManager fact_manager; - spvtools::ValidatorOptions validator_options; - TransformationContext transformation_context(&fact_manager, - validator_options); + for (auto env : + {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2, + SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5}) { + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); - TransformationSetLoopControl set_peel_and_partial( - 10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4); + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); - // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not valid - // in the context of older versions. - ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_0.get(), - transformation_context)); - ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_1.get(), - transformation_context)); - ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_2.get(), - transformation_context)); - ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_3.get(), - transformation_context)); + TransformationSetLoopControl transformation( + 10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4); - ASSERT_TRUE(set_peel_and_partial.IsApplicable(context_1_4.get(), - transformation_context)); - ASSERT_TRUE(set_peel_and_partial.IsApplicable(context_1_5.get(), - transformation_context)); + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_UNIVERSAL_1_1: + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_UNIVERSAL_1_3: + // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not + // valid in the context of older versions. + ASSERT_FALSE( + transformation.IsApplicable(context.get(), transformation_context)); + break; + case SPV_ENV_UNIVERSAL_1_4: + case SPV_ENV_UNIVERSAL_1_5: + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + break; + default: + assert(false && "Unhandled environment"); + break; + } + } } } // namespace diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp index 518ce9d4..eb16c3e2 100644 --- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp +++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_set_memory_operands_mask.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -92,7 +93,7 @@ TEST(TransformationSetMemoryOperandsMaskTest, PreSpirv14) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -337,7 +338,7 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_set_selection_control_test.cpp b/test/fuzz/transformation_set_selection_control_test.cpp index 9afb89d6..a6a9e333 100644 --- a/test/fuzz/transformation_set_selection_control_test.cpp +++ b/test/fuzz/transformation_set_selection_control_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_set_selection_control.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { @@ -102,7 +103,7 @@ TEST(TransformationSetSelectionControlTest, VariousScenarios) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp index 30bac026..6d94a422 100644 --- a/test/fuzz/transformation_split_block_test.cpp +++ b/test/fuzz/transformation_split_block_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_split_block.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -88,7 +89,7 @@ TEST(TransformationSplitBlockTest, NotApplicable) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -201,7 +202,7 @@ TEST(TransformationSplitBlockTest, SplitBlockSeveralTimes) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -417,7 +418,7 @@ TEST(TransformationSplitBlockTest, SplitBlockBeforeSelectBranch) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -549,7 +550,7 @@ TEST(TransformationSplitBlockTest, SplitBlockBeforeSwitchBranch) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -685,7 +686,7 @@ TEST(TransformationSplitBlockTest, NoSplitDuringOpPhis) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -740,7 +741,7 @@ TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -822,7 +823,7 @@ TEST(TransformationSplitBlockTest, DeadBlockShouldSplitToTwoDeadBlocks) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -912,7 +913,7 @@ TEST(TransformationSplitBlockTest, DoNotSplitUseOfOpSampledImage) { const auto consumer = nullptr; const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_store_test.cpp b/test/fuzz/transformation_store_test.cpp index 07d222f0..fb38aa1e 100644 --- a/test/fuzz/transformation_store_test.cpp +++ b/test/fuzz/transformation_store_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_store.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -93,7 +94,7 @@ TEST(TransformationStoreTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -397,7 +398,7 @@ TEST(TransformationStoreTest, DoNotAllowStoresToReadOnlyMemory) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_swap_commutable_operands_test.cpp b/test/fuzz/transformation_swap_commutable_operands_test.cpp index c213dfea..d0423167 100644 --- a/test/fuzz/transformation_swap_commutable_operands_test.cpp +++ b/test/fuzz/transformation_swap_commutable_operands_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_swap_commutable_operands.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -111,7 +112,7 @@ TEST(TransformationSwapCommutableOperandsTest, IsApplicableTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -338,7 +339,7 @@ TEST(TransformationSwapCommutableOperandsTest, ApplyTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp index 4383e072..76c45eff 100644 --- a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp +++ b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_swap_conditional_branch_operands.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -68,7 +69,7 @@ TEST(TransformationSwapConditionalBranchOperandsTest, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp index b20f59e0..8b097a13 100644 --- a/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp +++ b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_toggle_access_chain_instruction.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -111,7 +112,7 @@ TEST(TransformationToggleAccessChainInstructionTest, IsApplicableTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -307,7 +308,7 @@ TEST(TransformationToggleAccessChainInstructionTest, ApplyTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); diff --git a/test/fuzz/transformation_vector_shuffle_test.cpp b/test/fuzz/transformation_vector_shuffle_test.cpp index a29c511b..71f44d9a 100644 --- a/test/fuzz/transformation_vector_shuffle_test.cpp +++ b/test/fuzz/transformation_vector_shuffle_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/transformation_vector_shuffle.h" + #include "source/fuzz/instruction_descriptor.h" #include "test/fuzz/fuzz_test_util.h" @@ -85,89 +86,89 @@ TEST(TransformationVectorShuffle, BasicTest) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0}), context.get()); + MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(11, {}), MakeDataDescriptor(12, {1}), context.get()); + MakeDataDescriptor(11, {}), MakeDataDescriptor(12, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {0}), context.get()); + MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(11, {}), MakeDataDescriptor(16, {1}), context.get()); + MakeDataDescriptor(11, {}), MakeDataDescriptor(16, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {2}), context.get()); + MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {0}), context.get()); + MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {1}), context.get()); + MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {2}), context.get()); + MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {3}), context.get()); + MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {3})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(25, {}), MakeDataDescriptor(27, {0}), context.get()); + MakeDataDescriptor(25, {}), MakeDataDescriptor(27, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {1}), context.get()); + MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {0}), context.get()); + MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(26, {}), MakeDataDescriptor(31, {1}), context.get()); + MakeDataDescriptor(26, {}), MakeDataDescriptor(31, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {2}), context.get()); + MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {0}), context.get()); + MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {1}), context.get()); + MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {2}), context.get()); + MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {3}), context.get()); + MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {3})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(40, {}), MakeDataDescriptor(42, {0}), context.get()); + MakeDataDescriptor(40, {}), MakeDataDescriptor(42, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(41, {}), MakeDataDescriptor(42, {1}), context.get()); + MakeDataDescriptor(41, {}), MakeDataDescriptor(42, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {0}), context.get()); + MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(41, {}), MakeDataDescriptor(46, {1}), context.get()); + MakeDataDescriptor(41, {}), MakeDataDescriptor(46, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {2}), context.get()); + MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {0}), context.get()); + MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {1}), context.get()); + MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {2}), context.get()); + MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {3}), context.get()); + MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {3})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {0}), context.get()); + MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(56, {}), MakeDataDescriptor(61, {1}), context.get()); + MakeDataDescriptor(56, {}), MakeDataDescriptor(61, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {2}), context.get()); + MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {0}), context.get()); + MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {0})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {1}), context.get()); + MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {1})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {2}), context.get()); + MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {2})); transformation_context.GetFactManager()->AddFactDataSynonym( - MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {3}), context.get()); + MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {3})); // %103 does not dominate the return instruction. ASSERT_FALSE(TransformationVectorShuffle( @@ -488,7 +489,7 @@ TEST(TransformationVectorShuffleTest, IllegalInsertionPoints) { const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(IsValid(env, context.get())); - FactManager fact_manager; + FactManager fact_manager(context.get()); spvtools::ValidatorOptions validator_options; TransformationContext transformation_context(&fact_manager, validator_options); @@ -540,6 +541,171 @@ TEST(TransformationVectorShuffleTest, IllegalInsertionPoints) { .IsApplicable(context.get(), transformation_context)); } +TEST(TransformationVectorShuffle, HandlesIrrelevantIds1) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpTypeVector %6 2 + %10 = OpConstantTrue %6 + %11 = OpConstantFalse %6 + %12 = OpConstantComposite %7 %10 %11 + %112 = OpConstantComposite %7 %11 %10 + %13 = OpTypeVector %6 3 + %16 = OpConstantComposite %13 %10 %11 %10 + %17 = OpTypeVector %6 4 + %20 = OpConstantComposite %17 %10 %11 %10 %11 + %21 = OpTypeInt 32 1 + %22 = OpTypeVector %21 2 + %25 = OpConstant %21 1 + %26 = OpConstant %21 0 + %27 = OpConstantComposite %22 %25 %26 + %28 = OpTypeVector %21 3 + %31 = OpConstantComposite %28 %25 %26 %25 + %32 = OpTypeVector %21 4 + %33 = OpTypePointer Function %32 + %35 = OpConstantComposite %32 %25 %26 %25 %26 + %36 = OpTypeInt 32 0 + %37 = OpTypeVector %36 2 + %40 = OpConstant %36 1 + %41 = OpConstant %36 0 + %42 = OpConstantComposite %37 %40 %41 + %43 = OpTypeVector %36 3 + %46 = OpConstantComposite %43 %40 %41 %40 + %47 = OpTypeVector %36 4 + %50 = OpConstantComposite %47 %40 %41 %40 %41 + %51 = OpTypeFloat 32 + %55 = OpConstant %51 1 + %56 = OpConstant %51 0 + %58 = OpTypeVector %51 3 + %61 = OpConstantComposite %58 %55 %56 %55 + %62 = OpTypeVector %51 4 + %65 = OpConstantComposite %62 %55 %56 %55 %56 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %100 None + OpBranchConditional %10 %101 %102 + %101 = OpLabel + %103 = OpCompositeConstruct %62 %55 %55 %55 %56 + OpBranch %100 + %102 = OpLabel + OpBranch %100 + %100 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + TransformationVectorShuffle transformation( + MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {2, 0}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}), + MakeDataDescriptor(200, {1}))); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(112, {0}), + MakeDataDescriptor(200, {0}))); +} + +TEST(TransformationVectorShuffle, HandlesIrrelevantIds2) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpTypeVector %6 2 + %10 = OpConstantTrue %6 + %11 = OpConstantFalse %6 + %12 = OpConstantComposite %7 %10 %11 + %112 = OpConstantComposite %7 %11 %10 + %13 = OpTypeVector %6 3 + %16 = OpConstantComposite %13 %10 %11 %10 + %17 = OpTypeVector %6 4 + %20 = OpConstantComposite %17 %10 %11 %10 %11 + %21 = OpTypeInt 32 1 + %22 = OpTypeVector %21 2 + %25 = OpConstant %21 1 + %26 = OpConstant %21 0 + %27 = OpConstantComposite %22 %25 %26 + %28 = OpTypeVector %21 3 + %31 = OpConstantComposite %28 %25 %26 %25 + %32 = OpTypeVector %21 4 + %33 = OpTypePointer Function %32 + %35 = OpConstantComposite %32 %25 %26 %25 %26 + %36 = OpTypeInt 32 0 + %37 = OpTypeVector %36 2 + %40 = OpConstant %36 1 + %41 = OpConstant %36 0 + %42 = OpConstantComposite %37 %40 %41 + %43 = OpTypeVector %36 3 + %46 = OpConstantComposite %43 %40 %41 %40 + %47 = OpTypeVector %36 4 + %50 = OpConstantComposite %47 %40 %41 %40 %41 + %51 = OpTypeFloat 32 + %55 = OpConstant %51 1 + %56 = OpConstant %51 0 + %58 = OpTypeVector %51 3 + %61 = OpConstantComposite %58 %55 %56 %55 + %62 = OpTypeVector %51 4 + %65 = OpConstantComposite %62 %55 %56 %55 %56 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %100 None + OpBranchConditional %10 %101 %102 + %101 = OpLabel + %103 = OpCompositeConstruct %62 %55 %55 %55 %56 + OpBranch %100 + %102 = OpLabel + OpBranch %100 + %100 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_4; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager(context.get()); + spvtools::ValidatorOptions validator_options; + TransformationContext transformation_context(&fact_manager, + validator_options); + + fact_manager.AddFactIdIsIrrelevant(112); + TransformationVectorShuffle transformation( + MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {2, 0}); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + transformation.Apply(context.get(), &transformation_context); + ASSERT_TRUE(IsValid(env, context.get())); + ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}), + MakeDataDescriptor(200, {1}))); + ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(112, {0}), + MakeDataDescriptor(200, {0}))); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/uniform_buffer_element_descriptor_test.cpp b/test/fuzz/uniform_buffer_element_descriptor_test.cpp index 6c6d52a0..9bc1cff5 100644 --- a/test/fuzz/uniform_buffer_element_descriptor_test.cpp +++ b/test/fuzz/uniform_buffer_element_descriptor_test.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "source/fuzz/uniform_buffer_element_descriptor.h" + #include "test/fuzz/fuzz_test_util.h" namespace spvtools { diff --git a/test/link/binary_version_test.cpp b/test/link/binary_version_test.cpp index 0ceeebae..80aab0fd 100644 --- a/test/link/binary_version_test.cpp +++ b/test/link/binary_version_test.cpp @@ -27,21 +27,21 @@ TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) { spvtest::Binaries binaries = { { SpvMagicNumber, - 0x00000300u, + 0x00010300u, SPV_GENERATOR_CODEPLAY, 1u, // NOTE: Bound 0u // NOTE: Schema; reserved }, { SpvMagicNumber, - 0x00000600u, + 0x00010500u, SPV_GENERATOR_CODEPLAY, 1u, // NOTE: Bound 0u // NOTE: Schema; reserved }, { SpvMagicNumber, - 0x00000100u, + 0x00010100u, SPV_GENERATOR_CODEPLAY, 1u, // NOTE: Bound 0u // NOTE: Schema; reserved @@ -53,7 +53,7 @@ TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) { ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)); EXPECT_THAT(GetErrorMessage(), std::string()); - EXPECT_EQ(0x00000600u, linked_binary[1]); + EXPECT_EQ(0x00010500u, linked_binary[1]); } } // namespace diff --git a/test/opcode_table_get_test.cpp b/test/opcode_table_get_test.cpp index 5ebd6c11..4ff67d95 100644 --- a/test/opcode_table_get_test.cpp +++ b/test/opcode_table_get_test.cpp @@ -21,7 +21,7 @@ namespace { using GetTargetOpcodeTableGetTest = ::testing::TestWithParam<spv_target_env>; using ::testing::ValuesIn; -TEST_P(GetTargetOpcodeTableGetTest, SanityCheck) { +TEST_P(GetTargetOpcodeTableGetTest, IntegrityCheck) { spv_opcode_table table; ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam())); ASSERT_NE(0u, table->count); diff --git a/test/operand_test.cpp b/test/operand_test.cpp index 4e2c3215..ec45da58 100644 --- a/test/operand_test.cpp +++ b/test/operand_test.cpp @@ -42,9 +42,16 @@ TEST(OperandString, AllAreDefinedExceptVariable) { // None has no string, so don't test it. EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE); // Start testing at enum with value 1, skipping None. - for (int i = 1; i < int(SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE); i++) { - EXPECT_NE(nullptr, spvOperandTypeStr(static_cast<spv_operand_type_t>(i))) - << " Operand type " << i; + for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) { + const auto type = static_cast<spv_operand_type_t>(i); + if (spvOperandIsVariable(type)) { + EXPECT_STREQ("unknown", spvOperandTypeStr(type)) + << " variable type " << i << " has a name '" + << spvOperandTypeStr(type) << "'when it should not"; + } else { + EXPECT_STRNE("unknown", spvOperandTypeStr(type)) + << " operand type " << i << " has no name when it should"; + } } } @@ -71,5 +78,46 @@ TEST(OperandIsConcreteMask, Sample) { spvOperandIsConcreteMask(SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS)); } +TEST(OperandType, NoneTypeClassification) { + EXPECT_FALSE(spvOperandIsConcrete(SPV_OPERAND_TYPE_NONE)); + EXPECT_FALSE(spvOperandIsOptional(SPV_OPERAND_TYPE_NONE)); + EXPECT_FALSE(spvOperandIsVariable(SPV_OPERAND_TYPE_NONE)); +} + +TEST(OperandType, EndSentinelTypeClassification) { + EXPECT_FALSE(spvOperandIsConcrete(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES)); + EXPECT_FALSE(spvOperandIsOptional(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES)); + EXPECT_FALSE(spvOperandIsVariable(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES)); +} + +TEST(OperandType, WidthForcingTypeClassification) { + EXPECT_FALSE(spvOperandIsConcrete(SPV_FORCE_32BIT_spv_operand_type_t)); + EXPECT_FALSE(spvOperandIsOptional(SPV_FORCE_32BIT_spv_operand_type_t)); + EXPECT_FALSE(spvOperandIsVariable(SPV_FORCE_32BIT_spv_operand_type_t)); +} + +TEST(OperandType, EachTypeIsEitherConcreteOrOptionalNotBoth) { + EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE); + // Start testing at enum with value 1, skipping None. + for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) { + const auto type = static_cast<spv_operand_type_t>(i); + EXPECT_NE(spvOperandIsConcrete(type), spvOperandIsOptional(type)) + << " operand type " << int(type) << " concrete? " + << int(spvOperandIsConcrete(type)) << " optional? " + << int(spvOperandIsOptional(type)); + } +} + +TEST(OperandType, EachVariableTypeIsOptional) { + EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE); + // Start testing at enum with value 1, skipping None. + for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) { + const auto type = static_cast<spv_operand_type_t>(i); + if (spvOperandIsVariable(type)) { + EXPECT_TRUE(spvOperandIsOptional(type)) << " variable type " << int(type); + } + } +} + } // namespace } // namespace spvtools diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp index 125543d6..972e6e57 100644 --- a/test/opt/aggressive_dead_code_elim_test.cpp +++ b/test/opt/aggressive_dead_code_elim_test.cpp @@ -7525,6 +7525,62 @@ TEST_F(AggressiveDCETest, DebugInfoElimUnusedTextureKeepGlobalVariable) { SinglePassRunAndMatch<AggressiveDCEPass>(text, true); } +TEST_F(AggressiveDCETest, KeepDebugScopeParent) { + // Verify that local variable tc and its store are kept by DebugDeclare. + // + // Same shader source as DebugInfoInFunctionKeepStoreVarElim. The SPIR-V + // has just been inlined. + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "OpenCL.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %out_var_SV_TARGET0 + OpExecutionMode %main OriginUpperLeft + %11 = OpString "float" + %16 = OpString "t.hlsl" + %19 = OpString "src.main" + OpName %out_var_SV_TARGET0 "out.var.SV_TARGET0" + OpName %main "main" + OpDecorate %out_var_SV_TARGET0 Location 0 + %float = OpTypeFloat 32 + %float_0 = OpConstant %float 0 + %v4float = OpTypeVector %float 4 + %7 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 + %uint = OpTypeInt 32 0 + %uint_32 = OpConstant %uint 32 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %23 = OpTypeFunction %void + %26 = OpTypeFunction %v4float +%out_var_SV_TARGET0 = OpVariable %_ptr_Output_v4float Output +%_ptr_Function_v4float = OpTypePointer Function %v4float + %33 = OpExtInst %void %1 DebugInfoNone + %13 = OpExtInst %void %1 DebugTypeBasic %11 %uint_32 Float + %14 = OpExtInst %void %1 DebugTypeVector %13 4 + %15 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %14 + %17 = OpExtInst %void %1 DebugSource %16 + %18 = OpExtInst %void %1 DebugCompilationUnit 1 4 %17 HLSL + %20 = OpExtInst %void %1 DebugFunction %19 %15 %17 1 1 %18 %19 FlagIsProtected|FlagIsPrivate 2 %33 + %22 = OpExtInst %void %1 DebugLexicalBlock %17 2 1 %20 + %main = OpFunction %void None %23 + %24 = OpLabel + %31 = OpVariable %_ptr_Function_v4float Function +; CHECK: [[block:%\w+]] = OpExtInst %void %1 DebugLexicalBlock +; CHECK: DebugScope [[block]] + %34 = OpExtInst %void %1 DebugScope %22 + OpLine %16 3 5 + OpStore %31 %7 + OpStore %out_var_SV_TARGET0 %7 + %35 = OpExtInst %void %1 DebugNoScope + OpReturn + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<AggressiveDCEPass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Check that logical addressing required diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp index 52a291c8..943bc6c0 100644 --- a/test/opt/ccp_test.cpp +++ b/test/opt/ccp_test.cpp @@ -598,6 +598,11 @@ OpExecutionMode %func OriginUpperLeft %int_ptr_Input = OpTypePointer Input %int %in = OpVariable %int_ptr_Input Input %undef = OpUndef %int + +; Although no constants are propagated in this function, the propagator +; generates a new %true value while visiting conditional statements. +; CHECK: %true = OpConstantTrue %bool + %functy = OpTypeFunction %void %func = OpFunction %void None %functy %1 = OpLabel @@ -639,8 +644,8 @@ OpReturn OpFunctionEnd )"; - auto res = SinglePassRunToBinary<CCPPass>(text, true); - EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange); + auto result = SinglePassRunAndMatch<CCPPass>(text, true); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } TEST_F(CCPTest, UndefInPhi) { @@ -1056,6 +1061,103 @@ TEST_F(CCPTest, DebugFoldMultipleForSingleConstant) { SinglePassRunAndMatch<CCPPass>(text, true); } +// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 +TEST_F(CCPTest, CCPNoChangeFailure) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 2 + %13 = OpConstant %6 4 + %21 = OpConstant %6 1 + %10 = OpTypeBool + %17 = OpTypePointer Function %6 + +; CCP is generating two new constants during propagation that end up being +; dead because they cannot be replaced anywhere in the IR. CCP was wrongly +; considering the IR to be unmodified because of this. +; CHECK: %true = OpConstantTrue %bool +; CHECK: %int_3 = OpConstant %int 3 + + %4 = OpFunction %2 None %3 + %11 = OpLabel + OpBranch %5 + %5 = OpLabel + %23 = OpPhi %6 %7 %11 %20 %15 + %9 = OpSLessThan %10 %23 %13 + OpLoopMerge %8 %15 None + OpBranchConditional %9 %15 %8 + %15 = OpLabel + %20 = OpIAdd %6 %23 %21 + OpBranch %5 + %8 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndMatch<CCPPass>(text, true); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); +} + +// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3738 +// Similar to the previous one but more than one constant is generated in a +// single call to the instruction folder. +TEST_F(CCPTest, CCPNoChangeFailureSeveralConstantsDuringFolding) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + %void = OpTypeVoid + %4 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v3float = OpTypeVector %float 3 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %bool = OpTypeBool + %v3bool = OpTypeVector %bool 3 + %float_0 = OpConstant %float 0 + %12 = OpConstantComposite %v3float %float_0 %float_0 %float_0 +%float_0_300000012 = OpConstant %float 0.300000012 + %14 = OpConstantComposite %v3float %float_0_300000012 %float_0_300000012 %float_0_300000012 + +; CCP is generating several constants during a single instruction evaluation. +; When folding %19, it generates the constants %true and %24. They are dead +; because they cannot be replaced anywhere in the IR. CCP was wrongly +; considering the IR to be unmodified because of this. +; +; CHECK: %true = OpConstantTrue %bool +; CHECK: %24 = OpConstantComposite %v3bool %true %true %true +; CHECK: %float_1 = OpConstant %float 1 +; CHECK: %float_0_699999988 = OpConstant %float 0.699999988 + + %2 = OpFunction %void None %4 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + %17 = OpPhi %v3float %12 %15 %14 %18 + %19 = OpFOrdLessThan %v3bool %17 %14 + %20 = OpAll %bool %19 + OpLoopMerge %21 %18 None + OpBranchConditional %20 %18 %21 + %18 = OpLabel + OpBranch %16 + %21 = OpLabel + %22 = OpExtInst %v3float %1 FMix %12 %17 %14 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndMatch<CCPPass>(text, true); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/compact_ids_test.cpp b/test/opt/compact_ids_test.cpp index b1e4b2cb..ba31d84b 100644 --- a/test/opt/compact_ids_test.cpp +++ b/test/opt/compact_ids_test.cpp @@ -92,6 +92,42 @@ OpFunctionEnd SinglePassRunAndCheck<CompactIdsPass>(before, after, false, false); } +TEST_F(CompactIdsTest, DebugScope) { + const std::string text = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +%5 = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %3 "simple_kernel" +%2 = OpString "test" +%99 = OpTypeInt 32 0 +%10 = OpTypeVector %99 2 +%20 = OpConstant %99 2 +%30 = OpTypeArray %99 %20 +%40 = OpTypeVoid +%50 = OpTypeFunction %40 +%11 = OpExtInst %40 %5 DebugSource %2 +%12 = OpExtInst %40 %5 DebugCompilationUnit 1 4 %11 HLSL +%13 = OpExtInst %40 %5 DebugTypeFunction FlagIsProtected|FlagIsPrivate %40 + +; CHECK: [[fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction +%14 = OpExtInst %40 %5 DebugFunction %2 %13 %11 0 0 %12 %2 FlagIsProtected|FlagIsPrivate 0 %3 + %3 = OpFunction %40 None %50 +%70 = OpLabel + +; CHECK: DebugScope [[fn]] +%19 = OpExtInst %40 %5 DebugScope %14 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndMatch<CompactIdsPass>(text, true); +} + TEST(CompactIds, InstructionResultIsUpdated) { // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827 // In that bug, the compact Ids pass was directly updating the result Id diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp index 8ae6894d..9ea948a4 100644 --- a/test/opt/dead_insert_elim_test.cpp +++ b/test/opt/dead_insert_elim_test.cpp @@ -563,6 +563,113 @@ OpFunctionEnd after_predefs + after, true, true); } +TEST_F(DeadInsertElimTest, DebugInsertAfterInsertElim) { + // With two insertions to the same offset, the first is dead. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float In0; + // layout (location=1) in float In1; + // layout (location=2) in vec2 In2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec2 v = In2; + // v.x = In0 + In1; // dead + // v.x = 0.0; + // OutColor = v.xyxy; + // } + + const std::string text = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +%file_name = OpString "test" +%float_name = OpString "float" +%main_name = OpString "main" +%f_name = OpString "f" +OpName %main "main" +OpName %In2 "In2" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %OutColor "OutColor" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_n" +OpName %_ "" +OpDecorate %In2 Location 2 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %OutColor Location 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%In2 = OpVariable %_ptr_Input_v2float Input +%_ptr_Input_float = OpTypePointer Input %float +%In0 = OpVariable %_ptr_Input_float Input +%In1 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%int = OpTypeInt 32 1 +%_Globals_ = OpTypeStruct %uint %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform + +%nullexpr = OpExtInst %void %ext DebugExpression +%src = OpExtInst %void %ext DebugSource %file_name +%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float +%dbg_v2f = OpExtInst %void %ext DebugTypeVector %dbg_tf 2 +%main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %void +%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 0 %main +%dbg_foo = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v2f %src 0 0 %dbg_main FlagIsLocal + +%main = OpFunction %void None %11 +%25 = OpLabel +%26 = OpLoad %v2float %In2 +%27 = OpLoad %float %In0 +%28 = OpLoad %float %In1 +%29 = OpFAdd %float %27 %28 + +; CHECK: [[repl:%\w+]] = OpLoad %v2float %In2 +; CHECK-NOT: OpCompositeInsert +; CHECK: DebugValue {{%\w+}} [[repl:%\w+]] +; CHECK-NEXT: OpCompositeInsert %v2float %float_0 [[repl]] 0 +%35 = OpCompositeInsert %v2float %29 %26 0 +%value = OpExtInst %void %ext DebugValue %dbg_foo %35 %nullexpr +%37 = OpCompositeInsert %v2float %float_0 %35 0 + +%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1 +OpStore %OutColor %33 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch<DeadInsertElimPass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // diff --git a/test/opt/debug_info_manager_test.cpp b/test/opt/debug_info_manager_test.cpp index 82890f76..911331a7 100644 --- a/test/opt/debug_info_manager_test.cpp +++ b/test/opt/debug_info_manager_test.cpp @@ -354,6 +354,116 @@ void 200(float in_var_color : COLOR) { 200); } +TEST(DebugInfoManager, GetDebugFunction_InlinedAway) { + // struct PS_INPUT + // { + // float4 iColor : COLOR; + // }; + // + // struct PS_OUTPUT + // { + // float4 oColor : SV_Target0; + // }; + // + // float4 foo(float4 ic) + // { + // float4 c = ic / 2.0; + // return c; + // } + // + // PS_OUTPUT MainPs(PS_INPUT i) + // { + // PS_OUTPUT ps_output; + // float4 ic = i.iColor; + // ps_output.oColor = foo(ic); + // return ps_output; + // } + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "OpenCL.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %in_var_COLOR %out_var_SV_Target0 + OpExecutionMode %MainPs OriginUpperLeft + %15 = OpString "foo2.frag" + %19 = OpString "PS_OUTPUT" + %23 = OpString "float" + %26 = OpString "oColor" + %28 = OpString "PS_INPUT" + %31 = OpString "iColor" + %33 = OpString "foo" + %37 = OpString "c" + %39 = OpString "ic" + %42 = OpString "src.MainPs" + %47 = OpString "ps_output" + %50 = OpString "i" + OpName %in_var_COLOR "in.var.COLOR" + OpName %out_var_SV_Target0 "out.var.SV_Target0" + OpName %MainPs "MainPs" + OpDecorate %in_var_COLOR Location 0 + OpDecorate %out_var_SV_Target0 Location 0 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 + %uint_32 = OpConstant %uint 32 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %uint_128 = OpConstant %uint 128 + %uint_0 = OpConstant %uint 0 + %52 = OpTypeFunction %void +%in_var_COLOR = OpVariable %_ptr_Input_v4float Input +%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output + %float_0_5 = OpConstant %float 0.5 + %130 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 + %115 = OpExtInst %void %1 DebugInfoNone + %49 = OpExtInst %void %1 DebugExpression + %17 = OpExtInst %void %1 DebugSource %15 + %18 = OpExtInst %void %1 DebugCompilationUnit 1 4 %17 HLSL + %21 = OpExtInst %void %1 DebugTypeComposite %19 Structure %17 6 1 %18 %19 %uint_128 FlagIsProtected|FlagIsPrivate %22 + %24 = OpExtInst %void %1 DebugTypeBasic %23 %uint_32 Float + %25 = OpExtInst %void %1 DebugTypeVector %24 4 + %22 = OpExtInst %void %1 DebugTypeMember %26 %25 %17 8 5 %21 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate + %29 = OpExtInst %void %1 DebugTypeComposite %28 Structure %17 1 1 %18 %28 %uint_128 FlagIsProtected|FlagIsPrivate %30 + %30 = OpExtInst %void %1 DebugTypeMember %31 %25 %17 3 5 %29 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate + %32 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %25 %25 + %34 = OpExtInst %void %1 DebugFunction %33 %32 %17 11 1 %18 %33 FlagIsProtected|FlagIsPrivate 12 %115 + %36 = OpExtInst %void %1 DebugLexicalBlock %17 12 1 %34 + %38 = OpExtInst %void %1 DebugLocalVariable %37 %25 %17 13 12 %36 FlagIsLocal + %41 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %21 %29 + %43 = OpExtInst %void %1 DebugFunction %42 %41 %17 17 1 %18 %42 FlagIsProtected|FlagIsPrivate 18 %115 + %45 = OpExtInst %void %1 DebugLexicalBlock %17 18 1 %43 + %46 = OpExtInst %void %1 DebugLocalVariable %39 %25 %17 20 12 %45 FlagIsLocal + %48 = OpExtInst %void %1 DebugLocalVariable %47 %21 %17 19 15 %45 FlagIsLocal + %107 = OpExtInst %void %1 DebugInlinedAt 21 %45 + %MainPs = OpFunction %void None %52 + %53 = OpLabel + %57 = OpLoad %v4float %in_var_COLOR + %131 = OpExtInst %void %1 DebugScope %45 + OpLine %15 20 12 + %117 = OpExtInst %void %1 DebugValue %46 %57 %49 + %132 = OpExtInst %void %1 DebugScope %36 %107 + OpLine %15 13 19 + %112 = OpFMul %v4float %57 %130 + OpLine %15 13 12 + %116 = OpExtInst %void %1 DebugValue %38 %112 %49 + %133 = OpExtInst %void %1 DebugScope %45 + %128 = OpExtInst %void %1 DebugValue %48 %112 %49 %int_0 + %134 = OpExtInst %void %1 DebugNoScope + OpStore %out_var_SV_Target0 %112 + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + DebugInfoManager manager(context.get()); + + EXPECT_EQ(manager.GetDebugFunction(115), nullptr); +} + TEST(DebugInfoManager, CloneDebugInlinedAt) { const std::string text = R"( OpCapability Shader @@ -484,7 +594,7 @@ void main(float in_var_color : COLOR) { auto* dbg_info_mgr = context->get_debug_info_mgr(); auto* def_use_mgr = context->get_def_use_mgr(); - EXPECT_TRUE(dbg_info_mgr->IsDebugDeclared(100)); + EXPECT_TRUE(dbg_info_mgr->IsVariableDebugDeclared(100)); EXPECT_EQ(def_use_mgr->GetDef(36)->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugDeclare); EXPECT_EQ(def_use_mgr->GetDef(37)->GetOpenCL100DebugOpcode(), @@ -496,7 +606,7 @@ void main(float in_var_color : COLOR) { EXPECT_EQ(def_use_mgr->GetDef(36), nullptr); EXPECT_EQ(def_use_mgr->GetDef(37), nullptr); EXPECT_EQ(def_use_mgr->GetDef(38), nullptr); - EXPECT_FALSE(dbg_info_mgr->IsDebugDeclared(100)); + EXPECT_FALSE(dbg_info_mgr->IsVariableDebugDeclared(100)); } } // namespace diff --git a/test/opt/eliminate_dead_functions_test.cpp b/test/opt/eliminate_dead_functions_test.cpp index 2f8fa9a3..96ecdc60 100644 --- a/test/opt/eliminate_dead_functions_test.cpp +++ b/test/opt/eliminate_dead_functions_test.cpp @@ -344,6 +344,101 @@ OpFunctionEnd SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, false); } +TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoPersists) { + const std::string text = R"( +; CHECK: [[import:%\w+]] = OpExtInstImport +; CHECK: [[void:%\w+]] = OpTypeVoid +; CHECK-NOT: OpExtInst [[void]] [[import]] 1 +; CHECK: OpExtInst [[void]] [[import]] 2 +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%foo = OpFunction %void None %void_fn +%foo_entry = OpLabel +%non_semantic1 = OpExtInst %void %ext 1 +OpReturn +OpFunctionEnd +%non_semantic2 = OpExtInst %void %ext 2 +)"; + + SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true); +} + +TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoRemoveDependent) { + const std::string text = R"( +; CHECK: [[import:%\w+]] = OpExtInstImport +; CHECK: [[void:%\w+]] = OpTypeVoid +; CHECK-NOT: OpExtInst [[void]] [[import]] 1 +; CHECK-NOT: OpExtInst [[void]] [[import]] 2 +; CHECK: OpExtInst [[void]] [[import]] 3 +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%foo = OpFunction %void None %void_fn +%foo_entry = OpLabel +%non_semantic1 = OpExtInst %void %ext 1 +OpReturn +OpFunctionEnd +%non_semantic2 = OpExtInst %void %ext 2 %foo +%non_semantic3 = OpExtInst %void %ext 3 +)"; + + SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true); +} + +TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoRemoveDependentTree) { + const std::string text = R"( +; CHECK: [[import:%\w+]] = OpExtInstImport +; CHECK: [[void:%\w+]] = OpTypeVoid +; CHECK-NOT: OpExtInst [[void]] [[import]] 1 +; CHECK-NOT: OpExtInst [[void]] [[import]] 2 +; CHECK: OpExtInst [[void]] [[import]] 3 +; CHECK-NOT: OpExtInst [[void]] [[import]] 4 +; CHECK-NOT: OpExtInst [[void]] [[import]] 5 +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%foo = OpFunction %void None %void_fn +%foo_entry = OpLabel +%non_semantic1 = OpExtInst %void %ext 1 +OpReturn +OpFunctionEnd +%non_semantic2 = OpExtInst %void %ext 2 %foo +%non_semantic3 = OpExtInst %void %ext 3 +%non_semantic4 = OpExtInst %void %ext 4 %non_semantic2 +%non_semantic5 = OpExtInst %void %ext 5 %non_semantic4 +)"; + + SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp index beb26f24..bb6098cc 100644 --- a/test/opt/fold_test.cpp +++ b/test/opt/fold_test.cpp @@ -3464,7 +3464,18 @@ INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingT "%3 = OpCompositeExtract %float %2 4\n" + "OpReturn\n" + "OpFunctionEnd", - 3, 0) + 3, 0), + // Test case 14: https://github.com/KhronosGroup/SPIRV-Tools/issues/3631 + // Extract the component right after the vector constituent. + InstructionFoldingCase<uint32_t>( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCompositeConstruct %v2int %int_0 %int_0\n" + + "%3 = OpCompositeConstruct %v4int %2 %100 %int_0\n" + + "%4 = OpCompositeExtract %int %3 2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, INT_0_ID) )); INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest, diff --git a/test/opt/function_test.cpp b/test/opt/function_test.cpp index 38ab2987..af25bacc 100644 --- a/test/opt/function_test.cpp +++ b/test/opt/function_test.cpp @@ -29,6 +29,60 @@ namespace { using ::testing::Eq; +TEST(FunctionTest, HasEarlyReturn) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %6 "main" + +; Types + %2 = OpTypeBool + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + +; Constants + %5 = OpConstantTrue %2 + +; main function without early return + %6 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpBranch %10 + %10 = OpLabel + OpReturn + OpFunctionEnd + +; function with early return + %11 = OpFunction %3 None %4 + %12 = OpLabel + OpSelectionMerge %15 None + OpBranchConditional %5 %13 %14 + %13 = OpLabel + OpReturn + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + // Tests |function| without early return. + auto* function = spvtest::GetFunction(context->module(), 6); + ASSERT_FALSE(function->HasEarlyReturn()); + + // Tests |function| with early return. + function = spvtest::GetFunction(context->module(), 11); + ASSERT_TRUE(function->HasEarlyReturn()); +} + TEST(FunctionTest, IsNotRecursive) { const std::string text = R"( OpCapability Shader @@ -168,6 +222,80 @@ OpFunctionEnd EXPECT_FALSE(func->IsRecursive()); } +TEST(FunctionTest, NonSemanticInfoSkipIteration) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%4 = OpFunction %2 None %3 +%5 = OpLabel +%6 = OpExtInst %2 %1 1 +OpReturn +OpFunctionEnd +%7 = OpExtInst %2 %1 2 +%8 = OpExtInst %2 %1 3 +)"; + + std::unique_ptr<IRContext> ctx = + spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto* func = spvtest::GetFunction(ctx->module(), 4); + ASSERT_TRUE(func != nullptr); + std::unordered_set<uint32_t> non_semantic_ids; + func->ForEachInst( + [&non_semantic_ids](const Instruction* inst) { + if (inst->opcode() == SpvOpExtInst) { + non_semantic_ids.insert(inst->result_id()); + } + }, + true, false); + + EXPECT_EQ(1, non_semantic_ids.count(6)); + EXPECT_EQ(0, non_semantic_ids.count(7)); + EXPECT_EQ(0, non_semantic_ids.count(8)); +} + +TEST(FunctionTest, NonSemanticInfoIncludeIteration) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%4 = OpFunction %2 None %3 +%5 = OpLabel +%6 = OpExtInst %2 %1 1 +OpReturn +OpFunctionEnd +%7 = OpExtInst %2 %1 2 +%8 = OpExtInst %2 %1 3 +)"; + + std::unique_ptr<IRContext> ctx = + spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto* func = spvtest::GetFunction(ctx->module(), 4); + ASSERT_TRUE(func != nullptr); + std::unordered_set<uint32_t> non_semantic_ids; + func->ForEachInst( + [&non_semantic_ids](const Instruction* inst) { + if (inst->opcode() == SpvOpExtInst) { + non_semantic_ids.insert(inst->result_id()); + } + }, + true, true); + + EXPECT_EQ(1, non_semantic_ids.count(6)); + EXPECT_EQ(1, non_semantic_ids.count(7)); + EXPECT_EQ(1, non_semantic_ids.count(8)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/graphics_robust_access_test.cpp b/test/opt/graphics_robust_access_test.cpp index d38571e7..4b2cd447 100644 --- a/test/opt/graphics_robust_access_test.cpp +++ b/test/opt/graphics_robust_access_test.cpp @@ -1323,8 +1323,8 @@ TEST_F(GraphicsRobustAccessTest, // Split the address calculation across two access chains. Force // the transform to walk up the access chains to find the base variable. // This time, put the different access chains in different basic blocks. - // This sanity checks that we keep the instruction-to-block mapping - // consistent. + // This is an integrity check to ensure that we keep the instruction-to-block + // mapping consistent. for (auto* ac : AccessChains()) { std::ostringstream shaders; shaders << ShaderPreambleAC({"i", "j", "k", "bb1", "bb2", "ssbo_s", @@ -1387,6 +1387,167 @@ TEST_F(GraphicsRobustAccessTest, } } +TEST_F(GraphicsRobustAccessTest, bug3813) { + // This shader comes from Dawn's + // TextureViewSamplingTest.TextureCubeMapOnWholeTexture, converted from GLSL + // by glslang. + // The pass was inserting a signed 32-bit int type, but not correctly marking + // the shader as changed. + std::string shader = R"( +; SPIR-V +; Version: 1.0 +; Generator: Google Shaderc over Glslang; 10 +; Bound: 46 +; Schema: 0 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %12 %29 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 450 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %4 "main" + OpName %8 "sc" + OpName %12 "texCoord" + OpName %21 "tc" + OpName %29 "fragColor" + OpName %32 "texture0" + OpName %36 "sampler0" + OpDecorate %12 Location 0 + OpDecorate %29 Location 0 + OpDecorate %32 DescriptorSet 0 + OpDecorate %32 Binding 1 + OpDecorate %36 DescriptorSet 0 + OpDecorate %36 Binding 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %10 = OpTypeVector %6 2 + %11 = OpTypePointer Input %10 + %12 = OpVariable %11 Input + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 0 + %15 = OpTypePointer Input %6 + %19 = OpConstant %6 1 + %22 = OpConstant %13 1 + %27 = OpTypeVector %6 4 + %28 = OpTypePointer Output %27 + %29 = OpVariable %28 Output + %30 = OpTypeImage %6 Cube 0 0 0 1 Unknown + %31 = OpTypePointer UniformConstant %30 + %32 = OpVariable %31 UniformConstant + %34 = OpTypeSampler + %35 = OpTypePointer UniformConstant %34 + %36 = OpVariable %35 UniformConstant + %38 = OpTypeSampledImage %30 + %43 = OpTypeVector %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %21 = OpVariable %7 Function + %16 = OpAccessChain %15 %12 %14 + %17 = OpLoad %6 %16 + %18 = OpFMul %6 %9 %17 + %20 = OpFSub %6 %18 %19 + OpStore %8 %20 + %23 = OpAccessChain %15 %12 %22 + %24 = OpLoad %6 %23 + %25 = OpFMul %6 %9 %24 + %26 = OpFSub %6 %25 %19 + OpStore %21 %26 + %33 = OpLoad %30 %32 + %37 = OpLoad %34 %36 + %39 = OpSampledImage %38 %33 %37 + %40 = OpLoad %6 %21 + %41 = OpLoad %6 %8 + %42 = OpFNegate %6 %41 + %44 = OpCompositeConstruct %43 %19 %40 %42 + %45 = OpImageSampleImplicitLod %27 %39 %44 + OpStore %29 %45 + OpReturn + OpFunctionEnd +)"; + + std::string expected = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %texCoord %fragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpName %main "main" +OpName %sc "sc" +OpName %texCoord "texCoord" +OpName %tc "tc" +OpName %fragColor "fragColor" +OpName %texture0 "texture0" +OpName %sampler0 "sampler0" +OpDecorate %texCoord Location 0 +OpDecorate %fragColor Location 0 +OpDecorate %texture0 DescriptorSet 0 +OpDecorate %texture0 Binding 1 +OpDecorate %sampler0 DescriptorSet 0 +OpDecorate %sampler0 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_2 = OpConstant %float 2 +%v2float = OpTypeVector %float 2 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoord = OpVariable %_ptr_Input_v2float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_1 = OpConstant %float 1 +%uint_1 = OpConstant %uint 1 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%fragColor = OpVariable %_ptr_Output_v4float Output +%23 = OpTypeImage %float Cube 0 0 0 1 Unknown +%_ptr_UniformConstant_23 = OpTypePointer UniformConstant %23 +%texture0 = OpVariable %_ptr_UniformConstant_23 UniformConstant +%25 = OpTypeSampler +%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25 +%sampler0 = OpVariable %_ptr_UniformConstant_25 UniformConstant +%27 = OpTypeSampledImage %23 +%v3float = OpTypeVector %float 3 +%int = OpTypeInt 32 1 +%main = OpFunction %void None %10 +%29 = OpLabel +%sc = OpVariable %_ptr_Function_float Function +%tc = OpVariable %_ptr_Function_float Function +%30 = OpAccessChain %_ptr_Input_float %texCoord %uint_0 +%31 = OpLoad %float %30 +%32 = OpFMul %float %float_2 %31 +%33 = OpFSub %float %32 %float_1 +OpStore %sc %33 +%34 = OpAccessChain %_ptr_Input_float %texCoord %uint_1 +%35 = OpLoad %float %34 +%36 = OpFMul %float %float_2 %35 +%37 = OpFSub %float %36 %float_1 +OpStore %tc %37 +%38 = OpLoad %23 %texture0 +%39 = OpLoad %25 %sampler0 +%40 = OpSampledImage %27 %38 %39 +%41 = OpLoad %float %tc +%42 = OpLoad %float %sc +%43 = OpFNegate %float %42 +%44 = OpCompositeConstruct %v3float %float_1 %41 %43 +%45 = OpImageSampleImplicitLod %v4float %40 %44 +OpStore %fragColor %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<GraphicsRobustAccessPass>(shader, expected, false, + true); +} + // TODO(dneto): Test access chain index wider than 64 bits? // TODO(dneto): Test struct access chain index wider than 64 bits? // TODO(dneto): OpImageTexelPointer diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp index b8d2dfad..47e05333 100644 --- a/test/opt/inline_opaque_test.cpp +++ b/test/opt/inline_opaque_test.cpp @@ -90,7 +90,8 @@ OpFunctionEnd )"; const std::string after = - R"(%main = OpFunction %void None %12 + R"(%34 = OpUndef %void +%main = OpFunction %void None %12 %28 = OpLabel %s0 = OpVariable %_ptr_Function_S_t Function %param = OpVariable %_ptr_Function_S_t Function @@ -289,7 +290,8 @@ OpFunctionEnd )"; const std::string after = - R"(%main2 = OpFunction %void None %13 + R"(%35 = OpUndef %void +%main2 = OpFunction %void None %13 %29 = OpLabel %s0 = OpVariable %_ptr_Function_S_t Function %param = OpVariable %_ptr_Function_S_t Function diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp index ffd3e38a..951721bf 100644 --- a/test/opt/inline_test.cpp +++ b/test/opt/inline_test.cpp @@ -381,6 +381,7 @@ TEST_F(InlineTest, InOutParameter) { const std::vector<const char*> after = { // clang-format off + "%26 = OpUndef %void", "%main = OpFunction %void None %11", "%23 = OpLabel", "%b = OpVariable %_ptr_Function_v4float Function", @@ -1503,11 +1504,11 @@ OpSource OpenCL_C 120 %bool = OpTypeBool %true = OpConstantTrue %bool %void = OpTypeVoid +%5 = OpTypeFunction %void )"; const std::string nonEntryFuncs = - R"(%5 = OpTypeFunction %void -%6 = OpFunction %void None %5 + R"(%6 = OpFunction %void None %5 %7 = OpLabel OpBranch %8 %8 = OpLabel @@ -1542,9 +1543,11 @@ OpReturn OpFunctionEnd )"; - SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before, - predefs + nonEntryFuncs + after, - false, true); + const std::string undef = "%11 = OpUndef %void\n"; + + SinglePassRunAndCheck<InlineExhaustivePass>( + predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after, + false, true); } TEST_F(InlineTest, MultiBlockLoopHeaderCallsMultiBlockCallee) { @@ -1619,9 +1622,10 @@ OpReturn OpFunctionEnd )"; - SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before, - predefs + nonEntryFuncs + after, - false, true); + const std::string undef = "%20 = OpUndef %void\n"; + SinglePassRunAndCheck<InlineExhaustivePass>( + predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after, + false, true); } TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) { @@ -1635,7 +1639,7 @@ TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) { // the OpSelectionMerge, so inlining must create a new block to contain // the callee contents. // - // Additionally, we have two dummy OpCopyObject instructions to prove that + // Additionally, we have two extra OpCopyObject instructions to prove that // the OpLoopMerge is moved to the right location. // // Also ensure that OpPhis within the cloned callee code are valid. @@ -1707,10 +1711,10 @@ OpBranchConditional %true %13 %16 OpReturn OpFunctionEnd )"; - - SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before, - predefs + nonEntryFuncs + after, - false, true); + const std::string undef = "%15 = OpUndef %void\n"; + SinglePassRunAndCheck<InlineExhaustivePass>( + predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after, + false, true); } TEST_F(InlineTest, @@ -1789,9 +1793,10 @@ OpReturn OpFunctionEnd )"; - SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before, - predefs + nonEntryFuncs + after, - false, true); + const std::string undef = "%20 = OpUndef %void\n"; + SinglePassRunAndCheck<InlineExhaustivePass>( + predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after, + false, true); } TEST_F(InlineTest, NonInlinableCalleeWithSingleReturn) { @@ -2164,6 +2169,7 @@ OpName %foo "foo" OpName %foo_entry "foo_entry" %void = OpTypeVoid %void_fn = OpTypeFunction %void +%3 = OpUndef %void %foo = OpFunction %void None %void_fn %foo_entry = OpLabel OpReturn @@ -2437,6 +2443,7 @@ OpName %kill_ "kill(" %3 = OpTypeFunction %void %bool = OpTypeBool %true = OpConstantTrue %bool +%16 = OpUndef %void %main = OpFunction %void None %3 %5 = OpLabel OpKill @@ -2534,6 +2541,7 @@ OpName %kill_ "kill(" %3 = OpTypeFunction %void %bool = OpTypeBool %true = OpConstantTrue %bool +%16 = OpUndef %void %main = OpFunction %void None %3 %5 = OpLabel OpTerminateInvocation @@ -2761,6 +2769,7 @@ OpFunctionEnd %uint_0 = OpConstant %uint 0 %false = OpConstantFalse %bool %_ptr_Function_bool = OpTypePointer Function %bool +%11 = OpUndef %void %foo_ = OpFunction %void None %4 %7 = OpLabel %18 = OpVariable %_ptr_Function_bool Function %false @@ -3849,6 +3858,35 @@ OpFunctionEnd SinglePassRunAndMatch<InlineExhaustivePass>(text, true); } +TEST_F(InlineTest, UsingVoidFunctionResult) { + const std::string text = R"( +; CHECK: [[undef:%\w+]] = OpUndef %void +; CHECK: OpFunction +; CHECK: OpCopyObject %void [[undef]] +; CHECK: OpFunctionEnd + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpFunctionCall %2 %6 + %9 = OpCopyObject %2 %8 + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch<InlineExhaustivePass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Empty modules diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp index d867b01f..1432955b 100644 --- a/test/opt/inst_bindless_check_test.cpp +++ b/test/opt/inst_bindless_check_test.cpp @@ -2444,7 +2444,7 @@ OpBranchConditional %57 %59 %60 %61 = OpLoad %16 %33 %62 = OpSampledImage %26 %61 %35 %136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32 -%137 = OpINotEqual %bool %136 %uint_0 +%137 = OpULessThan %bool %uint_0 %136 OpSelectionMerge %138 None OpBranchConditional %137 %139 %140 %139 = OpLabel @@ -2697,7 +2697,7 @@ OpFunctionEnd %22 = OpLoad %14 %g_sAniso %23 = OpSampledImage %16 %21 %22 %50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0 -%52 = OpINotEqual %bool %50 %uint_0 +%52 = OpULessThan %bool %uint_0 %50 OpSelectionMerge %54 None OpBranchConditional %52 %55 %56 %55 = OpLabel @@ -3075,7 +3075,7 @@ OpBranchConditional %42 %44 %45 %44 = OpLabel %103 = OpBitcast %uint %7 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103 -%123 = OpINotEqual %bool %122 %uint_0 +%123 = OpULessThan %bool %uint_0 %122 OpSelectionMerge %124 None OpBranchConditional %123 %125 %126 %125 = OpLabel @@ -3356,7 +3356,7 @@ OpBranchConditional %42 %44 %45 %44 = OpLabel %103 = OpBitcast %uint %7 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103 -%123 = OpINotEqual %bool %122 %uint_0 +%123 = OpULessThan %bool %uint_0 %122 OpSelectionMerge %124 None OpBranchConditional %123 %125 %126 %125 = OpLabel @@ -3626,7 +3626,7 @@ OpBranchConditional %42 %44 %45 %44 = OpLabel %103 = OpBitcast %uint %7 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103 -%123 = OpINotEqual %bool %122 %uint_0 +%123 = OpULessThan %bool %uint_0 %122 OpSelectionMerge %124 None OpBranchConditional %123 %125 %126 %125 = OpLabel @@ -3870,7 +3870,7 @@ OpFunctionEnd %14 = OpLabel %15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0 %43 = OpFunctionCall %uint %20 %uint_0 %uint_0 %uint_3 %uint_0 -%45 = OpINotEqual %bool %43 %uint_0 +%45 = OpULessThan %bool %uint_0 %43 OpSelectionMerge %47 None OpBranchConditional %45 %48 %49 %48 = OpLabel @@ -4128,7 +4128,7 @@ OpBranchConditional %42 %44 %45 %44 = OpLabel %100 = OpBitcast %uint %7 %119 = OpFunctionCall %uint %101 %uint_0 %uint_0 %uint_4 %100 -%120 = OpINotEqual %bool %119 %uint_0 +%120 = OpULessThan %bool %uint_0 %119 OpSelectionMerge %121 None OpBranchConditional %120 %122 %123 %122 = OpLabel @@ -4405,7 +4405,7 @@ OpBranchConditional %25 %27 %28 %27 = OpLabel %90 = OpBitcast %uint %7 %112 = OpFunctionCall %uint %91 %uint_0 %uint_0 %uint_3 %90 -%113 = OpINotEqual %bool %112 %uint_0 +%113 = OpULessThan %bool %uint_0 %112 OpSelectionMerge %114 None OpBranchConditional %113 %115 %116 %115 = OpLabel @@ -4682,7 +4682,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %132 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0 -%133 = OpINotEqual %bool %132 %uint_0 +%133 = OpULessThan %bool %uint_0 %132 OpSelectionMerge %134 None OpBranchConditional %133 %135 %136 %135 = OpLabel @@ -4702,7 +4702,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %142 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_1 %141 -%143 = OpINotEqual %bool %142 %uint_0 +%143 = OpULessThan %bool %uint_0 %142 OpSelectionMerge %144 None OpBranchConditional %143 %145 %146 %145 = OpLabel @@ -4723,7 +4723,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %113 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %151 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0 -%152 = OpINotEqual %bool %151 %uint_0 +%152 = OpULessThan %bool %uint_0 %151 OpSelectionMerge %153 None OpBranchConditional %152 %154 %155 %154 = OpLabel @@ -5006,7 +5006,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -5026,7 +5026,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -5047,7 +5047,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -5330,7 +5330,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -5350,7 +5350,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -5371,7 +5371,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -5654,7 +5654,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -5674,7 +5674,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -5695,7 +5695,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -5978,7 +5978,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -5998,7 +5998,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -6019,7 +6019,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -6302,7 +6302,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -6322,7 +6322,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -6343,7 +6343,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -6626,7 +6626,7 @@ OpFunctionEnd %24 = OpLabel %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%134 = OpINotEqual %bool %133 %uint_0 +%134 = OpULessThan %bool %uint_0 %133 OpSelectionMerge %135 None OpBranchConditional %134 %136 %137 %136 = OpLabel @@ -6646,7 +6646,7 @@ OpBranchConditional %50 %52 %53 %52 = OpLabel %54 = OpLoad %13 %27 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142 -%144 = OpINotEqual %bool %143 %uint_0 +%144 = OpULessThan %bool %uint_0 %143 OpSelectionMerge %145 None OpBranchConditional %144 %146 %147 %146 = OpLabel @@ -6667,7 +6667,7 @@ OpBranch %51 %30 = OpCompositeExtract %float %114 0 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0 -%153 = OpINotEqual %bool %152 %uint_0 +%153 = OpULessThan %bool %uint_0 %152 OpSelectionMerge %154 None OpBranchConditional %153 %155 %156 %155 = OpLabel @@ -7038,7 +7038,7 @@ OpBranchConditional %59 %61 %62 %64 = OpSampledImage %27 %63 %26 %124 = OpBitcast %uint %19 %146 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_3 %124 -%147 = OpINotEqual %bool %146 %uint_0 +%147 = OpULessThan %bool %uint_0 %146 OpSelectionMerge %148 None OpBranchConditional %147 %149 %150 %149 = OpLabel @@ -7067,7 +7067,7 @@ OpStore %x %36 %42 = OpLoad %v2float %inTexcoord %47 = OpAccessChain %_ptr_Uniform_v2float %uniforms %int_0 %157 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_0 %uint_0 -%158 = OpINotEqual %bool %157 %uint_0 +%158 = OpULessThan %bool %uint_0 %157 OpSelectionMerge %159 None OpBranchConditional %158 %160 %161 %160 = OpLabel @@ -7081,7 +7081,7 @@ OpBranch %159 %49 = OpFMul %v2float %42 %166 %167 = OpSampledImage %27 %39 %40 %168 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_2 %uint_0 -%169 = OpINotEqual %bool %168 %uint_0 +%169 = OpULessThan %bool %uint_0 %168 OpSelectionMerge %170 None OpBranchConditional %169 %171 %172 %171 = OpLabel @@ -7181,6 +7181,802 @@ OpFunctionEnd true, 7u, 23u, true, true); } +TEST_F(InstBindlessTest, MultipleUniformNonAggregateRefsNoDescInit) { + // Check that uniform refs do not go out-of-bounds. All checks use same input + // buffer read function call result at top of function for uniform buffer + // length. Because descriptor indexing is not being checked, we can avoid one + // buffer load. + // + // Texture2D g_tColor; + // SamplerState g_sAniso; + // + // layout(push_constant) cbuffer PerViewPushConst_t { bool g_B; }; + // + // cbuffer PerViewConstantBuffer_t { + // float2 g_TexOff0; + // float2 g_TexOff1; + // }; + // + // struct PS_INPUT { + // float2 vTextureCoords : TEXCOORD2; + // }; + // + // struct PS_OUTPUT { + // float4 vColor : SV_Target0; + // }; + // + // PS_OUTPUT MainPs(PS_INPUT i) { + // PS_OUTPUT ps_output; + // float2 off; + // float2 vtc; + // if (g_B) + // off = g_TexOff0; + // else + // off = g_TexOff1; + // vtc = i.vTextureCoords.xy + off; + // ps_output.vColor = g_tColor.Sample(g_sAniso, vtc); + // return ps_output; + // } + + const std::string text = R"( + OpCapability Shader +;CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor +;CHECK: OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %130 %157 %gl_FragCoord + OpExecutionMode %MainPs OriginUpperLeft + OpSource HLSL 500 + OpName %MainPs "MainPs" + OpName %PerViewPushConst_t "PerViewPushConst_t" + OpMemberName %PerViewPushConst_t 0 "g_B" + OpName %_ "" + OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t" + OpMemberName %PerViewConstantBuffer_t 0 "g_TexOff0" + OpMemberName %PerViewConstantBuffer_t 1 "g_TexOff1" + OpName %__0 "" + OpName %g_tColor "g_tColor" + OpName %g_sAniso "g_sAniso" + OpName %i_vTextureCoords "i.vTextureCoords" + OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" + OpMemberDecorate %PerViewPushConst_t 0 Offset 0 + OpDecorate %PerViewPushConst_t Block + OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0 + OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 8 + OpDecorate %PerViewConstantBuffer_t Block + OpDecorate %__0 DescriptorSet 0 + OpDecorate %__0 Binding 1 + OpDecorate %g_tColor DescriptorSet 0 + OpDecorate %g_tColor Binding 0 + OpDecorate %g_sAniso DescriptorSet 0 + OpDecorate %g_sAniso Binding 2 + OpDecorate %i_vTextureCoords Location 0 + OpDecorate %_entryPointOutput_vColor Location 0 + ;CHECK: OpDecorate %_runtimearr_uint ArrayStride 4 + ;CHECK: OpDecorate %_struct_128 Block + ;CHECK: OpMemberDecorate %_struct_128 0 Offset 0 + ;CHECK: OpDecorate %130 DescriptorSet 7 + ;CHECK: OpDecorate %130 Binding 1 + ;CHECK: OpDecorate %_struct_155 Block + ;CHECK: OpMemberDecorate %_struct_155 0 Offset 0 + ;CHECK: OpMemberDecorate %_struct_155 1 Offset 4 + ;CHECK: OpDecorate %157 DescriptorSet 7 + ;CHECK: OpDecorate %157 Binding 0 + ;CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 +%PerViewPushConst_t = OpTypeStruct %uint +%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t + %_ = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 +%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint + %bool = OpTypeBool + %uint_0 = OpConstant %uint 0 +%PerViewConstantBuffer_t = OpTypeStruct %v2float %v2float +%_ptr_Uniform_PerViewConstantBuffer_t = OpTypePointer Uniform %PerViewConstantBuffer_t + %__0 = OpVariable %_ptr_Uniform_PerViewConstantBuffer_t Uniform +%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float + %int_1 = OpConstant %int 1 + %49 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_49 = OpTypePointer UniformConstant %49 + %g_tColor = OpVariable %_ptr_UniformConstant_49 UniformConstant + %53 = OpTypeSampler +%_ptr_UniformConstant_53 = OpTypePointer UniformConstant %53 + %g_sAniso = OpVariable %_ptr_UniformConstant_53 UniformConstant + %57 = OpTypeSampledImage %49 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output + ;CHECK: %uint_7 = OpConstant %uint 7 + ;CHECK: %uint_1 = OpConstant %uint 1 + ;CHECK: %122 = OpTypeFunction %uint %uint %uint %uint + ;CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint + ;CHECK: %_struct_128 = OpTypeStruct %_runtimearr_uint + ;CHECK: %_ptr_StorageBuffer__struct_128 = OpTypePointer StorageBuffer %_struct_128 + ;CHECK: %130 = OpVariable %_ptr_StorageBuffer__struct_128 StorageBuffer + ;CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint + ;CHECK: %uint_3 = OpConstant %uint 3 + ;CHECK: %148 = OpTypeFunction %void %uint %uint %uint %uint %uint + ;CHECK: %_struct_155 = OpTypeStruct %uint %_runtimearr_uint + ;CHECK: %_ptr_StorageBuffer__struct_155 = OpTypePointer StorageBuffer %_struct_155 + ;CHECK: %157 = OpVariable %_ptr_StorageBuffer__struct_155 StorageBuffer + ;CHECK: %uint_11 = OpConstant %uint 11 + ;CHECK: %uint_4 = OpConstant %uint 4 + ;CHECK: %uint_23 = OpConstant %uint 23 + ;CHECK: %uint_2 = OpConstant %uint 2 + ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float + ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input + ;CHECK: %v4uint = OpTypeVector %uint 4 + ;CHECK: %uint_5 = OpConstant %uint 5 + ;CHECK: %uint_8 = OpConstant %uint 8 + ;CHECK: %uint_9 = OpConstant %uint 9 + ;CHECK: %uint_10 = OpConstant %uint 10 + ;CHECK: %uint_71 = OpConstant %uint 71 + ;CHECK: %202 = OpConstantNull %v2float + ;CHECK: %uint_75 = OpConstant %uint 75 + %MainPs = OpFunction %void None %3 + %5 = OpLabel + ;CHECK: %140 = OpFunctionCall %uint %121 %uint_1 %uint_1 %uint_0 + ;CHECK: OpBranch %117 + ;CHECK: %117 = OpLabel + ;CHECK: OpBranch %116 + ;CHECK: %116 = OpLabel + %69 = OpLoad %v2float %i_vTextureCoords + %82 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0 + %83 = OpLoad %uint %82 + %84 = OpINotEqual %bool %83 %uint_0 + OpSelectionMerge %91 None + OpBranchConditional %84 %85 %88 + %85 = OpLabel + %86 = OpAccessChain %_ptr_Uniform_v2float %__0 %int_0 + %87 = OpLoad %v2float %86 + ;CHECK-NOT: %87 = OpLoad %v2float %86 + ;CHECK: %119 = OpIAdd %uint %uint_0 %uint_7 + ;CHECK: %141 = OpULessThan %bool %119 %140 + ;CHECK: OpSelectionMerge %143 None + ;CHECK: OpBranchConditional %141 %144 %145 + ;CHECK: %144 = OpLabel + ;CHECK: %146 = OpLoad %v2float %86 + ;CHECK: OpBranch %143 + ;CHECK: %145 = OpLabel + ;CHECK: %201 = OpFunctionCall %void %147 %uint_71 %uint_3 %uint_0 %119 %140 + ;CHECK: OpBranch %143 + ;CHECK: %143 = OpLabel + ;CHECK: %203 = OpPhi %v2float %146 %144 %202 %145 + OpBranch %91 + %88 = OpLabel + %89 = OpAccessChain %_ptr_Uniform_v2float %__0 %int_1 + %90 = OpLoad %v2float %89 + ;CHECK-NOT: %90 = OpLoad %v2float %89 + ;CHECK: %204 = OpIAdd %uint %uint_8 %uint_7 + ;CHECK: %205 = OpULessThan %bool %204 %140 + ;CHECK: OpSelectionMerge %206 None + ;CHECK: OpBranchConditional %205 %207 %208 + ;CHECK: %207 = OpLabel + ;CHECK: %209 = OpLoad %v2float %89 + ;CHECK: OpBranch %206 + ;CHECK: %208 = OpLabel + ;CHECK: %211 = OpFunctionCall %void %147 %uint_75 %uint_3 %uint_0 %204 %140 + ;CHECK: OpBranch %206 + ;CHECK: %206 = OpLabel + ;CHECK: %212 = OpPhi %v2float %209 %207 %202 %208 + OpBranch %91 + %91 = OpLabel + %115 = OpPhi %v2float %87 %85 %90 %88 + ;CHECK-NOT: %115 = OpPhi %v2float %87 %85 %90 %88 + ;CHECK: %115 = OpPhi %v2float %203 %143 %212 %206 + %95 = OpFAdd %v2float %69 %115 + %96 = OpLoad %49 %g_tColor + %97 = OpLoad %53 %g_sAniso + %98 = OpSampledImage %57 %96 %97 + %100 = OpImageSampleImplicitLod %v4float %98 %95 + OpStore %_entryPointOutput_vColor %100 + OpReturn + OpFunctionEnd + ;CHECK: %121 = OpFunction %uint None %122 + ;CHECK: %123 = OpFunctionParameter %uint + ;CHECK: %124 = OpFunctionParameter %uint + ;CHECK: %125 = OpFunctionParameter %uint + ;CHECK: %126 = OpLabel + ;CHECK: %132 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %123 + ;CHECK: %133 = OpLoad %uint %132 + ;CHECK: %134 = OpIAdd %uint %133 %124 + ;CHECK: %135 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %134 + ;CHECK: %136 = OpLoad %uint %135 + ;CHECK: %137 = OpIAdd %uint %136 %125 + ;CHECK: %138 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %137 + ;CHECK: %139 = OpLoad %uint %138 + ;CHECK: OpReturnValue %139 + ;CHECK: OpFunctionEnd + ;CHECK: %147 = OpFunction %void None %148 + ;CHECK: %149 = OpFunctionParameter %uint + ;CHECK: %150 = OpFunctionParameter %uint + ;CHECK: %151 = OpFunctionParameter %uint + ;CHECK: %152 = OpFunctionParameter %uint + ;CHECK: %153 = OpFunctionParameter %uint + ;CHECK: %154 = OpLabel + ;CHECK: %158 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_0 + ;CHECK: %161 = OpAtomicIAdd %uint %158 %uint_4 %uint_0 %uint_11 + ;CHECK: %162 = OpIAdd %uint %161 %uint_11 + ;CHECK: %163 = OpArrayLength %uint %157 1 + ;CHECK: %164 = OpULessThanEqual %bool %162 %163 + ;CHECK: OpSelectionMerge %165 None + ;CHECK: OpBranchConditional %164 %166 %165 + ;CHECK: %166 = OpLabel + ;CHECK: %167 = OpIAdd %uint %161 %uint_0 + ;CHECK: %168 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %167 + ;CHECK: OpStore %168 %uint_11 + ;CHECK: %170 = OpIAdd %uint %161 %uint_1 + ;CHECK: %171 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %170 + ;CHECK: OpStore %171 %uint_23 + ;CHECK: %173 = OpIAdd %uint %161 %uint_2 + ;CHECK: %174 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %173 + ;CHECK: OpStore %174 %149 + ;CHECK: %175 = OpIAdd %uint %161 %uint_3 + ;CHECK: %176 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %175 + ;CHECK: OpStore %176 %uint_4 + ;CHECK: %179 = OpLoad %v4float %gl_FragCoord + ;CHECK: %181 = OpBitcast %v4uint %179 + ;CHECK: %182 = OpCompositeExtract %uint %181 0 + ;CHECK: %183 = OpIAdd %uint %161 %uint_4 + ;CHECK: %184 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %183 + ;CHECK: OpStore %184 %182 + ;CHECK: %185 = OpCompositeExtract %uint %181 1 + ;CHECK: %187 = OpIAdd %uint %161 %uint_5 + ;CHECK: %188 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %187 + ;CHECK: OpStore %188 %185 + ;CHECK: %189 = OpIAdd %uint %161 %uint_7 + ;CHECK: %190 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %189 + ;CHECK: OpStore %190 %150 + ;CHECK: %192 = OpIAdd %uint %161 %uint_8 + ;CHECK: %193 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %192 + ;CHECK: OpStore %193 %151 + ;CHECK: %195 = OpIAdd %uint %161 %uint_9 + ;CHECK: %196 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %195 + ;CHECK: OpStore %196 %152 + ;CHECK: %198 = OpIAdd %uint %161 %uint_10 + ;CHECK: %199 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %198 + ;CHECK: OpStore %199 %153 + ;CHECK: OpBranch %165 + ;CHECK: %165 = OpLabel + ;CHECK: OpReturn + ;CHECK: OpFunctionEnd + )"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false, + false, true); +} + +TEST_F(InstBindlessTest, UniformArrayRefNoDescInit) { + // Check that uniform array ref does not go out-of-bounds. + // + // Texture2D g_tColor; + // SamplerState g_sAniso; + // + // layout(push_constant) cbuffer PerViewPushConst_t { uint g_c; }; + // + // struct PerBatchEnvMapConstantBuffer_t { + // float4x3 g_matEnvMapWorldToLocal; + // float4 g_vEnvironmentMapBoxMins; + // float2 g_TexOff; + // }; + // + // cbuffer _BindlessFastEnvMapCB_PS_t { + // PerBatchEnvMapConstantBuffer_t g_envMapConstants[128]; + // }; + // + // struct PS_INPUT { + // float2 vTextureCoords : TEXCOORD2; + // }; + // + // struct PS_OUTPUT { + // float4 vColor : SV_Target0; + // }; + // + // PS_OUTPUT MainPs(PS_INPUT i) { + // PS_OUTPUT ps_output; + // float2 off; + // float2 vtc; + // off = g_envMapConstants[g_c].g_TexOff; + // vtc = i.vTextureCoords.xy + off; + // ps_output.vColor = g_tColor.Sample(g_sAniso, vtc); + // return ps_output; + // } + + const std::string text = R"( + OpCapability Shader +;CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor + OpExecutionMode %MainPs OriginUpperLeft + OpSource HLSL 500 + OpName %MainPs "MainPs" + OpName %PerBatchEnvMapConstantBuffer_t "PerBatchEnvMapConstantBuffer_t" + OpMemberName %PerBatchEnvMapConstantBuffer_t 0 "g_matEnvMapWorldToLocal" + OpMemberName %PerBatchEnvMapConstantBuffer_t 1 "g_vEnvironmentMapBoxMins" + OpMemberName %PerBatchEnvMapConstantBuffer_t 2 "g_TexOff" + OpName %_BindlessFastEnvMapCB_PS_t "_BindlessFastEnvMapCB_PS_t" + OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants" + OpName %_ "" + OpName %PerViewPushConst_t "PerViewPushConst_t" + OpMemberName %PerViewPushConst_t 0 "g_c" + OpName %__0 "" + OpName %g_tColor "g_tColor" + OpName %g_sAniso "g_sAniso" + OpName %i_vTextureCoords "i.vTextureCoords" + OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 RowMajor + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 Offset 0 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 MatrixStride 16 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 1 Offset 48 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 2 Offset 64 + OpDecorate %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 ArrayStride 80 + OpMemberDecorate %_BindlessFastEnvMapCB_PS_t 0 Offset 0 + OpDecorate %_BindlessFastEnvMapCB_PS_t Block + OpDecorate %_ DescriptorSet 0 + OpDecorate %_ Binding 2 + OpMemberDecorate %PerViewPushConst_t 0 Offset 0 + OpDecorate %PerViewPushConst_t Block + OpDecorate %g_tColor DescriptorSet 0 + OpDecorate %g_tColor Binding 0 + OpDecorate %g_sAniso DescriptorSet 0 + OpDecorate %g_sAniso Binding 1 + OpDecorate %i_vTextureCoords Location 0 + OpDecorate %_entryPointOutput_vColor Location 0 +;CHECK: OpDecorate %_runtimearr_uint ArrayStride 4 +;CHECK: OpDecorate %_struct_111 Block +;CHECK: OpMemberDecorate %_struct_111 0 Offset 0 +;CHECK: OpDecorate %113 DescriptorSet 7 +;CHECK: OpDecorate %113 Binding 1 +;CHECK: OpDecorate %_struct_139 Block +;CHECK: OpMemberDecorate %_struct_139 0 Offset 0 +;CHECK: OpMemberDecorate %_struct_139 1 Offset 4 +;CHECK: OpDecorate %141 DescriptorSet 7 +;CHECK: OpDecorate %141 Binding 0 +;CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %v4float = OpTypeVector %float 4 + %v3float = OpTypeVector %float 3 +%mat4v3float = OpTypeMatrix %v3float 4 +%PerBatchEnvMapConstantBuffer_t = OpTypeStruct %mat4v3float %v4float %v2float + %uint = OpTypeInt 32 0 + %uint_128 = OpConstant %uint 128 +%_arr_PerBatchEnvMapConstantBuffer_t_uint_128 = OpTypeArray %PerBatchEnvMapConstantBuffer_t %uint_128 +%_BindlessFastEnvMapCB_PS_t = OpTypeStruct %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 +%_ptr_Uniform__BindlessFastEnvMapCB_PS_t = OpTypePointer Uniform %_BindlessFastEnvMapCB_PS_t + %_ = OpVariable %_ptr_Uniform__BindlessFastEnvMapCB_PS_t Uniform + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 +%PerViewPushConst_t = OpTypeStruct %uint +%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t + %__0 = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant +%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint + %int_2 = OpConstant %int 2 +%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float + %46 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_46 = OpTypePointer UniformConstant %46 + %g_tColor = OpVariable %_ptr_UniformConstant_46 UniformConstant + %50 = OpTypeSampler +%_ptr_UniformConstant_50 = OpTypePointer UniformConstant %50 + %g_sAniso = OpVariable %_ptr_UniformConstant_50 UniformConstant + %54 = OpTypeSampledImage %46 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +;CHECK: %uint_0 = OpConstant %uint 0 +;CHECK: %uint_80 = OpConstant %uint 80 +;CHECK: %uint_64 = OpConstant %uint 64 +;CHECK: %uint_7 = OpConstant %uint 7 +;CHECK: %uint_2 = OpConstant %uint 2 +;CHECK: %uint_1 = OpConstant %uint 1 +;CHECK: %105 = OpTypeFunction %uint %uint %uint %uint +;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint +;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint +;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111 +;CHECK: %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer +;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint +;CHECK: %bool = OpTypeBool +;CHECK: %uint_3 = OpConstant %uint 3 +;CHECK: %132 = OpTypeFunction %void %uint %uint %uint %uint %uint +;CHECK:%_struct_139 = OpTypeStruct %uint %_runtimearr_uint +;CHECK:%_ptr_StorageBuffer__struct_139 = OpTypePointer StorageBuffer %_struct_139 +;CHECK: %141 = OpVariable %_ptr_StorageBuffer__struct_139 StorageBuffer +;CHECK: %uint_11 = OpConstant %uint 11 +;CHECK: %uint_4 = OpConstant %uint 4 +;CHECK: %uint_23 = OpConstant %uint 23 +;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float +;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +;CHECK: %v4uint = OpTypeVector %uint 4 +;CHECK: %uint_5 = OpConstant %uint 5 +;CHECK: %uint_8 = OpConstant %uint 8 +;CHECK: %uint_9 = OpConstant %uint 9 +;CHECK: %uint_10 = OpConstant %uint 10 +;CHECK: %uint_78 = OpConstant %uint 78 +;CHECK: %185 = OpConstantNull %v2float + %MainPs = OpFunction %void None %3 + %5 = OpLabel +;CHECK: %123 = OpFunctionCall %uint %104 %uint_1 %uint_2 %uint_0 +;CHECK: OpBranch %93 +;CHECK: %93 = OpLabel +;CHECK: OpBranch %92 +;CHECK: %92 = OpLabel + %66 = OpLoad %v2float %i_vTextureCoords + %79 = OpAccessChain %_ptr_PushConstant_uint %__0 %int_0 + %80 = OpLoad %uint %79 + %81 = OpAccessChain %_ptr_Uniform_v2float %_ %int_0 %80 %int_2 + %82 = OpLoad %v2float %81 +;CHECK-NOT: %82 = OpLoad %v2float %81 +;CHECK: %96 = OpIMul %uint %uint_80 %80 +;CHECK: %97 = OpIAdd %uint %uint_0 %96 +;CHECK: %99 = OpIAdd %uint %97 %uint_64 +;CHECK: %101 = OpIAdd %uint %99 %uint_7 +;CHECK: %125 = OpULessThan %bool %101 %123 +;CHECK: OpSelectionMerge %127 None +;CHECK: OpBranchConditional %125 %128 %129 +;CHECK: %128 = OpLabel +;CHECK: %130 = OpLoad %v2float %81 +;CHECK: OpBranch %127 +;CHECK: %129 = OpLabel +;CHECK: %184 = OpFunctionCall %void %131 %uint_78 %uint_3 %uint_0 %101 %123 +;CHECK: OpBranch %127 +;CHECK: %127 = OpLabel +;CHECK: %186 = OpPhi %v2float %130 %128 %185 %129 + %86 = OpFAdd %v2float %66 %82 +;CHECK-NOT: %86 = OpFAdd %v2float %66 %82 +;CHECK: %86 = OpFAdd %v2float %66 %186 + %87 = OpLoad %46 %g_tColor + %88 = OpLoad %50 %g_sAniso + %89 = OpSampledImage %54 %87 %88 + %91 = OpImageSampleImplicitLod %v4float %89 %86 + OpStore %_entryPointOutput_vColor %91 + OpReturn + OpFunctionEnd +;CHECK: %104 = OpFunction %uint None %105 +;CHECK: %106 = OpFunctionParameter %uint +;CHECK: %107 = OpFunctionParameter %uint +;CHECK: %108 = OpFunctionParameter %uint +;CHECK: %109 = OpLabel +;CHECK: %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %106 +;CHECK: %116 = OpLoad %uint %115 +;CHECK: %117 = OpIAdd %uint %116 %107 +;CHECK: %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117 +;CHECK: %119 = OpLoad %uint %118 +;CHECK: %120 = OpIAdd %uint %119 %108 +;CHECK: %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120 +;CHECK: %122 = OpLoad %uint %121 +;CHECK: OpReturnValue %122 +;CHECK: OpFunctionEnd +;CHECK: %131 = OpFunction %void None %132 +;CHECK: %133 = OpFunctionParameter %uint +;CHECK: %134 = OpFunctionParameter %uint +;CHECK: %135 = OpFunctionParameter %uint +;CHECK: %136 = OpFunctionParameter %uint +;CHECK: %137 = OpFunctionParameter %uint +;CHECK: %138 = OpLabel +;CHECK: %142 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_0 +;CHECK: %145 = OpAtomicIAdd %uint %142 %uint_4 %uint_0 %uint_11 +;CHECK: %146 = OpIAdd %uint %145 %uint_11 +;CHECK: %147 = OpArrayLength %uint %141 1 +;CHECK: %148 = OpULessThanEqual %bool %146 %147 +;CHECK: OpSelectionMerge %149 None +;CHECK: OpBranchConditional %148 %150 %149 +;CHECK: %150 = OpLabel +;CHECK: %151 = OpIAdd %uint %145 %uint_0 +;CHECK: %152 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %151 +;CHECK: OpStore %152 %uint_11 +;CHECK: %154 = OpIAdd %uint %145 %uint_1 +;CHECK: %155 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %154 +;CHECK: OpStore %155 %uint_23 +;CHECK: %156 = OpIAdd %uint %145 %uint_2 +;CHECK: %157 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %156 +;CHECK: OpStore %157 %133 +;CHECK: %158 = OpIAdd %uint %145 %uint_3 +;CHECK: %159 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %158 +;CHECK: OpStore %159 %uint_4 +;CHECK: %162 = OpLoad %v4float %gl_FragCoord +;CHECK: %164 = OpBitcast %v4uint %162 +;CHECK: %165 = OpCompositeExtract %uint %164 0 +;CHECK: %166 = OpIAdd %uint %145 %uint_4 +;CHECK: %167 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %166 +;CHECK: OpStore %167 %165 +;CHECK: %168 = OpCompositeExtract %uint %164 1 +;CHECK: %170 = OpIAdd %uint %145 %uint_5 +;CHECK: %171 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %170 +;CHECK: OpStore %171 %168 +;CHECK: %172 = OpIAdd %uint %145 %uint_7 +;CHECK: %173 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %172 +;CHECK: OpStore %173 %134 +;CHECK: %175 = OpIAdd %uint %145 %uint_8 +;CHECK: %176 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %175 +;CHECK: OpStore %176 %135 +;CHECK: %178 = OpIAdd %uint %145 %uint_9 +;CHECK: %179 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %178 +;CHECK: OpStore %179 %136 +;CHECK: %181 = OpIAdd %uint %145 %uint_10 +;CHECK: %182 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %181 +;CHECK: OpStore %182 %137 +;CHECK: OpBranch %149 +;CHECK: %149 = OpLabel +;CHECK: OpReturn +;CHECK: OpFunctionEnd + )"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false, + false, true); +} + +TEST_F(InstBindlessTest, UniformArrayRefWithDescInit) { + // The buffer-oob and desc-init checks should use the same debug + // output buffer write function. + // + // Same source as UniformArrayRefNoDescInit + + const std::string text = R"( + OpCapability Shader +;CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor +;CHECK: OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %113 %144 %gl_FragCoord + OpExecutionMode %MainPs OriginUpperLeft + OpSource HLSL 500 + OpName %MainPs "MainPs" + OpName %PerBatchEnvMapConstantBuffer_t +"PerBatchEnvMapConstantBuffer_t" OpMemberName %PerBatchEnvMapConstantBuffer_t 0 +"g_matEnvMapWorldToLocal" OpMemberName %PerBatchEnvMapConstantBuffer_t 1 +"g_vEnvironmentMapBoxMins" OpMemberName %PerBatchEnvMapConstantBuffer_t 2 +"g_TexOff" OpName %_BindlessFastEnvMapCB_PS_t "_BindlessFastEnvMapCB_PS_t" + OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants" + OpName %_ "" + OpName %PerViewPushConst_t "PerViewPushConst_t" + OpMemberName %PerViewPushConst_t 0 "g_c" + OpName %__0 "" + OpName %g_tColor "g_tColor" + OpName %g_sAniso "g_sAniso" + OpName %i_vTextureCoords "i.vTextureCoords" + OpName %_entryPointOutput_vColor "@entryPointOutput.vColor" + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 RowMajor + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 Offset 0 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 MatrixStride 16 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 1 Offset 48 + OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 2 Offset 64 + OpDecorate %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 ArrayStride 80 + OpMemberDecorate %_BindlessFastEnvMapCB_PS_t 0 Offset 0 + OpDecorate %_BindlessFastEnvMapCB_PS_t Block + OpDecorate %_ DescriptorSet 0 + OpDecorate %_ Binding 2 + OpMemberDecorate %PerViewPushConst_t 0 Offset 0 + OpDecorate %PerViewPushConst_t Block + OpDecorate %g_tColor DescriptorSet 0 + OpDecorate %g_tColor Binding 0 + OpDecorate %g_sAniso DescriptorSet 0 + OpDecorate %g_sAniso Binding 1 + OpDecorate %i_vTextureCoords Location 0 + OpDecorate %_entryPointOutput_vColor Location 0 +;CHECK: OpDecorate %_runtimearr_uint ArrayStride 4 +;CHECK: OpDecorate %_struct_111 Block +;CHECK: OpMemberDecorate %_struct_111 0 Offset 0 +;CHECK: OpDecorate %113 DescriptorSet 7 +;CHECK: OpDecorate %113 Binding 1 +;CHECK: OpDecorate %_struct_142 Block +;CHECK: OpMemberDecorate %_struct_142 0 Offset 0 +;CHECK: OpMemberDecorate %_struct_142 1 Offset 4 +;CHECK: OpDecorate %144 DescriptorSet 7 +;CHECK: OpDecorate %144 Binding 0 +;CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v2float = OpTypeVector %float 2 + %v4float = OpTypeVector %float 4 + %v3float = OpTypeVector %float 3 +%mat4v3float = OpTypeMatrix %v3float 4 +%PerBatchEnvMapConstantBuffer_t = OpTypeStruct %mat4v3float %v4float %v2float + %uint = OpTypeInt 32 0 + %uint_128 = OpConstant %uint 128 +%_arr_PerBatchEnvMapConstantBuffer_t_uint_128 = OpTypeArray %PerBatchEnvMapConstantBuffer_t %uint_128 +%_BindlessFastEnvMapCB_PS_t = OpTypeStruct %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 +%_ptr_Uniform__BindlessFastEnvMapCB_PS_t = OpTypePointer Uniform %_BindlessFastEnvMapCB_PS_t + %_ = OpVariable %_ptr_Uniform__BindlessFastEnvMapCB_PS_t Uniform + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 +%PerViewPushConst_t = OpTypeStruct %uint +%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t + %__0 = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant +%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint + %int_2 = OpConstant %int 2 +%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float + %46 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_46 = OpTypePointer UniformConstant %46 + %g_tColor = OpVariable %_ptr_UniformConstant_46 UniformConstant + %50 = OpTypeSampler +%_ptr_UniformConstant_50 = OpTypePointer UniformConstant %50 + %g_sAniso = OpVariable %_ptr_UniformConstant_50 UniformConstant + %54 = OpTypeSampledImage %46 +%_ptr_Input_v2float = OpTypePointer Input %v2float +%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output +;CHECK: %uint_0 = OpConstant %uint 0 +;CHECK: %uint_80 = OpConstant %uint 80 +;CHECK: %uint_64 = OpConstant %uint 64 +;CHECK: %uint_7 = OpConstant %uint 7 +;CHECK: %uint_2 = OpConstant %uint 2 +;CHECK: %104 = OpTypeFunction %uint %uint %uint %uint %uint +;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint +;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint +;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111 +;CHECK: %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer +;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint +;CHECK: %bool = OpTypeBool +;CHECK: %uint_3 = OpConstant %uint 3 +;CHECK: %135 = OpTypeFunction %void %uint %uint %uint %uint %uint +;CHECK:%_struct_142 = OpTypeStruct %uint %_runtimearr_uint +;CHECK:%_ptr_StorageBuffer__struct_142 = OpTypePointer StorageBuffer %_struct_142 +;CHECK: %144 = OpVariable %_ptr_StorageBuffer__struct_142 StorageBuffer +;CHECK: %uint_11 = OpConstant %uint 11 +;CHECK: %uint_4 = OpConstant %uint 4 +;CHECK: %uint_1 = OpConstant %uint 1 +;CHECK: %uint_23 = OpConstant %uint 23 +;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float +;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +;CHECK: %v4uint = OpTypeVector %uint 4 +;CHECK: %uint_5 = OpConstant %uint 5 +;CHECK: %uint_8 = OpConstant %uint 8 +;CHECK: %uint_9 = OpConstant %uint 9 +;CHECK: %uint_10 = OpConstant %uint 10 +;CHECK: %uint_78 = OpConstant %uint 78 +;CHECK: %189 = OpConstantNull %v2float +;CHECK: %uint_83 = OpConstant %uint 83 +;CHECK: %201 = OpConstantNull %v4float + %MainPs = OpFunction %void None %3 + %5 = OpLabel +;CHECK: %126 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_2 %uint_0 +;CHECK: %191 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_0 %uint_0 +;CHECK: OpBranch %93 +;CHECK: %93 = OpLabel +;CHECK: OpBranch %92 +;CHECK: %92 = OpLabel + %66 = OpLoad %v2float %i_vTextureCoords + %79 = OpAccessChain %_ptr_PushConstant_uint %__0 %int_0 + %80 = OpLoad %uint %79 + %81 = OpAccessChain %_ptr_Uniform_v2float %_ %int_0 %80 %int_2 + %82 = OpLoad %v2float %81 + %86 = OpFAdd %v2float %66 %82 +;CHECK-NOT: %82 = OpLoad %v2float %81 +;CHECK-NOT: %86 = OpFAdd %v2float %66 %82 +;CHECK: %96 = OpIMul %uint %uint_80 %80 +;CHECK: %97 = OpIAdd %uint %uint_0 %96 +;CHECK: %99 = OpIAdd %uint %97 %uint_64 +;CHECK: %101 = OpIAdd %uint %99 %uint_7 +;CHECK: %128 = OpULessThan %bool %101 %126 +;CHECK: OpSelectionMerge %130 None +;CHECK: OpBranchConditional %128 %131 %132 +;CHECK: %131 = OpLabel +;CHECK: %133 = OpLoad %v2float %81 +;CHECK: OpBranch %130 +;CHECK: %132 = OpLabel +;CHECK: %188 = OpFunctionCall %void %134 %uint_78 %uint_3 %uint_0 %101 %126 +;CHECK: OpBranch %130 +;CHECK: %130 = OpLabel +;CHECK: %190 = OpPhi %v2float %133 %131 %189 %132 +;CHECK: %86 = OpFAdd %v2float %66 %190 + %87 = OpLoad %46 %g_tColor %88 = OpLoad %50 %g_sAniso %89 = + OpSampledImage %54 %87 %88 %91 = OpImageSampleImplicitLod %v4float %89 %86 + OpStore %_entryPointOutput_vColor %91 +;CHECK-NOT: %91 = OpImageSampleImplicitLod %v4float %89 %86 +;CHECK-NOT: OpStore %_entryPointOutput_vColor %91 +;CHECK: %192 = OpULessThan %bool %uint_0 %191 +;CHECK: OpSelectionMerge %193 None +;CHECK: OpBranchConditional %192 %194 %195 +;CHECK: %194 = OpLabel +;CHECK: %196 = OpLoad %46 %g_tColor +;CHECK: %197 = OpSampledImage %54 %196 %88 +;CHECK: %198 = OpImageSampleImplicitLod %v4float %197 %86 +;CHECK: OpBranch %193 +;CHECK: %195 = OpLabel +;CHECK: %200 = OpFunctionCall %void %134 %uint_83 %uint_1 %uint_0 %uint_0 %uint_0 +;CHECK: OpBranch %193 +;CHECK: %193 = OpLabel +;CHECK: %202 = OpPhi %v4float %198 %194 %201 %195 +;CHECK: OpStore %_entryPointOutput_vColor %202 + OpReturn + OpFunctionEnd +;CHECK: %103 = OpFunction %uint None %104 +;CHECK: %105 = OpFunctionParameter %uint +;CHECK: %106 = OpFunctionParameter %uint +;CHECK: %107 = OpFunctionParameter %uint +;CHECK: %108 = OpFunctionParameter %uint +;CHECK: %109 = OpLabel +;CHECK: %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %105 +;CHECK: %116 = OpLoad %uint %115 +;CHECK: %117 = OpIAdd %uint %116 %106 +;CHECK: %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117 +;CHECK: %119 = OpLoad %uint %118 +;CHECK: %120 = OpIAdd %uint %119 %107 +;CHECK: %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120 +;CHECK: %122 = OpLoad %uint %121 +;CHECK: %123 = OpIAdd %uint %122 %108 +;CHECK: %124 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %123 +;CHECK: %125 = OpLoad %uint %124 +;CHECK: OpReturnValue %125 +;CHECK: OpFunctionEnd +;CHECK: %134 = OpFunction %void None %135 +;CHECK: %136 = OpFunctionParameter %uint +;CHECK: %137 = OpFunctionParameter %uint +;CHECK: %138 = OpFunctionParameter %uint +;CHECK: %139 = OpFunctionParameter %uint +;CHECK: %140 = OpFunctionParameter %uint +;CHECK: %141 = OpLabel +;CHECK: %145 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_0 +;CHECK: %148 = OpAtomicIAdd %uint %145 %uint_4 %uint_0 %uint_11 +;CHECK: %149 = OpIAdd %uint %148 %uint_11 +;CHECK: %150 = OpArrayLength %uint %144 1 +;CHECK: %151 = OpULessThanEqual %bool %149 %150 +;CHECK: OpSelectionMerge %152 None +;CHECK: OpBranchConditional %151 %153 %152 +;CHECK: %153 = OpLabel +;CHECK: %154 = OpIAdd %uint %148 %uint_0 +;CHECK: %156 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %154 +;CHECK: OpStore %156 %uint_11 +;CHECK: %158 = OpIAdd %uint %148 %uint_1 +;CHECK: %159 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %158 +;CHECK: OpStore %159 %uint_23 +;CHECK: %160 = OpIAdd %uint %148 %uint_2 +;CHECK: %161 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %160 +;CHECK: OpStore %161 %136 +;CHECK: %162 = OpIAdd %uint %148 %uint_3 +;CHECK: %163 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %162 +;CHECK: OpStore %163 %uint_4 +;CHECK: %166 = OpLoad %v4float %gl_FragCoord +;CHECK: %168 = OpBitcast %v4uint %166 +;CHECK: %169 = OpCompositeExtract %uint %168 0 +;CHECK: %170 = OpIAdd %uint %148 %uint_4 +;CHECK: %171 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %170 +;CHECK: OpStore %171 %169 +;CHECK: %172 = OpCompositeExtract %uint %168 1 +;CHECK: %174 = OpIAdd %uint %148 %uint_5 +;CHECK: %175 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %174 +;CHECK: OpStore %175 %172 +;CHECK: %176 = OpIAdd %uint %148 %uint_7 +;CHECK: %177 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %176 +;CHECK: OpStore %177 %137 +;CHECK: %179 = OpIAdd %uint %148 %uint_8 +;CHECK: %180 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %179 +;CHECK: OpStore %180 %138 +;CHECK: %182 = OpIAdd %uint %148 %uint_9 +;CHECK: %183 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %182 +;CHECK: OpStore %183 %139 +;CHECK: %185 = OpIAdd %uint %148 %uint_10 +;CHECK: %186 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %185 +;CHECK: OpStore %186 %140 +;CHECK: OpBranch %152 +;CHECK: %152 = OpLabel +;CHECK: OpReturn +;CHECK: OpFunctionEnd + )"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, true, true, + true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Compute shader diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp index 437fe736..ff8be97f 100644 --- a/test/opt/ir_context_test.cpp +++ b/test/opt/ir_context_test.cpp @@ -37,22 +37,22 @@ using Analysis = IRContext::Analysis; using ::testing::Each; using ::testing::UnorderedElementsAre; -class DummyPassPreservesNothing : public Pass { +class NoopPassPreservesNothing : public Pass { public: - DummyPassPreservesNothing(Status s) : Pass(), status_to_return_(s) {} + NoopPassPreservesNothing(Status s) : Pass(), status_to_return_(s) {} - const char* name() const override { return "dummy-pass"; } + const char* name() const override { return "noop-pass"; } Status Process() override { return status_to_return_; } private: Status status_to_return_; }; -class DummyPassPreservesAll : public Pass { +class NoopPassPreservesAll : public Pass { public: - DummyPassPreservesAll(Status s) : Pass(), status_to_return_(s) {} + NoopPassPreservesAll(Status s) : Pass(), status_to_return_(s) {} - const char* name() const override { return "dummy-pass"; } + const char* name() const override { return "noop-pass"; } Status Process() override { return status_to_return_; } Analysis GetPreservedAnalyses() override { @@ -63,11 +63,11 @@ class DummyPassPreservesAll : public Pass { Status status_to_return_; }; -class DummyPassPreservesFirst : public Pass { +class NoopPassPreservesFirst : public Pass { public: - DummyPassPreservesFirst(Status s) : Pass(), status_to_return_(s) {} + NoopPassPreservesFirst(Status s) : Pass(), status_to_return_(s) {} - const char* name() const override { return "dummy-pass"; } + const char* name() const override { return "noop-pass"; } Status Process() override { return status_to_return_; } Analysis GetPreservedAnalyses() override { return IRContext::kAnalysisBegin; } @@ -116,7 +116,7 @@ TEST_F(IRContextTest, AllValidAfterPassNoChange) { built_analyses |= i; } - DummyPassPreservesNothing pass(Pass::Status::SuccessWithoutChange); + NoopPassPreservesNothing pass(Pass::Status::SuccessWithoutChange); Pass::Status s = pass.Run(&localContext); EXPECT_EQ(s, Pass::Status::SuccessWithoutChange); EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses)); @@ -132,7 +132,7 @@ TEST_F(IRContextTest, NoneValidAfterPassWithChange) { localContext.BuildInvalidAnalyses(i); } - DummyPassPreservesNothing pass(Pass::Status::SuccessWithChange); + NoopPassPreservesNothing pass(Pass::Status::SuccessWithChange); Pass::Status s = pass.Run(&localContext); EXPECT_EQ(s, Pass::Status::SuccessWithChange); for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; @@ -151,7 +151,7 @@ TEST_F(IRContextTest, AllPreservedAfterPassWithChange) { localContext.BuildInvalidAnalyses(i); } - DummyPassPreservesAll pass(Pass::Status::SuccessWithChange); + NoopPassPreservesAll pass(Pass::Status::SuccessWithChange); Pass::Status s = pass.Run(&localContext); EXPECT_EQ(s, Pass::Status::SuccessWithChange); for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; @@ -170,7 +170,7 @@ TEST_F(IRContextTest, PreserveFirstOnlyAfterPassWithChange) { localContext.BuildInvalidAnalyses(i); } - DummyPassPreservesFirst pass(Pass::Status::SuccessWithChange); + NoopPassPreservesFirst pass(Pass::Status::SuccessWithChange); Pass::Status s = pass.Run(&localContext); EXPECT_EQ(s, Pass::Status::SuccessWithChange); EXPECT_TRUE(localContext.AreAnalysesValid(IRContext::kAnalysisBegin)); @@ -912,7 +912,7 @@ OpFunctionEnd)"; BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo); - DummyPassPreservesAll pass(Pass::Status::SuccessWithChange); + NoopPassPreservesAll pass(Pass::Status::SuccessWithChange); pass.Run(ctx.get()); EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo)); @@ -978,7 +978,7 @@ OpFunctionEnd)"; BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo); - DummyPassPreservesAll pass(Pass::Status::SuccessWithChange); + NoopPassPreservesAll pass(Pass::Status::SuccessWithChange); pass.Run(ctx.get()); EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo)); @@ -1011,6 +1011,71 @@ OpFunctionEnd)"; dbg_decl1->GetSingleWordOperand(kDebugDeclareOperandVariableIndex) == 20); } +TEST_F(IRContextTest, DebugInstructionReplaceDebugScopeAndDebugInlinedAt) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +%2 = OpString "test" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeFloat 32 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpTypeInt 32 0 +%9 = OpConstant %8 32 +%10 = OpExtInst %3 %1 DebugExpression +%11 = OpExtInst %3 %1 DebugSource %2 +%12 = OpExtInst %3 %1 DebugCompilationUnit 1 4 %11 HLSL +%13 = OpExtInst %3 %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %3 +%14 = OpExtInst %3 %1 DebugFunction %2 %13 %11 0 0 %12 %2 FlagIsProtected|FlagIsPrivate 0 %17 +%15 = OpExtInst %3 %1 DebugInfoNone +%16 = OpExtInst %3 %1 DebugFunction %2 %13 %11 10 10 %12 %2 FlagIsProtected|FlagIsPrivate 0 %15 +%25 = OpExtInst %3 %1 DebugInlinedAt 0 %14 +%26 = OpExtInst %3 %1 DebugInlinedAt 2 %14 +%17 = OpFunction %3 None %4 +%18 = OpLabel +%19 = OpExtInst %3 %1 DebugScope %14 +%20 = OpVariable %6 Function +OpBranch %21 +%21 = OpLabel +%24 = OpExtInst %3 %1 DebugScope %16 +%22 = OpPhi %5 %7 %18 +OpBranch %23 +%23 = OpLabel +%27 = OpExtInst %3 %1 DebugScope %16 %25 +OpLine %2 0 0 +%28 = OpFAdd %5 %7 %7 +OpStore %20 %28 +OpReturn +OpFunctionEnd)"; + + std::unique_ptr<IRContext> ctx = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo); + NoopPassPreservesAll pass(Pass::Status::SuccessWithChange); + pass.Run(ctx.get()); + EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo)); + + auto* inst0 = ctx->get_def_use_mgr()->GetDef(20); + auto* inst1 = ctx->get_def_use_mgr()->GetDef(22); + auto* inst2 = ctx->get_def_use_mgr()->GetDef(28); + EXPECT_EQ(inst0->GetDebugScope().GetLexicalScope(), 14); + EXPECT_EQ(inst1->GetDebugScope().GetLexicalScope(), 16); + EXPECT_EQ(inst2->GetDebugScope().GetLexicalScope(), 16); + EXPECT_EQ(inst2->GetDebugInlinedAt(), 25); + + EXPECT_TRUE(ctx->ReplaceAllUsesWith(14, 12)); + EXPECT_TRUE(ctx->ReplaceAllUsesWith(16, 14)); + EXPECT_TRUE(ctx->ReplaceAllUsesWith(25, 26)); + EXPECT_EQ(inst0->GetDebugScope().GetLexicalScope(), 12); + EXPECT_EQ(inst1->GetDebugScope().GetLexicalScope(), 14); + EXPECT_EQ(inst2->GetDebugScope().GetLexicalScope(), 14); + EXPECT_EQ(inst2->GetDebugInlinedAt(), 26); +} + TEST_F(IRContextTest, AddDebugValueAfterReplaceUse) { const std::string text = R"( OpCapability Shader @@ -1055,7 +1120,7 @@ OpFunctionEnd)"; BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo); - DummyPassPreservesAll pass(Pass::Status::SuccessWithChange); + NoopPassPreservesAll pass(Pass::Status::SuccessWithChange); pass.Run(ctx.get()); EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo)); @@ -1070,11 +1135,13 @@ OpFunctionEnd)"; // No DebugValue should be added because result id '26' is not used for // DebugDeclare. - ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 26, 22, dbg_decl); + ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 22, + dbg_decl); EXPECT_EQ(dbg_decl->NextNode()->opcode(), SpvOpReturn); // DebugValue should be added because result id '20' is used for DebugDeclare. - ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 20, 22, dbg_decl); + ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 22, + dbg_decl); EXPECT_EQ(dbg_decl->NextNode()->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue); @@ -1087,13 +1154,15 @@ OpFunctionEnd)"; // No DebugValue should be added because result id '20' is not used for // DebugDeclare. - ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 20, 7, dbg_decl); + ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 7, + dbg_decl); Instruction* dbg_value = dbg_decl->NextNode(); EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue); EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 22); // DebugValue should be added because result id '26' is used for DebugDeclare. - ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 26, 7, dbg_decl); + ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 7, + dbg_decl); dbg_value = dbg_decl->NextNode(); EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue); EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 7); diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp index 39899e3e..3161d903 100644 --- a/test/opt/local_access_chain_convert_test.cpp +++ b/test/opt/local_access_chain_convert_test.cpp @@ -927,6 +927,37 @@ TEST_F(LocalAccessChainConvertTest, IdOverflowReplacingStore2) { EXPECT_EQ(Pass::Status::Failure, std::get<1>(result)); } +TEST_F(LocalAccessChainConvertTest, AccessChainWithNoIndex) { + const std::string before = + R"( +; CHECK: OpFunction +; CHECK: [[var:%\w+]] = OpVariable +; CHECK: OpStore [[var]] %true +; CHECK: OpLoad %bool [[var]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %void = OpTypeVoid + %4 = OpTypeFunction %void + %bool = OpTypeBool + %true = OpConstantTrue %bool +%_ptr_Function_bool = OpTypePointer Function %bool + %2 = OpFunction %void None %4 + %8 = OpLabel + %9 = OpVariable %_ptr_Function_bool Function + %10 = OpAccessChain %_ptr_Function_bool %9 + OpStore %10 %true + %11 = OpLoad %bool %10 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch<LocalAccessChainConvertPass>(before, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Assorted vector and matrix types diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp index 6581ebfc..3d3c4791 100644 --- a/test/opt/local_ssa_elim_test.cpp +++ b/test/opt/local_ssa_elim_test.cpp @@ -2165,6 +2165,135 @@ OpFunctionEnd SinglePassRunAndMatch<SSARewritePass>(text, true); } +TEST_F(LocalSSAElimTest, PartiallyKillDebugDeclare) { + // For a reference variable e.g., int i in the following example, + // we do not propagate DebugValue for a store or phi instruction + // out of the variable's scope. In that case, we should not remove + // DebugDeclare for the variable that we did not add its DebugValue. + // + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // int j; + // void main() + // { + // float f = 0.0; + // for (j=0; j<4; j++) { + // int& i = j; + // f = f + BC[i]; + // } + // fo = f; + // } + + const std::string text = R"( +; CHECK: [[f_name:%\w+]] = OpString "f" +; CHECK: [[i_name:%\w+]] = OpString "i" +; CHECK: [[fn:%\w+]] = OpExtInst %void [[ext:%\d+]] DebugFunction +; CHECK: [[bb:%\w+]] = OpExtInst %void [[ext]] DebugLexicalBlock +; CHECK: [[dbg_f:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[f_name]] {{%\w+}} {{%\w+}} 0 0 [[fn]] +; CHECK: [[dbg_i:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[i_name]] {{%\w+}} {{%\w+}} 0 0 [[bb]] + +; CHECK: OpStore %f %float_0 +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0 +; CHECK-NOT: DebugDeclare [[dbg_f]] +; CHECK: OpExtInst %void [[ext]] DebugDeclare [[dbg_i]] %j + +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +%file_name = OpString "test" +OpSource GLSL 140 +%float_name = OpString "float" +%main_name = OpString "main" +%f_name = OpString "f" +%i_name = OpString "i" +%j_name = OpString "j" +OpName %main "main" +OpName %f "f" +OpName %j "j" +OpName %BC "BC" +OpName %fo "fo" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Private_int = OpTypePointer Private %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +%j = OpVariable %_ptr_Private_int Private +%null_expr = OpExtInst %void %ext DebugExpression +%src = OpExtInst %void %ext DebugSource %file_name +%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float +%dbg_v4f = OpExtInst %void %ext DebugTypeVector %dbg_tf 4 +%main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_v4f %dbg_v4f +%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %main +%bb = OpExtInst %void %ext DebugLexicalBlock %src 0 0 %dbg_main +%dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal +%dbg_i = OpExtInst %void %ext DebugLocalVariable %i_name %dbg_v4f %src 0 0 %bb FlagIsLocal +%dbg_j = OpExtInst %void %ext DebugGlobalVariable %j_name %dbg_v4f %src 0 0 %dbg_main %j_name %j FlagIsPrivate +%main = OpFunction %void None %8 +%22 = OpLabel +%s0 = OpExtInst %void %ext DebugScope %dbg_main +%f = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %j %int_0 +%decl0 = OpExtInst %void %ext DebugDeclare %dbg_f %f %null_expr +OpBranch %23 +%23 = OpLabel +%s1 = OpExtInst %void %ext DebugScope %dbg_main +OpLoopMerge %24 %25 None +OpBranch %26 +%26 = OpLabel +%s2 = OpExtInst %void %ext DebugScope %dbg_main +%27 = OpLoad %int %j +%28 = OpSLessThan %bool %27 %int_4 +OpBranchConditional %28 %29 %24 +%29 = OpLabel +%s3 = OpExtInst %void %ext DebugScope %bb +%decl1 = OpExtInst %void %ext DebugDeclare %dbg_i %j %null_expr +%30 = OpLoad %float %f +%31 = OpLoad %int %j +%32 = OpAccessChain %_ptr_Input_float %BC %31 +%33 = OpLoad %float %32 +%34 = OpFAdd %float %30 %33 +OpStore %f %34 +OpBranch %25 +%25 = OpLabel +%s4 = OpExtInst %void %ext DebugScope %dbg_main +%35 = OpLoad %int %j +%36 = OpIAdd %int %35 %int_1 +OpStore %j %36 +OpBranch %23 +%24 = OpLabel +%s5 = OpExtInst %void %ext DebugScope %dbg_main +%37 = OpLoad %float %f +OpStore %fo %37 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch<SSARewritePass>(text, true); +} + TEST_F(LocalSSAElimTest, DebugValueForReferenceVariable) { // #version 140 // @@ -3362,6 +3491,199 @@ OpFunctionEnd SinglePassRunAndMatch<SSARewritePass>(text, true); } +TEST_F(LocalSSAElimTest, RemoveDebugDeclareWithoutLoads) { + // Check that the DebugDeclare for c is removed even though its loads + // had been removed previously by single block store/load optimization. + // In the presence of DebugDeclare, single-block can and does remove loads, + // but cannot change the stores into DebugValues and remove the DebugDeclare + // because it is only a per block optimization, not a function optimization. + // So SSA-rewrite must perform this role. + // + // Texture2D g_tColor; + // SamplerState g_sAniso; + // + // struct PS_INPUT + // { + // float2 vTextureCoords2 : TEXCOORD2; + // float2 vTextureCoords3 : TEXCOORD3; + // }; + // + // struct PS_OUTPUT + // { + // float4 vColor : SV_Target0; + // }; + // + // PS_OUTPUT MainPs(PS_INPUT i) + // { + // PS_OUTPUT ps_output; + // float4 c; + // c = g_tColor.Sample(g_sAniso, i.vTextureCoords2.xy); + // c += g_tColor.Sample(g_sAniso, i.vTextureCoords3.xy); + // ps_output.vColor = c; + // return ps_output; + // } + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "OpenCL.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %g_sAniso %in_var_TEXCOORD2 %in_var_TEXCOORD3 %out_var_SV_Target0 + OpExecutionMode %MainPs OriginUpperLeft + %22 = OpString "foo.frag" + %26 = OpString "PS_OUTPUT" + %30 = OpString "float" + %33 = OpString "vColor" + %35 = OpString "PS_INPUT" + %40 = OpString "vTextureCoords3" + %42 = OpString "vTextureCoords2" + %44 = OpString "@type.2d.image" + %45 = OpString "type.2d.image" + %47 = OpString "Texture2D.TemplateParam" + %51 = OpString "src.MainPs" + %55 = OpString "c" + %57 = OpString "ps_output" + %60 = OpString "i" + %62 = OpString "@type.sampler" + %63 = OpString "type.sampler" + %65 = OpString "g_sAniso" + %67 = OpString "g_tColor" + OpName %type_2d_image "type.2d.image" + OpName %g_tColor "g_tColor" + OpName %type_sampler "type.sampler" + OpName %g_sAniso "g_sAniso" + OpName %in_var_TEXCOORD2 "in.var.TEXCOORD2" + OpName %in_var_TEXCOORD3 "in.var.TEXCOORD3" + OpName %out_var_SV_Target0 "out.var.SV_Target0" + OpName %MainPs "MainPs" + OpName %PS_INPUT "PS_INPUT" + OpMemberName %PS_INPUT 0 "vTextureCoords2" + OpMemberName %PS_INPUT 1 "vTextureCoords3" + OpName %param_var_i "param.var.i" + OpName %PS_OUTPUT "PS_OUTPUT" + OpMemberName %PS_OUTPUT 0 "vColor" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %in_var_TEXCOORD2 Location 0 + OpDecorate %in_var_TEXCOORD3 Location 1 + OpDecorate %out_var_SV_Target0 Location 0 + OpDecorate %g_tColor DescriptorSet 0 + OpDecorate %g_tColor Binding 0 + OpDecorate %g_sAniso DescriptorSet 0 + OpDecorate %g_sAniso Binding 1 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %uint = OpTypeInt 32 0 + %uint_32 = OpConstant %uint 32 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image +%type_sampler = OpTypeSampler +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler + %v2float = OpTypeVector %float 2 +%_ptr_Input_v2float = OpTypePointer Input %v2float + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %uint_128 = OpConstant %uint 128 + %uint_0 = OpConstant %uint 0 + %uint_64 = OpConstant %uint 64 + %69 = OpTypeFunction %void + %PS_INPUT = OpTypeStruct %v2float %v2float +%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT + %PS_OUTPUT = OpTypeStruct %v4float +%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Function_v2float = OpTypePointer Function %v2float +%type_sampled_image = OpTypeSampledImage %type_2d_image + %g_tColor = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant + %g_sAniso = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant +%in_var_TEXCOORD2 = OpVariable %_ptr_Input_v2float Input +%in_var_TEXCOORD3 = OpVariable %_ptr_Input_v2float Input +%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output + %43 = OpExtInst %void %1 DebugInfoNone + %59 = OpExtInst %void %1 DebugExpression + %24 = OpExtInst %void %1 DebugSource %22 + %25 = OpExtInst %void %1 DebugCompilationUnit 1 4 %24 HLSL + %28 = OpExtInst %void %1 DebugTypeComposite %26 Structure %24 11 1 %25 %26 %uint_128 FlagIsProtected|FlagIsPrivate %29 + %31 = OpExtInst %void %1 DebugTypeBasic %30 %uint_32 Float + %32 = OpExtInst %void %1 DebugTypeVector %31 4 + %29 = OpExtInst %void %1 DebugTypeMember %33 %32 %24 13 5 %28 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate + %36 = OpExtInst %void %1 DebugTypeComposite %35 Structure %24 5 1 %25 %35 %uint_128 FlagIsProtected|FlagIsPrivate %37 %38 + %39 = OpExtInst %void %1 DebugTypeVector %31 2 + %38 = OpExtInst %void %1 DebugTypeMember %40 %39 %24 8 5 %36 %uint_64 %uint_64 FlagIsProtected|FlagIsPrivate + %37 = OpExtInst %void %1 DebugTypeMember %42 %39 %24 7 5 %36 %uint_0 %uint_64 FlagIsProtected|FlagIsPrivate + %46 = OpExtInst %void %1 DebugTypeComposite %44 Class %24 0 0 %25 %45 %43 FlagIsProtected|FlagIsPrivate + %50 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %28 %36 + %52 = OpExtInst %void %1 DebugFunction %51 %50 %24 16 1 %25 %51 FlagIsProtected|FlagIsPrivate 17 %43 + %54 = OpExtInst %void %1 DebugLexicalBlock %24 17 1 %52 + %56 = OpExtInst %void %1 DebugLocalVariable %55 %32 %24 20 12 %54 FlagIsLocal + %58 = OpExtInst %void %1 DebugLocalVariable %57 %28 %24 18 15 %54 FlagIsLocal + %61 = OpExtInst %void %1 DebugLocalVariable %60 %36 %24 16 29 %52 FlagIsLocal 1 + %64 = OpExtInst %void %1 DebugTypeComposite %62 Structure %24 0 0 %25 %63 %43 FlagIsProtected|FlagIsPrivate + %66 = OpExtInst %void %1 DebugGlobalVariable %65 %64 %24 3 14 %25 %65 %g_sAniso FlagIsDefinition + %68 = OpExtInst %void %1 DebugGlobalVariable %67 %46 %24 1 11 %25 %67 %g_tColor FlagIsDefinition + %MainPs = OpFunction %void None %69 + %70 = OpLabel + %135 = OpExtInst %void %1 DebugScope %54 + %111 = OpVariable %_ptr_Function_PS_OUTPUT Function + %112 = OpVariable %_ptr_Function_v4float Function + %136 = OpExtInst %void %1 DebugNoScope +%param_var_i = OpVariable %_ptr_Function_PS_INPUT Function + %74 = OpLoad %v2float %in_var_TEXCOORD2 + %75 = OpLoad %v2float %in_var_TEXCOORD3 + %76 = OpCompositeConstruct %PS_INPUT %74 %75 + OpStore %param_var_i %76 + %137 = OpExtInst %void %1 DebugScope %52 + %115 = OpExtInst %void %1 DebugDeclare %61 %param_var_i %59 + %138 = OpExtInst %void %1 DebugScope %54 + %116 = OpExtInst %void %1 DebugDeclare %58 %111 %59 + %117 = OpExtInst %void %1 DebugDeclare %56 %112 %59 +;CHECK-NOT: %117 = OpExtInst %void %1 DebugDeclare %56 %112 %59 + OpLine %22 21 9 + %118 = OpLoad %type_2d_image %g_tColor + OpLine %22 21 29 + %119 = OpLoad %type_sampler %g_sAniso + OpLine %22 21 40 + %120 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_0 + %121 = OpLoad %v2float %120 + OpLine %22 21 9 + %122 = OpSampledImage %type_sampled_image %118 %119 + %123 = OpImageSampleImplicitLod %v4float %122 %121 None + OpLine %22 21 5 + OpStore %112 %123 +;CHECK: %140 = OpExtInst %void %1 DebugValue %56 %123 %59 + OpLine %22 22 10 + %124 = OpLoad %type_2d_image %g_tColor + OpLine %22 22 30 + %125 = OpLoad %type_sampler %g_sAniso + OpLine %22 22 41 + %126 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_1 + %127 = OpLoad %v2float %126 + OpLine %22 22 10 + %128 = OpSampledImage %type_sampled_image %124 %125 + %129 = OpImageSampleImplicitLod %v4float %128 %127 None + OpLine %22 22 7 + %131 = OpFAdd %v4float %123 %129 + OpLine %22 22 5 + OpStore %112 %131 +;CHECK: %141 = OpExtInst %void %1 DebugValue %56 %131 %59 + OpLine %22 23 5 + %133 = OpAccessChain %_ptr_Function_v4float %111 %int_0 + OpStore %133 %131 + OpLine %22 24 12 + %134 = OpLoad %PS_OUTPUT %111 + %139 = OpExtInst %void %1 DebugNoScope + %79 = OpCompositeExtract %v4float %134 0 + OpStore %out_var_SV_Target0 %79 + OpReturn + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<SSARewritePass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // No optimization in the presence of diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp index 6030135f..016316ae 100644 --- a/test/opt/loop_optimizations/unroll_simple.cpp +++ b/test/opt/loop_optimizations/unroll_simple.cpp @@ -194,6 +194,190 @@ OpFunctionEnd SinglePassRunAndCheck<LoopUnroller>(text, output, false); } +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + float x[4]; + for (int i = 0; i < 4; ++i) { + x[i] = 1.0f; + } +} +*/ +TEST_F(PassClassTest, SimpleFullyUnrollWithDebugInstructions) { + // We must preserve the debug information including OpenCL.DebugInfo.100 + // instructions and OpLine instructions. Only the first block has + // DebugDeclare and DebugValue used for the declaration (i.e., DebugValue + // with Deref). Other blocks unrolled from the loop must not contain them. + const std::string text = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 330 +%file_name = OpString "test" +%float_name = OpString "float" +%main_name = OpString "main" +%f_name = OpString "f" +%i_name = OpString "i" +OpName %2 "main" +OpName %5 "x" +OpName %3 "c" +OpDecorate %3 Location 0 +%6 = OpTypeVoid +%7 = OpTypeFunction %6 +%8 = OpTypeInt 32 1 +%9 = OpTypePointer Function %8 +%10 = OpConstant %8 0 +%11 = OpConstant %8 4 +%12 = OpTypeBool +%13 = OpTypeFloat 32 +%14 = OpTypeInt 32 0 +%uint_32 = OpConstant %14 32 +%15 = OpConstant %14 4 +%16 = OpTypeArray %13 %15 +%17 = OpTypePointer Function %16 +%18 = OpConstant %13 1 +%19 = OpTypePointer Function %13 +%20 = OpConstant %8 1 +%21 = OpTypeVector %13 4 +%22 = OpTypePointer Output %21 +%3 = OpVariable %22 Output +%null_expr = OpExtInst %6 %ext DebugExpression +%deref = OpExtInst %6 %ext DebugOperation Deref +%deref_expr = OpExtInst %6 %ext DebugExpression %deref +%src = OpExtInst %6 %ext DebugSource %file_name +%cu = OpExtInst %6 %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %6 %ext DebugTypeBasic %float_name %uint_32 Float +%dbg_v4f = OpExtInst %6 %ext DebugTypeVector %dbg_tf 4 +%main_ty = OpExtInst %6 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_v4f %dbg_v4f +%dbg_main = OpExtInst %6 %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %2 +%bb = OpExtInst %6 %ext DebugLexicalBlock %src 0 0 %dbg_main +%dbg_f = OpExtInst %6 %ext DebugLocalVariable %f_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal +%dbg_i = OpExtInst %6 %ext DebugLocalVariable %i_name %dbg_v4f %src 1 0 %bb FlagIsLocal + +; CHECK: [[f:%\w+]] = OpString "f" +; CHECK: [[i:%\w+]] = OpString "i" +; CHECK: [[int_0:%\w+]] = OpConstant {{%\w+}} 0 + +; CHECK: [[null_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression +; CHECK: [[deref:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugOperation Deref +; CHECK: [[deref_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression [[deref]] +; CHECK: [[dbg_fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction +; CHECK: [[dbg_bb:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLexicalBlock +; CHECK: [[dbg_f:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[f]] {{%\w+}} {{%\w+}} 0 0 [[dbg_fn]] +; CHECK: [[dbg_i:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[i]] {{%\w+}} {{%\w+}} 1 0 [[dbg_bb]] + +%2 = OpFunction %6 None %7 +%23 = OpLabel + +; The first block has DebugDeclare and DebugValue with Deref +; +; CHECK: OpLabel +; CHECK: DebugScope [[dbg_fn]] +; CHECK: [[x:%\w+]] = OpVariable {{%\w+}} Function +; CHECK: OpLine {{%\w+}} 0 0 +; CHECK: OpBranch +; CHECK: OpLabel +; CHECK: DebugScope [[dbg_fn]] +; CHECK: DebugValue [[dbg_f]] [[int_0]] [[null_expr]] +; CHECK: OpBranch +; CHECK: DebugScope [[dbg_fn]] +; CHECK: OpLine {{%\w+}} 1 1 +; CHECK: OpSLessThan +; CHECK: OpLine {{%\w+}} 2 0 +; CHECK: OpBranch +; CHECK: OpLabel +; CHECK: DebugScope [[dbg_bb]] +; CHECK: DebugDeclare [[dbg_f]] [[x]] [[null_expr]] +; CHECK: DebugValue [[dbg_i]] [[x]] [[deref_expr]] +; CHECK: OpLine {{%\w+}} 3 0 +; +; CHECK: OpLine {{%\w+}} 6 0 +; CHECK: [[add:%\w+]] = OpIAdd +; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]] +; CHECK: OpLine {{%\w+}} 7 0 + +; Other blocks do not have DebugDeclare and DebugValue with Deref +; +; CHECK: DebugScope [[dbg_fn]] +; CHECK: OpLine {{%\w+}} 1 1 +; CHECK: OpSLessThan +; CHECK: OpLine {{%\w+}} 2 0 +; CHECK: OpBranch +; CHECK: OpLabel +; +; CHECK: DebugScope [[dbg_bb]] +; CHECK-NOT: DebugDeclare [[dbg_f]] [[x]] [[null_expr]] +; CHECK-NOT: DebugValue [[dbg_i]] [[x]] [[deref_expr]] +; CHECK: OpLine {{%\w+}} 3 0 +; +; CHECK: OpLine {{%\w+}} 6 0 +; CHECK: [[add:%\w+]] = OpIAdd +; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]] +; CHECK: OpLine {{%\w+}} 7 0 +; +; CHECK-NOT: DebugDeclare [[dbg_f]] [[x]] [[null_expr]] +; CHECK-NOT: DebugValue [[dbg_i]] [[x]] [[deref_expr]] +; CHECK: DebugScope [[dbg_fn]] +; CHECK: OpLine {{%\w+}} 8 0 +; CHECK: OpReturn + +%s0 = OpExtInst %6 %ext DebugScope %dbg_main +%5 = OpVariable %17 Function +OpLine %file_name 0 0 +OpBranch %24 +%24 = OpLabel +%s1 = OpExtInst %6 %ext DebugScope %dbg_main +%35 = OpPhi %8 %10 %23 %34 %26 +%value0 = OpExtInst %6 %ext DebugValue %dbg_f %35 %null_expr +OpLine %file_name 1 0 +OpLoopMerge %25 %26 Unroll +OpBranch %27 +%27 = OpLabel +%s2 = OpExtInst %6 %ext DebugScope %dbg_main +OpLine %file_name 1 1 +%29 = OpSLessThan %12 %35 %11 +OpLine %file_name 2 0 +OpBranchConditional %29 %30 %25 +%30 = OpLabel +%s3 = OpExtInst %6 %ext DebugScope %bb +%decl0 = OpExtInst %6 %ext DebugDeclare %dbg_f %5 %null_expr +%decl1 = OpExtInst %6 %ext DebugValue %dbg_i %5 %deref_expr +OpLine %file_name 3 0 +%32 = OpAccessChain %19 %5 %35 +OpLine %file_name 4 0 +OpStore %32 %18 +OpLine %file_name 5 0 +OpBranch %26 +%26 = OpLabel +%s4 = OpExtInst %6 %ext DebugScope %dbg_main +OpLine %file_name 6 0 +%34 = OpIAdd %8 %35 %20 +%value1 = OpExtInst %6 %ext DebugValue %dbg_f %34 %null_expr +OpLine %file_name 7 0 +OpBranch %24 +%25 = OpLabel +%s5 = OpExtInst %6 %ext DebugScope %dbg_main +OpLine %file_name 8 0 +OpReturn +OpFunctionEnd)"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndMatch<LoopUnroller>(text, true); +} + template <int factor> class PartialUnrollerTestPass : public Pass { public: @@ -3068,6 +3252,155 @@ TEST_F(PassClassTest, UnreachableMerge) { SinglePassRunAndMatch<opt::LoopUnroller>(text, true, kFullyUnroll, kUnrollFactor); } + +TEST_F(PassClassTest, InitValueIsConstantNull) { + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstantNull %6 + %13 = OpConstant %6 1 + %21 = OpConstant %6 1 + %10 = OpTypeBool + %17 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %11 = OpLabel + OpBranch %5 + %5 = OpLabel + %23 = OpPhi %6 %7 %11 %20 %15 + OpLoopMerge %8 %15 Unroll + OpBranch %14 + %14 = OpLabel + %9 = OpSLessThan %10 %23 %13 + OpBranchConditional %9 %15 %8 + %15 = OpLabel + %20 = OpIAdd %6 %23 %21 + OpBranch %5 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const std::string output = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource ESSL 320 +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpConstantNull %5 +%7 = OpConstant %5 1 +%8 = OpConstant %5 1 +%9 = OpTypeBool +%10 = OpTypePointer Function %5 +%2 = OpFunction %3 None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpSLessThan %9 %6 %7 +OpBranch %15 +%15 = OpLabel +%14 = OpIAdd %5 %6 %8 +OpBranch %16 +%16 = OpLabel +OpReturn +OpFunctionEnd +)"; + + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << shader << std::endl; + + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck<LoopUnroller>(shader, output, false); +} + +TEST_F(PassClassTest, ConditionValueIsConstantNull) { + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpConstantNull %6 + %13 = OpConstant %6 1 + %21 = OpConstant %6 1 + %10 = OpTypeBool + %17 = OpTypePointer Function %6 + %4 = OpFunction %2 None %3 + %11 = OpLabel + OpBranch %5 + %5 = OpLabel + %23 = OpPhi %6 %13 %11 %20 %15 + OpLoopMerge %8 %15 Unroll + OpBranch %14 + %14 = OpLabel + %9 = OpSGreaterThan %10 %23 %7 + OpBranchConditional %9 %15 %8 + %15 = OpLabel + %20 = OpISub %6 %23 %21 + OpBranch %5 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const std::string output = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource ESSL 320 +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpConstantNull %5 +%7 = OpConstant %5 1 +%8 = OpConstant %5 1 +%9 = OpTypeBool +%10 = OpTypePointer Function %5 +%2 = OpFunction %3 None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpSGreaterThan %9 %7 %6 +OpBranch %15 +%15 = OpLabel +%14 = OpISub %5 %7 %8 +OpBranch %16 +%16 = OpLabel +OpReturn +OpFunctionEnd +)"; + + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << shader << std::endl; + + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck<LoopUnroller>(shader, output, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp index 406da093..a3c2eed7 100644 --- a/test/opt/module_test.cpp +++ b/test/opt/module_test.cpp @@ -295,6 +295,47 @@ OpLine %5 1 1 AssembleAndDisassemble(text); } + +TEST(ModuleTest, NonSemanticInfoIteration) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%4 = OpExtInst %2 %1 1 +%5 = OpFunction %2 None %3 +%6 = OpLabel +%7 = OpExtInst %2 %1 1 +OpReturn +OpFunctionEnd +%8 = OpExtInst %2 %1 1 +%9 = OpFunction %2 None %3 +%10 = OpLabel +%11 = OpExtInst %2 %1 1 +OpReturn +OpFunctionEnd +%12 = OpExtInst %2 %1 1 +)"; + + std::unique_ptr<IRContext> context = BuildModule(text); + std::unordered_set<uint32_t> non_semantic_ids; + context->module()->ForEachInst( + [&non_semantic_ids](const Instruction* inst) { + if (inst->opcode() == SpvOpExtInst) { + non_semantic_ids.insert(inst->result_id()); + } + }, + false); + + EXPECT_EQ(1, non_semantic_ids.count(4)); + EXPECT_EQ(1, non_semantic_ids.count(7)); + EXPECT_EQ(1, non_semantic_ids.count(8)); + EXPECT_EQ(1, non_semantic_ids.count(11)); + EXPECT_EQ(1, non_semantic_ids.count(12)); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h index 64c089d8..e5208218 100644 --- a/test/opt/pass_fixture.h +++ b/test/opt/pass_fixture.h @@ -176,9 +176,11 @@ class PassTest : public TestT { // result, using checks parsed from |original|. Always skips OpNop. // This does *not* involve pass manager. Callers are suggested to use // SCOPED_TRACE() for better messages. + // Returns a tuple of disassembly string and the boolean value from the pass + // Process() function. template <typename PassT, typename... Args> - void SinglePassRunAndMatch(const std::string& original, bool do_validation, - Args&&... args) { + std::tuple<std::string, Pass::Status> SinglePassRunAndMatch( + const std::string& original, bool do_validation, Args&&... args) { const bool skip_nop = true; auto pass_result = SinglePassRunAndDisassemble<PassT>( original, skip_nop, do_validation, std::forward<Args>(args)...); @@ -187,6 +189,7 @@ class PassTest : public TestT { EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) << match_result.message() << "\nChecking result:\n" << disassembly; + return pass_result; } // Runs a single pass of class |PassT| on the binary assembled from the diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp index f4269041..fd97efab 100644 --- a/test/opt/pass_merge_return_test.cpp +++ b/test/opt/pass_merge_return_test.cpp @@ -534,7 +534,7 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) { ; CHECK: [[true:%\w+]] = OpConstantTrue ; CHECK: OpFunction ; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]] -; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]] +; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]] ; CHECK: OpSelectionMerge [[merge_lab:%\w+]] ; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]] ; CHECK: [[if_lab]] = OpLabel @@ -542,9 +542,9 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) { ; CHECK-NEXT: OpBranch ; CHECK: [[then_lab]] = OpLabel ; CHECK-NEXT: OpStore [[var]] [[true]] -; CHECK-NEXT: OpBranch [[dummy_loop_merge]] +; CHECK-NEXT: OpBranch [[single_case_switch_merge]] ; CHECK: [[merge_lab]] = OpLabel -; CHECK: [[dummy_loop_merge]] = OpLabel +; CHECK: [[single_case_switch_merge]] = OpLabel ; CHECK-NEXT: OpReturn OpCapability Addresses OpCapability Shader @@ -631,10 +631,10 @@ TEST_F(MergeReturnPassTest, SplitBlockUsedInPhi) { const std::string before = R"( ; CHECK: OpFunction -; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]] +; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]] ; CHECK: OpLoopMerge [[loop_merge:%\w+]] ; CHECK: [[loop_merge]] = OpLabel -; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] [[old_code_path:%\w+]] +; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge]] [[old_code_path:%\w+]] ; CHECK: [[old_code_path:%\w+]] = OpLabel ; CHECK: OpBranchConditional {{%\w+}} [[side_node:%\w+]] [[phi_block:%\w+]] ; CHECK: [[phi_block]] = OpLabel @@ -828,7 +828,7 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) { R"( ; CHECK: OpFunction ; CHECK: [[ret_flag:%\w+]] = OpVariable %_ptr_Function_bool Function %false -; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]] +; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]] ; CHECK: OpLoopMerge [[loop1_merge:%\w+]] {{%\w+}} ; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] {{%\w+}} ; CHECK: [[if_lab]] = OpLabel @@ -837,7 +837,7 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) { ; CHECK: [[loop1_merge]] = OpLabel ; CHECK-NEXT: [[ld:%\w+]] = OpLoad %bool [[ret_flag]] ; CHECK-NOT: OpLabel -; CHECK: OpBranchConditional [[ld]] [[dummy_loop_merge]] [[empty_block:%\w+]] +; CHECK: OpBranchConditional [[ld]] [[single_case_switch_merge]] [[empty_block:%\w+]] ; CHECK: [[empty_block]] = OpLabel ; CHECK-NEXT: OpBranch [[loop2:%\w+]] ; CHECK: [[loop2]] = OpLabel @@ -1217,7 +1217,7 @@ TEST_F(MergeReturnPassTest, NestedLoopMerge) { const std::string test = R"( ; CHECK: OpFunction -; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]] +; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]] ; CHECK: OpLoopMerge [[outer_loop_merge:%\w+]] ; CHECK: OpLoopMerge [[inner_loop_merge:%\w+]] ; CHECK: OpSelectionMerge @@ -1230,8 +1230,8 @@ TEST_F(MergeReturnPassTest, NestedLoopMerge) { ; CHECK: OpBranchConditional {{%\w+}} [[outer_loop_merge]] ; CHECK: [[outer_loop_merge]] = OpLabel ; CHECK-NOT: OpLabel -; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] -; CHECK: [[dummy_loop_merge]] = OpLabel +; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge]] +; CHECK: [[single_case_switch_merge]] = OpLabel ; CHECK-NOT: OpLabel ; CHECK: OpReturn OpCapability SampledBuffer @@ -2145,12 +2145,12 @@ TEST_F(MergeReturnPassTest, PhiInSecondMerge) { } TEST_F(MergeReturnPassTest, ReturnsInSwitch) { - // Cannot branch directly to dummy switch merge block from original switch. - // Must branch to merge block of original switch and then do predicated - // branch to merge block of dummy switch. + // Cannot branch directly to single case switch merge block from original + // switch. Must branch to merge block of original switch and then do + // predicated branch to merge block of single case switch. const std::string text = R"( -; CHECK: OpSelectionMerge [[dummy_merge_bb:%\w+]] +; CHECK: OpSelectionMerge [[single_case_switch_merge_bb:%\w+]] ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]] ; CHECK-NEXT: [[def_bb1]] = OpLabel ; CHECK: OpSelectionMerge @@ -2158,7 +2158,7 @@ TEST_F(MergeReturnPassTest, ReturnsInSwitch) { ; CHECK: OpBranch [[inner_merge_bb]] ; CHECK: OpBranch [[inner_merge_bb]] ; CHECK-NEXT: [[inner_merge_bb]] = OpLabel -; CHECK: OpBranchConditional {{%\w+}} [[dummy_merge_bb]] {{%\w+}} +; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge_bb]] {{%\w+}} OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 diff --git a/test/opt/private_to_local_test.cpp b/test/opt/private_to_local_test.cpp index 12306529..8b5ec59e 100644 --- a/test/opt/private_to_local_test.cpp +++ b/test/opt/private_to_local_test.cpp @@ -452,6 +452,50 @@ TEST_F(PrivateToLocalTest, IdBoundOverflow1) { EXPECT_EQ(Pass::Status::Failure, std::get<1>(result)); } +TEST_F(PrivateToLocalTest, DebugPrivateToLocal) { + // Debug instructions must not have any impact on changing the private + // variable to a local. + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + %10 = OpExtInstImport "OpenCL.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + %11 = OpString "test" + OpSource GLSL 430 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 32 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 +; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat 32 + %5 = OpTypeFloat 32 +; CHECK: [[newtype:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]] + %6 = OpTypePointer Private %5 +; CHECK-NOT: OpVariable [[.+]] Private + %8 = OpVariable %6 Private + + %12 = OpExtInst %3 %10 DebugTypeBasic %11 %14 Float + %15 = OpExtInst %3 %10 DebugSource %11 + %16 = OpExtInst %3 %10 DebugCompilationUnit 1 4 %15 GLSL +; CHECK-NOT: DebugGlobalVariable +; CHECK: [[dbg_newvar:%[a-zA-Z_\d]+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable + %17 = OpExtInst %3 %10 DebugGlobalVariable %11 %12 %15 0 0 %16 %11 %8 FlagIsDefinition + +; CHECK: OpFunction + %2 = OpFunction %3 None %4 +; CHECK: OpLabel + %7 = OpLabel +; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[newtype]] Function +; CHECK-NEXT: DebugDeclare [[dbg_newvar]] [[newvar]] +; CHECK: OpLoad [[float]] [[newvar]] + %9 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch<PrivateToLocalPass>(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp index 3cf46ca1..2130f695 100644 --- a/test/opt/scalar_replacement_test.cpp +++ b/test/opt/scalar_replacement_test.cpp @@ -1899,6 +1899,186 @@ TEST_F(ScalarReplacementTest, RelaxedPrecisionMemberDecoration) { SinglePassRunAndMatch<ScalarReplacementPass>(text, true); } +TEST_F(ScalarReplacementTest, DebugDeclare) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +%test = OpString "test" +OpName %6 "simple_struct" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%uint_32 = OpConstant %2 32 +%3 = OpTypeStruct %2 %2 %2 %2 +%4 = OpTypePointer Function %3 +%5 = OpTypePointer Function %2 +%6 = OpTypeFunction %2 +%7 = OpConstantNull %3 +%8 = OpConstant %2 0 +%9 = OpConstant %2 1 +%10 = OpConstant %2 2 +%11 = OpConstant %2 3 +%null_expr = OpExtInst %1 %ext DebugExpression +%src = OpExtInst %1 %ext DebugSource %test +%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float +%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1 +%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12 +%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal +%12 = OpFunction %2 None %6 +%13 = OpLabel +%scope = OpExtInst %1 %ext DebugScope %dbg_main +%14 = OpVariable %4 Function %7 + +; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref +; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable +; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]] +; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3 +; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 +; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 +; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 +; CHECK-NOT: DebugDeclare +%decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr + +%15 = OpInBoundsAccessChain %5 %14 %8 +%16 = OpLoad %2 %15 +%17 = OpAccessChain %5 %14 %10 +%18 = OpLoad %2 %17 +%19 = OpIAdd %2 %16 %18 +OpReturnValue %19 +OpFunctionEnd +)"; + + SinglePassRunAndMatch<ScalarReplacementPass>(text, true); +} + +TEST_F(ScalarReplacementTest, DebugValue) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +%test = OpString "test" +OpName %6 "simple_struct" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%uint_32 = OpConstant %2 32 +%3 = OpTypeStruct %2 %2 %2 %2 +%4 = OpTypePointer Function %3 +%5 = OpTypePointer Function %2 +%6 = OpTypeFunction %2 +%7 = OpConstantNull %3 +%8 = OpConstant %2 0 +%9 = OpConstant %2 1 +%10 = OpConstant %2 2 +%11 = OpConstant %2 3 +%deref = OpExtInst %1 %ext DebugOperation Deref +%deref_expr = OpExtInst %1 %ext DebugExpression %deref +%null_expr = OpExtInst %1 %ext DebugExpression +%src = OpExtInst %1 %ext DebugSource %test +%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float +%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1 +%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12 +%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal +%12 = OpFunction %2 None %6 +%13 = OpLabel +%scope = OpExtInst %1 %ext DebugScope %dbg_main +%14 = OpVariable %4 Function %7 + +; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref +; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]] +; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable +; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3 +%value = OpExtInst %1 %ext DebugValue %dbg_foo %14 %deref_expr + +%15 = OpInBoundsAccessChain %5 %14 %8 +%16 = OpLoad %2 %15 +%17 = OpAccessChain %5 %14 %10 +%18 = OpLoad %2 %17 +%19 = OpIAdd %2 %16 %18 +OpReturnValue %19 +OpFunctionEnd +)"; + + SinglePassRunAndMatch<ScalarReplacementPass>(text, true); +} + +TEST_F(ScalarReplacementTest, DebugDeclareRecursive) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +%ext = OpExtInstImport "OpenCL.DebugInfo.100" +OpMemoryModel Logical GLSL450 +%test = OpString "test" +OpName %6 "simple_struct" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%uint_32 = OpConstant %2 32 +%float = OpTypeFloat 32 +%float_1 = OpConstant %float 1 +%member = OpTypeStruct %2 %float +%3 = OpTypeStruct %2 %member %float +%4 = OpTypePointer Function %3 +%5 = OpTypePointer Function %2 +%ptr_float_Function = OpTypePointer Function %float +%6 = OpTypeFunction %2 +%cmember = OpConstantComposite %member %uint_32 %float_1 +%7 = OpConstantComposite %3 %uint_32 %cmember %float_1 +%8 = OpConstant %2 0 +%9 = OpConstant %2 1 +%10 = OpConstant %2 2 +%null_expr = OpExtInst %1 %ext DebugExpression +%src = OpExtInst %1 %ext DebugSource %test +%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL +%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float +%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1 +%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12 +%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal +%12 = OpFunction %2 None %6 +%13 = OpLabel +%scope = OpExtInst %1 %ext DebugScope %dbg_main +%14 = OpVariable %4 Function %7 + +; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref +; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable +; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]] +; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_float Function %float_1 +; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32 +; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_float Function %float_1 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_2 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 %int_0 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_1 %int_1 +; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 +; CHECK-NOT: DebugDeclare +%decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr + +%15 = OpInBoundsAccessChain %5 %14 %8 +%16 = OpLoad %2 %15 +%17 = OpAccessChain %ptr_float_Function %14 %10 +%18 = OpLoad %float %17 +%value = OpConvertFToU %2 %18 +%19 = OpIAdd %2 %16 %value +OpReturnValue %19 +OpFunctionEnd +)"; + + SinglePassRunAndMatch<ScalarReplacementPass>(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/strip_reflect_info_test.cpp b/test/opt/strip_reflect_info_test.cpp index 5db34b72..f3fc115a 100644 --- a/test/opt/strip_reflect_info_test.cpp +++ b/test/opt/strip_reflect_info_test.cpp @@ -25,6 +25,7 @@ namespace opt { namespace { using StripLineReflectInfoTest = PassTest<::testing::Test>; +using StripNonSemanticInfoTest = PassTest<::testing::Test>; // This test acts as an end-to-end code example on how to strip // reflection info from a SPIR-V module. Use this code pattern @@ -132,6 +133,99 @@ OpMemoryModel Logical Simple SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false); } +TEST_F(StripNonSemanticInfoTest, StripNonSemanticImport) { + std::string text = R"( +; CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info" +; CHECK-NOT: OpExtInstImport +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +)"; + + SinglePassRunAndMatch<StripReflectInfoPass>(text, true); +} + +TEST_F(StripNonSemanticInfoTest, StripNonSemanticGlobal) { + std::string text = R"( +; CHECK-NOT: OpExtInst +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%1 = OpExtInst %void %ext 1 +)"; + + SinglePassRunAndMatch<StripReflectInfoPass>(text, true); +} + +TEST_F(StripNonSemanticInfoTest, StripNonSemanticInFunction) { + std::string text = R"( +; CHECK-NOT: OpExtInst +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +%1 = OpExtInst %void %ext 1 %foo +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch<StripReflectInfoPass>(text, true); +} + +TEST_F(StripNonSemanticInfoTest, StripNonSemanticAfterFunction) { + std::string text = R"( +; CHECK-NOT: OpExtInst +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%1 = OpExtInst %void %ext 1 %foo +)"; + + SinglePassRunAndMatch<StripReflectInfoPass>(text, true); +} + +TEST_F(StripNonSemanticInfoTest, StripNonSemanticBetweenFunctions) { + std::string text = R"( +; CHECK-NOT: OpExtInst +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.Test" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%1 = OpExtInst %void %ext 1 %foo +%bar = OpFunction %void None %void_fn +%bar_entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch<StripReflectInfoPass>(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/struct_cfg_analysis_test.cpp b/test/opt/struct_cfg_analysis_test.cpp index 2d1deecc..e7031cb5 100644 --- a/test/opt/struct_cfg_analysis_test.cpp +++ b/test/opt/struct_cfg_analysis_test.cpp @@ -60,7 +60,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -72,7 +74,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -84,7 +88,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -129,7 +135,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -141,7 +149,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 1); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.LoopNestingDepth(2), 1); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -153,7 +163,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -165,7 +177,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 1); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.LoopNestingDepth(4), 1); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_TRUE(analysis.IsContinueBlock(4)); @@ -215,7 +229,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -227,7 +243,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 1); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.LoopNestingDepth(2), 1); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -239,7 +257,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -251,7 +271,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 1); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.LoopNestingDepth(4), 1); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_TRUE(analysis.IsContinueBlock(4)); @@ -263,7 +285,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 1); EXPECT_EQ(analysis.MergeBlock(5), 6); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 3); + EXPECT_EQ(analysis.LoopNestingDepth(5), 1); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_FALSE(analysis.IsContinueBlock(5)); @@ -275,7 +299,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 1); EXPECT_EQ(analysis.ContainingLoop(6), 1); EXPECT_EQ(analysis.MergeBlock(6), 3); + EXPECT_EQ(analysis.NestingDepth(6), 1); EXPECT_EQ(analysis.LoopMergeBlock(6), 3); + EXPECT_EQ(analysis.LoopNestingDepth(6), 1); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -325,7 +351,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -337,7 +365,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -349,7 +379,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -361,7 +393,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 0); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 0); + EXPECT_EQ(analysis.LoopNestingDepth(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -373,7 +407,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 2); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 4); + EXPECT_EQ(analysis.LoopNestingDepth(5), 1); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_TRUE(analysis.IsContinueBlock(5)); @@ -385,7 +421,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 2); EXPECT_EQ(analysis.ContainingLoop(6), 2); EXPECT_EQ(analysis.MergeBlock(6), 4); + EXPECT_EQ(analysis.NestingDepth(6), 2); EXPECT_EQ(analysis.LoopMergeBlock(6), 4); + EXPECT_EQ(analysis.LoopNestingDepth(6), 1); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -433,7 +471,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -445,7 +485,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -457,7 +499,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -469,7 +513,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 0); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 0); + EXPECT_EQ(analysis.LoopNestingDepth(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -481,7 +527,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 0); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 0); + EXPECT_EQ(analysis.LoopNestingDepth(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_FALSE(analysis.IsContinueBlock(5)); @@ -533,7 +581,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -545,7 +595,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 1); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.LoopNestingDepth(2), 1); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -557,7 +609,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -569,7 +623,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 1); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.LoopNestingDepth(4), 1); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -581,7 +637,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 2); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 4); + EXPECT_EQ(analysis.LoopNestingDepth(5), 2); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_TRUE(analysis.IsContinueBlock(5)); @@ -593,7 +651,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 2); EXPECT_EQ(analysis.ContainingLoop(6), 2); EXPECT_EQ(analysis.MergeBlock(6), 4); + EXPECT_EQ(analysis.NestingDepth(6), 2); EXPECT_EQ(analysis.LoopMergeBlock(6), 4); + EXPECT_EQ(analysis.LoopNestingDepth(6), 2); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -605,7 +665,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(7), 1); EXPECT_EQ(analysis.ContainingLoop(7), 1); EXPECT_EQ(analysis.MergeBlock(7), 3); + EXPECT_EQ(analysis.NestingDepth(7), 1); EXPECT_EQ(analysis.LoopMergeBlock(7), 3); + EXPECT_EQ(analysis.LoopNestingDepth(7), 1); EXPECT_EQ(analysis.ContainingSwitch(7), 0); EXPECT_EQ(analysis.SwitchMergeBlock(7), 0); EXPECT_TRUE(analysis.IsContinueBlock(7)); @@ -645,7 +707,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(i), 0); EXPECT_EQ(analysis.ContainingLoop(i), 0); EXPECT_EQ(analysis.MergeBlock(i), 0); + EXPECT_EQ(analysis.NestingDepth(i), 0); EXPECT_EQ(analysis.LoopMergeBlock(i), 0); + EXPECT_EQ(analysis.LoopNestingDepth(i), 0); EXPECT_EQ(analysis.ContainingSwitch(i), 0); EXPECT_EQ(analysis.SwitchMergeBlock(i), 0); EXPECT_FALSE(analysis.IsContinueBlock(i)); @@ -707,7 +771,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -719,7 +785,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -731,7 +799,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -781,7 +851,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -793,7 +865,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -805,7 +879,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -817,7 +893,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 0); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 0); + EXPECT_EQ(analysis.LoopNestingDepth(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 1); EXPECT_EQ(analysis.SwitchMergeBlock(4), 3); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -829,7 +907,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 2); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 4); + EXPECT_EQ(analysis.LoopNestingDepth(5), 1); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_TRUE(analysis.IsContinueBlock(5)); @@ -841,7 +921,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 2); EXPECT_EQ(analysis.ContainingLoop(6), 2); EXPECT_EQ(analysis.MergeBlock(6), 4); + EXPECT_EQ(analysis.NestingDepth(6), 2); EXPECT_EQ(analysis.LoopMergeBlock(6), 4); + EXPECT_EQ(analysis.LoopNestingDepth(6), 1); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -889,7 +971,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -901,7 +985,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 1); EXPECT_EQ(analysis.SwitchMergeBlock(2), 3); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -913,7 +999,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -925,7 +1013,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 0); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 0); + EXPECT_EQ(analysis.LoopNestingDepth(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 1); EXPECT_EQ(analysis.SwitchMergeBlock(4), 3); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -937,7 +1027,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 0); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 0); + EXPECT_EQ(analysis.LoopNestingDepth(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 1); EXPECT_EQ(analysis.SwitchMergeBlock(5), 3); EXPECT_FALSE(analysis.IsContinueBlock(5)); @@ -985,7 +1077,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -997,7 +1091,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 0); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 0); + EXPECT_EQ(analysis.LoopNestingDepth(2), 0); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -1009,7 +1105,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -1021,7 +1119,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 0); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 0); + EXPECT_EQ(analysis.LoopNestingDepth(4), 0); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -1033,7 +1133,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 2); EXPECT_EQ(analysis.ContainingLoop(5), 0); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 0); + EXPECT_EQ(analysis.LoopNestingDepth(5), 0); EXPECT_EQ(analysis.ContainingSwitch(5), 2); EXPECT_EQ(analysis.SwitchMergeBlock(5), 4); EXPECT_FALSE(analysis.IsContinueBlock(5)); @@ -1083,7 +1185,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -1095,7 +1199,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 1); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.LoopNestingDepth(2), 1); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -1107,7 +1213,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -1119,7 +1227,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 1); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.LoopNestingDepth(4), 1); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_TRUE(analysis.IsContinueBlock(4)); @@ -1131,7 +1241,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 4); EXPECT_EQ(analysis.ContainingLoop(5), 1); EXPECT_EQ(analysis.MergeBlock(5), 6); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 3); + EXPECT_EQ(analysis.LoopNestingDepth(5), 1); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_FALSE(analysis.IsContinueBlock(5)); @@ -1143,7 +1255,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 1); EXPECT_EQ(analysis.ContainingLoop(6), 1); EXPECT_EQ(analysis.MergeBlock(6), 3); + EXPECT_EQ(analysis.NestingDepth(6), 1); EXPECT_EQ(analysis.LoopMergeBlock(6), 3); + EXPECT_EQ(analysis.LoopNestingDepth(6), 1); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -1195,7 +1309,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(1), 0); EXPECT_EQ(analysis.ContainingLoop(1), 0); EXPECT_EQ(analysis.MergeBlock(1), 0); + EXPECT_EQ(analysis.NestingDepth(1), 0); EXPECT_EQ(analysis.LoopMergeBlock(1), 0); + EXPECT_EQ(analysis.LoopNestingDepth(1), 0); EXPECT_EQ(analysis.ContainingSwitch(1), 0); EXPECT_EQ(analysis.SwitchMergeBlock(1), 0); EXPECT_FALSE(analysis.IsContinueBlock(1)); @@ -1207,7 +1323,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(2), 1); EXPECT_EQ(analysis.ContainingLoop(2), 1); EXPECT_EQ(analysis.MergeBlock(2), 3); + EXPECT_EQ(analysis.NestingDepth(2), 1); EXPECT_EQ(analysis.LoopMergeBlock(2), 3); + EXPECT_EQ(analysis.LoopNestingDepth(2), 1); EXPECT_EQ(analysis.ContainingSwitch(2), 0); EXPECT_EQ(analysis.SwitchMergeBlock(2), 0); EXPECT_FALSE(analysis.IsContinueBlock(2)); @@ -1219,7 +1337,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(3), 0); EXPECT_EQ(analysis.ContainingLoop(3), 0); EXPECT_EQ(analysis.MergeBlock(3), 0); + EXPECT_EQ(analysis.NestingDepth(3), 0); EXPECT_EQ(analysis.LoopMergeBlock(3), 0); + EXPECT_EQ(analysis.LoopNestingDepth(3), 0); EXPECT_EQ(analysis.ContainingSwitch(3), 0); EXPECT_EQ(analysis.SwitchMergeBlock(3), 0); EXPECT_FALSE(analysis.IsContinueBlock(3)); @@ -1231,7 +1351,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(4), 1); EXPECT_EQ(analysis.ContainingLoop(4), 1); EXPECT_EQ(analysis.MergeBlock(4), 3); + EXPECT_EQ(analysis.NestingDepth(4), 1); EXPECT_EQ(analysis.LoopMergeBlock(4), 3); + EXPECT_EQ(analysis.LoopNestingDepth(4), 1); EXPECT_EQ(analysis.ContainingSwitch(4), 0); EXPECT_EQ(analysis.SwitchMergeBlock(4), 0); EXPECT_FALSE(analysis.IsContinueBlock(4)); @@ -1243,7 +1365,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(5), 7); EXPECT_EQ(analysis.ContainingLoop(5), 7); EXPECT_EQ(analysis.MergeBlock(5), 4); + EXPECT_EQ(analysis.NestingDepth(5), 2); EXPECT_EQ(analysis.LoopMergeBlock(5), 4); + EXPECT_EQ(analysis.LoopNestingDepth(5), 2); EXPECT_EQ(analysis.ContainingSwitch(5), 0); EXPECT_EQ(analysis.SwitchMergeBlock(5), 0); EXPECT_TRUE(analysis.IsContinueBlock(5)); @@ -1255,7 +1379,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(6), 7); EXPECT_EQ(analysis.ContainingLoop(6), 7); EXPECT_EQ(analysis.MergeBlock(6), 4); + EXPECT_EQ(analysis.NestingDepth(6), 2); EXPECT_EQ(analysis.LoopMergeBlock(6), 4); + EXPECT_EQ(analysis.LoopNestingDepth(6), 2); EXPECT_EQ(analysis.ContainingSwitch(6), 0); EXPECT_EQ(analysis.SwitchMergeBlock(6), 0); EXPECT_FALSE(analysis.IsContinueBlock(6)); @@ -1267,7 +1393,9 @@ OpFunctionEnd EXPECT_EQ(analysis.ContainingConstruct(7), 1); EXPECT_EQ(analysis.ContainingLoop(7), 1); EXPECT_EQ(analysis.MergeBlock(7), 3); + EXPECT_EQ(analysis.NestingDepth(7), 1); EXPECT_EQ(analysis.LoopMergeBlock(7), 3); + EXPECT_EQ(analysis.LoopNestingDepth(7), 1); EXPECT_EQ(analysis.ContainingSwitch(7), 0); EXPECT_EQ(analysis.SwitchMergeBlock(7), 0); EXPECT_TRUE(analysis.IsContinueBlock(7)); diff --git a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp index 69ef1f48..0e461140 100644 --- a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp +++ b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp @@ -80,7 +80,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); @@ -125,7 +125,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { } ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); // Start again, and apply the other op. @@ -134,7 +134,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) { CheckValid(kEnv, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -225,7 +225,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, AlreadySimplified) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -276,7 +276,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -313,7 +313,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) { CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -361,7 +361,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -396,7 +396,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -453,7 +453,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) { CheckValid(kEnv, context.get()); auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -492,7 +492,7 @@ TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) { CheckEqual(kEnv, after, context.get()); ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp index dfb614ef..8506ee08 100644 --- a/test/reduce/merge_blocks_test.cpp +++ b/test/reduce/merge_blocks_test.cpp @@ -66,7 +66,7 @@ TEST(MergeBlocksReductionPassTest, BasicCheck) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(5, ops.size()); // Try order 3, 0, 2, 4, 1 @@ -356,7 +356,7 @@ TEST(MergeBlocksReductionPassTest, Loops) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(11, ops.size()); for (auto& ri : ops) { @@ -474,7 +474,7 @@ TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -556,7 +556,7 @@ void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) { ASSERT_NE(context.get(), nullptr); auto opportunities = MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); // A->B and B->C ASSERT_EQ(opportunities.size(), 2); diff --git a/test/reduce/operand_to_constant_test.cpp b/test/reduce/operand_to_constant_test.cpp index b2f67ee1..f44de51c 100644 --- a/test/reduce/operand_to_constant_test.cpp +++ b/test/reduce/operand_to_constant_test.cpp @@ -101,7 +101,7 @@ TEST(OperandToConstantReductionPassTest, BasicCheck) { BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(17, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -151,10 +151,158 @@ TEST(OperandToConstantReductionPassTest, WithCalledFunction) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } +TEST(OperandToConstantReductionPassTest, TargetSpecificFunction) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %17 = OpConstant %6 1 + %20 = OpConstant %6 2 + %23 = OpConstant %6 0 + %24 = OpTypeBool + %35 = OpConstant %6 3 + %53 = OpConstant %6 10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %65 = OpVariable %7 Function + %68 = OpVariable %7 Function + %73 = OpVariable %7 Function + OpStore %65 %35 + %66 = OpLoad %6 %65 + %67 = OpIAdd %6 %66 %17 + OpStore %65 %67 + %69 = OpLoad %6 %65 + OpStore %68 %69 + %70 = OpFunctionCall %6 %13 %68 + %71 = OpLoad %6 %65 + %72 = OpIAdd %6 %71 %70 + OpStore %65 %72 + %74 = OpLoad %6 %65 + OpStore %73 %74 + %75 = OpFunctionCall %6 %10 %73 + %76 = OpLoad %6 %65 + %77 = OpIAdd %6 %76 %75 + OpStore %65 %77 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %15 = OpVariable %7 Function + %16 = OpLoad %6 %9 + %18 = OpIAdd %6 %16 %17 + OpStore %15 %18 + %19 = OpLoad %6 %15 + %21 = OpIAdd %6 %19 %20 + OpStore %15 %21 + %22 = OpLoad %6 %15 + %25 = OpSGreaterThan %24 %22 %23 + OpSelectionMerge %27 None + OpBranchConditional %25 %26 %27 + %26 = OpLabel + %28 = OpLoad %6 %9 + OpReturnValue %28 + %27 = OpLabel + %30 = OpLoad %6 %9 + %31 = OpIAdd %6 %30 %17 + OpReturnValue %31 + OpFunctionEnd + %13 = OpFunction %6 None %8 + %12 = OpFunctionParameter %7 + %14 = OpLabel + %41 = OpVariable %7 Function + %46 = OpVariable %7 Function + %55 = OpVariable %7 Function + %34 = OpLoad %6 %12 + %36 = OpIEqual %24 %34 %35 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + %39 = OpLoad %6 %12 + %40 = OpIMul %6 %20 %39 + OpStore %41 %40 + %42 = OpFunctionCall %6 %10 %41 + OpReturnValue %42 + %38 = OpLabel + %44 = OpLoad %6 %12 + %45 = OpIAdd %6 %44 %17 + OpStore %12 %45 + OpStore %46 %23 + OpBranch %47 + %47 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %54 = OpSLessThan %24 %52 %53 + OpBranchConditional %54 %48 %49 + %48 = OpLabel + %56 = OpLoad %6 %12 + OpStore %55 %56 + %57 = OpFunctionCall %6 %10 %55 + %58 = OpLoad %6 %12 + %59 = OpIAdd %6 %58 %57 + OpStore %12 %59 + OpBranch %50 + %50 = OpLabel + %60 = OpLoad %6 %46 + %61 = OpIAdd %6 %60 %17 + OpStore %46 %61 + OpBranch %47 + %49 = OpLabel + %62 = OpLoad %6 %12 + OpReturnValue %62 + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = + BuildModule(env, consumer, shader, kReduceAssembleOption); + + // Targeting all functions, there are quite a few opportunities. To avoid + // making the test too sensitive, we check that there are more than a number + // somewhat lower than the real number. + const auto all_ops = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 0); + ASSERT_TRUE(all_ops.size() > 100); + + // Targeting individual functions, there are fewer opportunities. Again, we + // avoid checking against an exact number so that the test is not too + // sensitive. + const auto ops_for_function_4 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 4); + const auto ops_for_function_10 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 10); + const auto ops_for_function_13 = + OperandToConstReductionOpportunityFinder().GetAvailableOpportunities( + context.get(), 13); + ASSERT_TRUE(ops_for_function_4.size() < 60); + ASSERT_TRUE(ops_for_function_10.size() < 50); + ASSERT_TRUE(ops_for_function_13.size() < 80); + + // The total number of opportunities should be the sum of the per-function + // opportunities. + ASSERT_EQ(all_ops.size(), ops_for_function_4.size() + + ops_for_function_10.size() + + ops_for_function_13.size()); +} + } // namespace } // namespace reduce } // namespace spvtools diff --git a/test/reduce/operand_to_dominating_id_test.cpp b/test/reduce/operand_to_dominating_id_test.cpp index cd5b2c68..697c5cb8 100644 --- a/test/reduce/operand_to_dominating_id_test.cpp +++ b/test/reduce/operand_to_dominating_id_test.cpp @@ -56,7 +56,7 @@ TEST(OperandToDominatingIdReductionPassTest, BasicCheck) { const auto context = BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToDominatingIdReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(10, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); diff --git a/test/reduce/operand_to_undef_test.cpp b/test/reduce/operand_to_undef_test.cpp index fa64bd53..41974275 100644 --- a/test/reduce/operand_to_undef_test.cpp +++ b/test/reduce/operand_to_undef_test.cpp @@ -167,7 +167,7 @@ TEST(OperandToUndefReductionPassTest, BasicCheck) { BuildModule(env, consumer, original, kReduceAssembleOption); const auto ops = OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(10, ops.size()); @@ -221,7 +221,7 @@ TEST(OperandToUndefReductionPassTest, WithCalledFunction) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp index 0de5af1d..276aedc8 100644 --- a/test/reduce/reducer_test.cpp +++ b/test/reduce/reducer_test.cpp @@ -14,6 +14,8 @@ #include "source/reduce/reducer.h" +#include <unordered_map> + #include "source/opt/build_module.h" #include "source/reduce/operand_to_const_reduction_opportunity_finder.h" #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h" @@ -23,11 +25,8 @@ namespace spvtools { namespace reduce { namespace { -using opt::BasicBlock; -using opt::IRContext; - const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3; -const MessageConsumer kMessageConsumer = CLIMessageConsumer; +const MessageConsumer kMessageConsumer = NopDiagnostic; // This changes its mind each time IsInteresting is invoked as to whether the // binary is interesting, until some limit is reached after which the binary is @@ -42,7 +41,7 @@ class PingPongInteresting { always_interesting_after_(always_interesting_after), count_(0) {} - bool IsInteresting(const std::vector<uint32_t>&) { + bool IsInteresting() { bool result; if (count_ > always_interesting_after_) { result = true; @@ -194,10 +193,10 @@ TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) { Reducer reducer(kEnv); PingPongInteresting ping_pong_interesting(10); - reducer.SetMessageConsumer(NopDiagnostic); + reducer.SetMessageConsumer(kMessageConsumer); reducer.SetInterestingnessFunction( - [&](const std::vector<uint32_t>& binary, uint32_t) -> bool { - return ping_pong_interesting.IsInteresting(binary); + [&ping_pong_interesting](const std::vector<uint32_t>&, uint32_t) -> bool { + return ping_pong_interesting.IsInteresting(); }); reducer.AddReductionPass( MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(false)); @@ -230,13 +229,14 @@ bool InterestingWhileOpcodeExists(const std::vector<uint32_t>& binary, DumpShader(binary, ss.str().c_str()); } - std::unique_ptr<IRContext> context = + std::unique_ptr<opt::IRContext> context = BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size()); assert(context); bool interesting = false; for (auto& function : *context->module()) { context->cfg()->ForEachBlockInPostOrder( - &*function.begin(), [opcode, &interesting](BasicBlock* block) -> void { + &*function.begin(), + [opcode, &interesting](opt::BasicBlock* block) -> void { for (auto& inst : *block) { if (inst.opcode() == opcode) { interesting = true; @@ -369,6 +369,147 @@ const std::string kShaderWithLoopsDivAndMul = R"( OpFunctionEnd )"; +// The shader below comes from the following GLSL. +// #version 320 es +// +// int baz(int x) { +// int y = x + 1; +// y = y + 2; +// if (y > 0) { +// return x; +// } +// return x + 1; +// } +// +// int bar(int a) { +// if (a == 3) { +// return baz(2*a); +// } +// a = a + 1; +// for (int i = 0; i < 10; i++) { +// a += baz(a); +// } +// return a; +// } +// +// void main() { +// int x; +// x = 3; +// x += 1; +// x += bar(x); +// x += baz(x); +// } +const std::string kShaderWithMultipleFunctions = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %17 = OpConstant %6 1 + %20 = OpConstant %6 2 + %23 = OpConstant %6 0 + %24 = OpTypeBool + %35 = OpConstant %6 3 + %53 = OpConstant %6 10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %65 = OpVariable %7 Function + %68 = OpVariable %7 Function + %73 = OpVariable %7 Function + OpStore %65 %35 + %66 = OpLoad %6 %65 + %67 = OpIAdd %6 %66 %17 + OpStore %65 %67 + %69 = OpLoad %6 %65 + OpStore %68 %69 + %70 = OpFunctionCall %6 %13 %68 + %71 = OpLoad %6 %65 + %72 = OpIAdd %6 %71 %70 + OpStore %65 %72 + %74 = OpLoad %6 %65 + OpStore %73 %74 + %75 = OpFunctionCall %6 %10 %73 + %76 = OpLoad %6 %65 + %77 = OpIAdd %6 %76 %75 + OpStore %65 %77 + OpReturn + OpFunctionEnd + %10 = OpFunction %6 None %8 + %9 = OpFunctionParameter %7 + %11 = OpLabel + %15 = OpVariable %7 Function + %16 = OpLoad %6 %9 + %18 = OpIAdd %6 %16 %17 + OpStore %15 %18 + %19 = OpLoad %6 %15 + %21 = OpIAdd %6 %19 %20 + OpStore %15 %21 + %22 = OpLoad %6 %15 + %25 = OpSGreaterThan %24 %22 %23 + OpSelectionMerge %27 None + OpBranchConditional %25 %26 %27 + %26 = OpLabel + %28 = OpLoad %6 %9 + OpReturnValue %28 + %27 = OpLabel + %30 = OpLoad %6 %9 + %31 = OpIAdd %6 %30 %17 + OpReturnValue %31 + OpFunctionEnd + %13 = OpFunction %6 None %8 + %12 = OpFunctionParameter %7 + %14 = OpLabel + %41 = OpVariable %7 Function + %46 = OpVariable %7 Function + %55 = OpVariable %7 Function + %34 = OpLoad %6 %12 + %36 = OpIEqual %24 %34 %35 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + %39 = OpLoad %6 %12 + %40 = OpIMul %6 %20 %39 + OpStore %41 %40 + %42 = OpFunctionCall %6 %10 %41 + OpReturnValue %42 + %38 = OpLabel + %44 = OpLoad %6 %12 + %45 = OpIAdd %6 %44 %17 + OpStore %12 %45 + OpStore %46 %23 + OpBranch %47 + %47 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %54 = OpSLessThan %24 %52 %53 + OpBranchConditional %54 %48 %49 + %48 = OpLabel + %56 = OpLoad %6 %12 + OpStore %55 %56 + %57 = OpFunctionCall %6 %10 %55 + %58 = OpLoad %6 %12 + %59 = OpIAdd %6 %58 %57 + OpStore %12 %59 + OpBranch %50 + %50 = OpLabel + %60 = OpLoad %6 %46 + %61 = OpIAdd %6 %60 %17 + OpStore %46 %61 + OpBranch %47 + %49 = OpLabel + %62 = OpLoad %6 %12 + OpReturnValue %62 + OpFunctionEnd + )"; + TEST(ReducerTest, ShaderReduceWhileMulReachable) { Reducer reducer(kEnv); @@ -417,6 +558,70 @@ TEST(ReducerTest, ShaderReduceWhileDivReachable) { ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); } +// Computes an instruction count for each function in the module represented by +// |binary|. +std::unordered_map<uint32_t, uint32_t> GetFunctionInstructionCount( + const std::vector<uint32_t>& binary) { + std::unique_ptr<opt::IRContext> context = + BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size()); + assert(context != nullptr && "Failed to build module."); + std::unordered_map<uint32_t, uint32_t> result; + for (auto& function : *context->module()) { + uint32_t& count = result[function.result_id()] = 0; + function.ForEachInst([&count](opt::Instruction*) { count++; }); + } + return result; +} + +TEST(ReducerTest, SingleFunctionReduction) { + Reducer reducer(kEnv); + + PingPongInteresting ping_pong_interesting(4); + reducer.SetInterestingnessFunction( + [&ping_pong_interesting](const std::vector<uint32_t>&, uint32_t) -> bool { + return ping_pong_interesting.IsInteresting(); + }); + reducer.AddDefaultReductionPasses(); + reducer.SetMessageConsumer(kMessageConsumer); + + std::vector<uint32_t> binary_in; + SpirvTools t(kEnv); + + ASSERT_TRUE(t.Assemble(kShaderWithMultipleFunctions, &binary_in, + kReduceAssembleOption)); + + auto original_instruction_count = GetFunctionInstructionCount(binary_in); + + std::vector<uint32_t> binary_out; + spvtools::ReducerOptions reducer_options; + reducer_options.set_step_limit(500); + reducer_options.set_fail_on_validation_error(true); + + // Instruct the reducer to only target function 13. + reducer_options.set_target_function(13); + + spvtools::ValidatorOptions validator_options; + + Reducer::ReductionResultStatus status = reducer.Run( + std::move(binary_in), &binary_out, reducer_options, validator_options); + + ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete); + + auto final_instruction_count = GetFunctionInstructionCount(binary_out); + + // Nothing should have been removed from these functions. + ASSERT_EQ(original_instruction_count.at(4), final_instruction_count.at(4)); + ASSERT_EQ(original_instruction_count.at(10), final_instruction_count.at(10)); + + // Function 13 should have been reduced to these five instructions: + // OpFunction + // OpFunctionParameter + // OpLabel + // OpReturnValue + // OpFunctionEnd + ASSERT_EQ(5, final_instruction_count.at(13)); +} + } // namespace } // namespace reduce } // namespace spvtools diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp index f31cc9da..2500d0c9 100644 --- a/test/reduce/remove_block_test.cpp +++ b/test/reduce/remove_block_test.cpp @@ -66,7 +66,7 @@ TEST(RemoveBlockReductionPassTest, BasicCheck) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -181,7 +181,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -209,7 +209,7 @@ TEST(RemoveBlockReductionPassTest, OneBlock) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -247,7 +247,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) { BuildModule(env, consumer, shader, kReduceAssembleOption); const auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -287,7 +287,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { const auto context = BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -323,7 +323,7 @@ TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) { // removed. ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); diff --git a/test/reduce/remove_function_test.cpp b/test/reduce/remove_function_test.cpp index 576b6031..e293f4ec 100644 --- a/test/reduce/remove_function_test.cpp +++ b/test/reduce/remove_function_test.cpp @@ -67,7 +67,7 @@ TEST(RemoveFunctionTest, BasicCheck) { auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -97,7 +97,7 @@ TEST(RemoveFunctionTest, BasicCheck) { CheckEqual(env, after_first, context.get()); ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -156,7 +156,7 @@ TEST(RemoveFunctionTest, NothingToRemove) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -193,7 +193,7 @@ TEST(RemoveFunctionTest, TwoRemovableFunctions) { auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -254,7 +254,7 @@ TEST(RemoveFunctionTest, NoRemovalsDueToOpName) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -286,7 +286,7 @@ TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/remove_selection_test.cpp b/test/reduce/remove_selection_test.cpp index f8acd5d4..2921bbeb 100644 --- a/test/reduce/remove_selection_test.cpp +++ b/test/reduce/remove_selection_test.cpp @@ -62,7 +62,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -96,7 +96,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -136,7 +136,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -168,7 +168,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -212,7 +212,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocksOneMerge) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -258,7 +258,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocks) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -306,7 +306,7 @@ TEST(RemoveSelectionTest, NoOpportunityBecauseMergeUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -384,7 +384,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -427,7 +427,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -505,7 +505,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) { auto ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -548,7 +548,7 @@ TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) { CheckEqual(env, after, context.get()); ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities( - context.get()); + context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/remove_unused_instruction_test.cpp b/test/reduce/remove_unused_instruction_test.cpp index 68bc6014..eb548e11 100644 --- a/test/reduce/remove_unused_instruction_test.cpp +++ b/test/reduce/remove_unused_instruction_test.cpp @@ -72,7 +72,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckValid(kEnv, context.get()); - auto ops = finder.GetAvailableOpportunities(context.get()); + auto ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(10, ops.size()); @@ -108,7 +108,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_2, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(7, ops.size()); @@ -137,7 +137,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_3, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -165,7 +165,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_4, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -192,7 +192,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_5, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -218,7 +218,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) { CheckEqual(kEnv, step_6, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -258,7 +258,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckValid(kEnv, context.get()); - auto ops = finder.GetAvailableOpportunities(context.get()); + auto ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); @@ -289,7 +289,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(3, ops.size()); @@ -317,7 +317,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_2, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -344,7 +344,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_3, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -370,7 +370,7 @@ TEST(RemoveUnusedInstructionReductionPassTest, Referenced) { CheckEqual(kEnv, after_4, context.get()); - ops = finder.GetAvailableOpportunities(context.get()); + ops = finder.GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -438,7 +438,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(7, ops.size()); for (auto& op : ops) { @@ -487,7 +487,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { CheckEqual(env, expected_1, context.get()); ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); for (auto& op : ops) { @@ -530,7 +530,7 @@ TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) { CheckEqual(env, expected_2, context.get()); ops = RemoveUnusedInstructionReductionOpportunityFinder(true) - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(6, ops.size()); for (auto& op : ops) { diff --git a/test/reduce/remove_unused_struct_member_test.cpp b/test/reduce/remove_unused_struct_member_test.cpp index 402ef2d8..d3c1487d 100644 --- a/test/reduce/remove_unused_struct_member_test.cpp +++ b/test/reduce/remove_unused_struct_member_test.cpp @@ -61,7 +61,7 @@ TEST(RemoveUnusedStructMemberTest, RemoveOneMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -143,7 +143,7 @@ TEST(RemoveUnusedStructMemberTest, RemoveUniformBufferMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); ops[0]->TryToApply(); @@ -229,7 +229,7 @@ TEST(RemoveUnusedStructMemberTest, DoNotRemoveNamedMemberRemoveOneMember) { BuildModule(env, consumer, shader, kReduceAssembleOption); auto ops = RemoveUnusedStructMemberReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/simple_conditional_branch_to_branch_test.cpp b/test/reduce/simple_conditional_branch_to_branch_test.cpp index d55e6910..fcc9d721 100644 --- a/test/reduce/simple_conditional_branch_to_branch_test.cpp +++ b/test/reduce/simple_conditional_branch_to_branch_test.cpp @@ -73,7 +73,7 @@ TEST(SimpleConditionalBranchToBranchTest, Diamond) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -122,7 +122,7 @@ TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -161,7 +161,7 @@ TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -217,7 +217,7 @@ TEST(SimpleConditionalBranchToBranchTest, ConditionalBranchesButNotSimple) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -266,7 +266,7 @@ TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -303,7 +303,7 @@ TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -349,7 +349,7 @@ TEST(SimpleConditionalBranchToBranchTest, CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -384,7 +384,7 @@ TEST(SimpleConditionalBranchToBranchTest, CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -438,7 +438,7 @@ TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) { CheckValid(kEnv, context.get()); auto ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); @@ -477,7 +477,7 @@ TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) { CheckEqual(kEnv, after, context.get()); ops = SimpleConditionalBranchToBranchOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp index 95b5f4f1..0cfcfdff 100644 --- a/test/reduce/structured_loop_to_selection_test.cpp +++ b/test/reduce/structured_loop_to_selection_test.cpp @@ -65,7 +65,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader1) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -211,7 +211,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader2) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(4, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -680,7 +680,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader3) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } @@ -758,7 +758,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader4) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // Initially there are two opportunities. ASSERT_EQ(2, ops.size()); @@ -881,7 +881,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak1) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -956,7 +956,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak2) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1024,7 +1024,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, UnconditionalBreak) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1224,7 +1224,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, Complex) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -1691,7 +1691,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ComplexOptimized) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2008,7 +2008,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, DominanceIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2156,7 +2156,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, AccessChainIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2313,7 +2313,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, DominanceAndPhiIssue) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2421,7 +2421,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, OpLineBeforeOpPhi) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2511,7 +2511,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // There should be no opportunities. ASSERT_EQ(0, ops.size()); @@ -2555,7 +2555,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // There should be no opportunities. ASSERT_EQ(0, ops.size()); @@ -2595,7 +2595,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, ContinueTargetIsSwitchTarget) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2670,7 +2670,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2733,7 +2733,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopBranchesStraightToMerge) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2790,7 +2790,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2874,7 +2874,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, MultipleAccessChains) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -2970,7 +2970,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3089,7 +3089,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(2, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3209,7 +3209,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // We cannot transform the inner loop due to its header jumping straight to // the outer loop merge (the inner loop's merge does not post-dominate its @@ -3254,7 +3254,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, // Now look again for more opportunities. ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); // What was the inner loop should now be transformable, as the jump to the // outer loop's merge has been redirected. @@ -3422,7 +3422,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LongAccessChains) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3513,7 +3513,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, LoopyShaderWithOpDecorate) { const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(1, ops.size()); ASSERT_TRUE(ops[0]->PreconditionHolds()); @@ -3619,7 +3619,7 @@ TEST(StructuredLoopToSelectionReductionPassTest, const auto env = SPV_ENV_UNIVERSAL_1_3; const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); const auto ops = StructuredLoopToSelectionReductionOpportunityFinder() - .GetAvailableOpportunities(context.get()); + .GetAvailableOpportunities(context.get(), 0); ASSERT_EQ(0, ops.size()); } diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp index 2981c2ed..d8643449 100644 --- a/test/reduce/validation_during_reduction_test.cpp +++ b/test/reduce/validation_during_reduction_test.cpp @@ -22,10 +22,10 @@ namespace reduce { namespace { using opt::Function; -using opt::Instruction; using opt::IRContext; +using opt::Instruction; -// A dumb reduction opportunity finder that finds opportunities to remove global +// A reduction opportunity finder that finds opportunities to remove global // values regardless of whether they are referenced. This is very likely to make // the resulting module invalid. We use this to test the reducer's behavior in // the scenario where a bad reduction pass leads to an invalid module. @@ -43,7 +43,7 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder // referenced (directly or indirectly) from elsewhere in the module, each such // opportunity will make the module invalid. std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - IRContext* context) const final { + IRContext* context, uint32_t /*unused*/) const final { std::vector<std::unique_ptr<ReductionOpportunity>> result; for (auto& inst : context->module()->types_values()) { if (inst.HasResultId()) { @@ -55,7 +55,7 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder } }; -// A dumb reduction opportunity that exists at the start of every function whose +// A reduction opportunity that exists at the start of every function whose // first instruction is an OpVariable instruction. When applied, the OpVariable // instruction is duplicated (with a fresh result id). This allows each // reduction step to increase the number of variables to check if the validator @@ -101,7 +101,7 @@ class OpVariableDuplicatorReductionOpportunityFinder } std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities( - IRContext* context) const final { + IRContext* context, uint32_t /*unused*/) const final { std::vector<std::unique_ptr<ReductionOpportunity>> result; for (auto& function : *context->module()) { Instruction* first_instruction = &*function.begin()[0].begin(); diff --git a/test/tools/spirv_test_framework.py b/test/tools/spirv_test_framework.py index 42f83c64..542f1446 100755 --- a/test/tools/spirv_test_framework.py +++ b/test/tools/spirv_test_framework.py @@ -146,9 +146,9 @@ class TestStatus: # Some of our MacOS bots still run Python 2, so need to be backwards # compatible here. if type(stdout) is not str: - if sys.version_info[0] is 2: + if sys.version_info[0] == 2: self.stdout = stdout.decode('utf-8') - elif sys.version_info[0] is 3: + elif sys.version_info[0] == 3: self.stdout = str(stdout, encoding='utf-8') if stdout is not None else stdout else: raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info)) @@ -156,9 +156,9 @@ class TestStatus: self.stdout = stdout if type(stderr) is not str: - if sys.version_info[0] is 2: + if sys.version_info[0] == 2: self.stderr = stderr.decode('utf-8') - elif sys.version_info[0] is 3: + elif sys.version_info[0] == 3: self.stderr = str(stderr, encoding='utf-8') if stderr is not None else stderr else: raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info)) diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt index 23d7a19e..c458a2f9 100644 --- a/test/val/CMakeLists.txt +++ b/test/val/CMakeLists.txt @@ -41,21 +41,21 @@ add_spvtools_unittest(TARGET val_abcde val_extension_spv_khr_terminate_invocation.cpp val_ext_inst_test.cpp ${VAL_TEST_COMMON_SRCS} - LIBS ${SPIRV_TOOLS} + LIBS ${SPIRV_TOOLS}-static PCH_FILE pch_test_val ) add_spvtools_unittest(TARGET val_capability SRCS val_capability_test.cpp - LIBS ${SPIRV_TOOLS} + LIBS ${SPIRV_TOOLS}-static PCH_FILE pch_test_val ) add_spvtools_unittest(TARGET val_limits SRCS val_limits_test.cpp ${VAL_TEST_COMMON_SRCS} - LIBS ${SPIRV_TOOLS} + LIBS ${SPIRV_TOOLS}-static PCH_FILE pch_test_val ) @@ -76,7 +76,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop val_opencl_test.cpp val_primitives_test.cpp ${VAL_TEST_COMMON_SRCS} - LIBS ${SPIRV_TOOLS} + LIBS ${SPIRV_TOOLS}-static PCH_FILE pch_test_val ) @@ -91,6 +91,6 @@ add_spvtools_unittest(TARGET val_stuvw val_version_test.cpp val_webgpu_test.cpp ${VAL_TEST_COMMON_SRCS} - LIBS ${SPIRV_TOOLS} + LIBS ${SPIRV_TOOLS}-static PCH_FILE pch_test_val ) diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp index 0b09de0c..29598535 100644 --- a/test/val/val_adjacency_test.cpp +++ b/test/val/val_adjacency_test.cpp @@ -324,7 +324,7 @@ OpBranch %end_label %false_label = OpLabel OpBranch %end_label %end_label = OpLabel -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 %result = OpPhi %bool %true %true_label %false %false_label )"; @@ -350,7 +350,7 @@ OpBranch %end_label OpBranch %end_label %end_label = OpLabel %result1 = OpPhi %bool %true %true_label %false %false_label -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 %result2 = OpPhi %bool %true %true_label %false %false_label )"; @@ -377,7 +377,7 @@ OpBranch %end_label %end_label = OpLabel OpLine %string 0 0 %result = OpPhi %bool %true %true_label %false %false_label -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 )"; const std::string extra = R"(OpCapability Shader @@ -411,7 +411,7 @@ OpExecutionMode %main OriginUpperLeft %paramfunc_type = OpTypeFunction %void %int %int %paramfunc = OpFunction %void None %paramfunc_type -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 %a = OpFunctionParameter %int %b = OpFunctionParameter %int %paramfunc_entry = OpLabel @@ -454,7 +454,7 @@ OpExecutionMode %main OriginUpperLeft %paramfunc = OpFunction %void None %paramfunc_type %a = OpFunctionParameter %int -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 %b = OpFunctionParameter %int %paramfunc_entry = OpLabel OpReturn @@ -498,7 +498,7 @@ OpExecutionMode %main OriginUpperLeft %a = OpFunctionParameter %int %b = OpFunctionParameter %int %paramfunc_entry = OpLabel -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 OpReturn OpFunctionEnd @@ -540,7 +540,7 @@ OpExecutionMode %main OriginUpperLeft OpReturn OpFunctionEnd -%dummy = OpExtInst %void %extinst 123 %int_1 +%placeholder = OpExtInst %void %extinst 123 %int_1 %main = OpFunction %void None %func %main_entry = OpLabel diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp index b1ab0c9e..cc9bda67 100644 --- a/test/val/val_builtins_test.cpp +++ b/test/val/val_builtins_test.cpp @@ -56,7 +56,7 @@ using ValidateVulkanSubgroupBuiltIns = spvtest::ValidateBase< std::tuple<const char*, const char*, const char*, const char*, TestResult>>; using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult = spvtest::ValidateBase<std::tuple<const char*, const char*, const char*, - const char*, TestResult>>; + const char*, const char*, TestResult>>; using ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult = spvtest::ValidateBase<std::tuple<const char*, const char*, const char*, const char*, TestResult>>; @@ -67,7 +67,7 @@ using ValidateWebGPUCombineBuiltInArrayedVariable = spvtest::ValidateBase< using ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult = spvtest::ValidateBase< std::tuple<const char*, const char*, const char*, const char*, - const char*, const char*, TestResult>>; + const char*, const char*, const char*, TestResult>>; bool InitializerRequired(spv_target_env env, const char* const storage_class) { return spvIsWebGPUEnv(env) && (strncmp(storage_class, "Output", 6) == 0 || @@ -151,12 +151,40 @@ CodeGenerator GetInMainCodeGenerator(spv_target_env env, return generator; } +// Allows test parameter test to list all possible VUIDs with a delimiter that +// is then split here to check if one VUID was in the error message +MATCHER_P(AnyVUID, vuid_set, "VUID from the set is in error message") { + // use space as delimiter because clang-format will properly line break VUID + // strings which is important the entire VUID is in a single line for script + // to scan + std::string delimiter = " "; + std::string token; + std::string vuids = std::string(vuid_set); + size_t position; + do { + position = vuids.find(delimiter); + if (position != std::string::npos) { + token = vuids.substr(0, position); + vuids.erase(0, position + delimiter.length()); + } else { + token = vuids.substr(0); // last item + } + + // arg contains diagnostic message + if (arg.find(token) != std::string::npos) { + return true; + } + } while (position != std::string::npos); + return false; +} + TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) { const char* const built_in = std::get<0>(GetParam()); const char* const execution_model = std::get<1>(GetParam()); const char* const storage_class = std::get<2>(GetParam()); const char* const data_type = std::get<3>(GetParam()); - const TestResult& test_result = std::get<4>(GetParam()); + const char* const vuid = std::get<4>(GetParam()); + const TestResult& test_result = std::get<5>(GetParam()); CodeGenerator generator = GetInMainCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model, @@ -171,6 +199,9 @@ TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) { if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InMain) { @@ -204,7 +235,8 @@ TEST_P( const char* const data_type = std::get<3>(GetParam()); const char* const capabilities = std::get<4>(GetParam()); const char* const extensions = std::get<5>(GetParam()); - const TestResult& test_result = std::get<6>(GetParam()); + const char* const vuid = std::get<6>(GetParam()); + const TestResult& test_result = std::get<7>(GetParam()); CodeGenerator generator = GetInMainCodeGenerator( SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, @@ -219,6 +251,9 @@ TEST_P( if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } CodeGenerator GetInFunctionCodeGenerator(spv_target_env env, @@ -316,7 +351,8 @@ TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) { const char* const execution_model = std::get<1>(GetParam()); const char* const storage_class = std::get<2>(GetParam()); const char* const data_type = std::get<3>(GetParam()); - const TestResult& test_result = std::get<4>(GetParam()); + const char* const vuid = std::get<4>(GetParam()); + const TestResult& test_result = std::get<5>(GetParam()); CodeGenerator generator = GetInFunctionCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model, @@ -331,6 +367,9 @@ TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) { if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InFunction) { @@ -364,7 +403,8 @@ TEST_P( const char* const data_type = std::get<3>(GetParam()); const char* const capabilities = std::get<4>(GetParam()); const char* const extensions = std::get<5>(GetParam()); - const TestResult& test_result = std::get<6>(GetParam()); + const char* const vuid = std::get<6>(GetParam()); + const TestResult& test_result = std::get<7>(GetParam()); CodeGenerator generator = GetInFunctionCodeGenerator( SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, @@ -379,6 +419,9 @@ TEST_P( if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } CodeGenerator GetVariableCodeGenerator(spv_target_env env, @@ -459,7 +502,8 @@ TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) { const char* const execution_model = std::get<1>(GetParam()); const char* const storage_class = std::get<2>(GetParam()); const char* const data_type = std::get<3>(GetParam()); - const TestResult& test_result = std::get<4>(GetParam()); + const char* const vuid = std::get<4>(GetParam()); + const TestResult& test_result = std::get<5>(GetParam()); CodeGenerator generator = GetVariableCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model, @@ -474,6 +518,9 @@ TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) { if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, Variable) { @@ -507,7 +554,8 @@ TEST_P( const char* const data_type = std::get<3>(GetParam()); const char* const capabilities = std::get<4>(GetParam()); const char* const extensions = std::get<5>(GetParam()); - const TestResult& test_result = std::get<6>(GetParam()); + const char* const vuid = std::get<6>(GetParam()); + const TestResult& test_result = std::get<7>(GetParam()); CodeGenerator generator = GetVariableCodeGenerator( SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, @@ -522,6 +570,9 @@ TEST_P( if (test_result.error_str2) { EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); } + if (vuid) { + EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid)); + } } INSTANTIATE_TEST_SUITE_P( @@ -530,7 +581,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("ClipDistance", "CullDistance"), Values("Vertex", "Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Output"), Values("%f32arr2", "%f32arr4"), + Values("Output"), Values("%f32arr2", "%f32arr4"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( @@ -539,14 +590,14 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("ClipDistance", "CullDistance"), Values("Fragment", "Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Input"), Values("%f32arr2", "%f32arr4"), + Values("Input"), Values("%f32arr2", "%f32arr4"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( ClipAndCullDistanceFragmentOutput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), - Values("Output"), Values("%f32arr4"), + Values("Output"), Values("%f32arr4"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance " @@ -558,7 +609,7 @@ INSTANTIATE_TEST_SUITE_P( VertexIdAndInstanceIdVertexInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("VertexId", "InstanceId"), Values("Vertex"), Values("Input"), - Values("%u32"), + Values("%u32"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow BuiltIn VertexId/InstanceId to be " @@ -568,7 +619,7 @@ INSTANTIATE_TEST_SUITE_P( ClipAndCullDistanceVertexInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("Vertex"), - Values("Input"), Values("%f32arr4"), + Values("Input"), Values("%f32arr4"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance " @@ -581,6 +632,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("GLCompute"), Values("Input", "Output"), Values("%f32arr4"), + Values("VUID-ClipDistance-ClipDistance-04187 " + "VUID-CullDistance-CullDistance-04196"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be used only with Fragment, Vertex, TessellationControl, " @@ -591,6 +644,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), Values("Input"), Values("%f32vec2", "%f32vec4", "%f32"), + Values("VUID-ClipDistance-ClipDistance-04191 " + "VUID-CullDistance-CullDistance-04200"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float array", "is not an array")))); @@ -600,6 +655,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), Values("Input"), Values("%u32arr2", "%u64arr4"), + Values("VUID-ClipDistance-ClipDistance-04191 " + "VUID-CullDistance-CullDistance-04200"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float array", "components are not float scalar")))); @@ -609,6 +666,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), Values("Input"), Values("%f64arr2", "%f64arr4"), + Values("VUID-ClipDistance-ClipDistance-04191 " + "VUID-CullDistance-CullDistance-04200"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float array", "has components with bit width 64")))); @@ -616,7 +675,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( FragCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), - Values("%f32vec4"), Values(TestResult()))); + Values("%f32vec4"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( FragCoordSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, @@ -631,6 +690,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%f32vec4"), + Values("VUID-FragCoord-FragCoord-04210"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -646,7 +706,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( FragCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragCoord"), Values("Fragment"), Values("Output"), - Values("%f32vec4"), + Values("%f32vec4"), Values("VUID-FragCoord-FragCoord-04211"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -666,6 +726,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), Values("%f32arr4", "%u32vec4"), + Values("VUID-FragCoord-FragCoord-04212"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "is not a float vector")))); @@ -683,7 +744,7 @@ INSTANTIATE_TEST_SUITE_P( FragCoordNotFloatVec4, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), - Values("%f32vec3"), + Values("%f32vec3"), Values("VUID-FragCoord-FragCoord-04212"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "has 3 components")))); @@ -701,7 +762,7 @@ INSTANTIATE_TEST_SUITE_P( FragCoordNotF32Vec4, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), - Values("%f64vec4"), + Values("%f64vec4"), Values("VUID-FragCoord-FragCoord-04212"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "has components with bit width 64")))); @@ -709,7 +770,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( FragDepthSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), - Values("%f32"), Values(TestResult()))); + Values("%f32"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( FragDepthSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, @@ -724,6 +785,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Output"), Values("%f32"), + Values("VUID-FragDepth-FragDepth-04213"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -740,7 +802,7 @@ INSTANTIATE_TEST_SUITE_P( FragDepthNotOutput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragDepth"), Values("Fragment"), Values("Input"), - Values("%f32"), + Values("%f32"), Values("VUID-FragDepth-FragDepth-04214"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Output storage class", @@ -761,6 +823,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), Values("%f32vec4", "%u32"), + Values("VUID-FragDepth-FragDepth-04215"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float scalar", "is not a float scalar")))); @@ -777,7 +840,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), - Values("%f64"), + Values("%f64"), Values("VUID-FragDepth-FragDepth-04215"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float scalar", "has bit width 64")))); @@ -786,7 +849,8 @@ INSTANTIATE_TEST_SUITE_P( FrontFacingAndHelperInvocationSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), - Values("Input"), Values("%bool"), Values(TestResult()))); + Values("Input"), Values("%bool"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( FrontFacingSuccess, @@ -802,6 +866,8 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%bool"), + Values("VUID-FrontFacing-FrontFacing-04229 " + "VUID-HelperInvocation-HelperInvocation-04239"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -819,6 +885,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), Values("Output"), Values("%bool"), + Values("VUID-FrontFacing-FrontFacing-04230 " + "VUID-HelperInvocation-HelperInvocation-04240"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -839,6 +907,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), Values("Input"), Values("%f32", "%u32"), + Values("VUID-FrontFacing-FrontFacing-04231 " + "VUID-HelperInvocation-HelperInvocation-04241"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a bool scalar", "is not a bool scalar")))); @@ -858,7 +928,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", "WorkgroupId"), Values("GLCompute"), Values("Input"), Values("%u32vec3"), - Values(TestResult()))); + Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( ComputeShaderInputInt32Vec3Success, @@ -876,6 +946,10 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "Fragment", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%u32vec3"), + Values("VUID-GlobalInvocationId-GlobalInvocationId-04236 " + "VUID-LocalInvocationId-LocalInvocationId-04281 " + "VUID-NumWorkgroups-NumWorkgroups-04296 " + "VUID-WorkgroupId-WorkgroupId-04422"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with GLCompute execution model")))); @@ -894,6 +968,10 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", "WorkgroupId"), Values("GLCompute"), Values("Output"), Values("%u32vec3"), + Values("VUID-GlobalInvocationId-GlobalInvocationId-04237 " + "VUID-LocalInvocationId-LocalInvocationId-04282 " + "VUID-NumWorkgroups-NumWorkgroups-04297 " + "VUID-WorkgroupId-WorkgroupId-04423"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -916,6 +994,10 @@ INSTANTIATE_TEST_SUITE_P( "WorkgroupId"), Values("GLCompute"), Values("Input"), Values("%u32arr3", "%f32vec3"), + Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 " + "VUID-LocalInvocationId-LocalInvocationId-04283 " + "VUID-NumWorkgroups-NumWorkgroups-04298 " + "VUID-WorkgroupId-WorkgroupId-04424"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit int vector", "is not an int vector")))); @@ -936,6 +1018,10 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", "WorkgroupId"), Values("GLCompute"), Values("Input"), Values("%u32vec4"), + Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 " + "VUID-LocalInvocationId-LocalInvocationId-04283 " + "VUID-NumWorkgroups-NumWorkgroups-04298 " + "VUID-WorkgroupId-WorkgroupId-04424"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit int vector", "has 4 components")))); @@ -955,6 +1041,10 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", "WorkgroupId"), Values("GLCompute"), Values("Input"), Values("%u64vec3"), + Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 " + "VUID-LocalInvocationId-LocalInvocationId-04283 " + "VUID-NumWorkgroups-NumWorkgroups-04298 " + "VUID-WorkgroupId-WorkgroupId-04424"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit int vector", "has components with bit width 64")))); @@ -963,7 +1053,8 @@ INSTANTIATE_TEST_SUITE_P( InvocationIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), - Values("Input"), Values("%u32"), Values(TestResult()))); + Values("Input"), Values("%u32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( InvocationIdInvalidExecutionModel, @@ -971,6 +1062,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("InvocationId"), Values("Vertex", "Fragment", "GLCompute", "TessellationEvaluation"), Values("Input"), Values("%u32"), + Values("VUID-InvocationId-InvocationId-04257"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with TessellationControl or " "Geometry execution models")))); @@ -980,6 +1072,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), Values("Output"), Values("%u32"), + Values("VUID-InvocationId-InvocationId-04258"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -990,6 +1083,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-InvocationId-InvocationId-04259"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -999,6 +1093,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), Values("Input"), Values("%u64"), + Values("VUID-InvocationId-InvocationId-04259"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1007,7 +1102,7 @@ INSTANTIATE_TEST_SUITE_P( InstanceIndexSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), - Values("%u32"), Values(TestResult()))); + Values("%u32"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( InstanceIndexSuccess, @@ -1022,6 +1117,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Geometry", "Fragment", "GLCompute", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%u32"), + Values("VUID-InstanceIndex-InstanceIndex-04263"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Vertex execution model")))); @@ -1037,7 +1133,7 @@ INSTANTIATE_TEST_SUITE_P( InstanceIndexNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"), - Values("%u32"), + Values("%u32"), Values("VUID-InstanceIndex-InstanceIndex-04264"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -1058,6 +1154,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-InstanceIndex-InstanceIndex-04265"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -1075,7 +1172,7 @@ INSTANTIATE_TEST_SUITE_P( InstanceIndexNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), - Values("%u64"), + Values("%u64"), Values("VUID-InstanceIndex-InstanceIndex-04265"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1084,31 +1181,35 @@ INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexInputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), - Values("Input"), Values("%u32"), Values(TestResult()))); + Values("Input"), Values("%u32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexOutputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Layer", "ViewportIndex"), Values("Geometry"), - Values("Output"), Values("%u32"), Values(TestResult()))); + Values("Output"), Values("%u32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexInvalidExecutionModel, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, - Combine(Values("Layer", "ViewportIndex"), - Values("TessellationControl", "GLCompute"), Values("Input"), - Values("%u32"), - Values(TestResult( - SPV_ERROR_INVALID_DATA, - "to be used only with Vertex, TessellationEvaluation, " - "Geometry, or Fragment execution models")))); + Combine( + Values("Layer", "ViewportIndex"), + Values("TessellationControl", "GLCompute"), Values("Input"), + Values("%u32"), + Values("VUID-Layer-Layer-04272 VUID-ViewportIndex-ViewportIndex-04404"), + Values( + TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Vertex, TessellationEvaluation, " + "Geometry, or Fragment execution models")))); INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexExecutionModelEnabledByCapability, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Layer", "ViewportIndex"), Values("Vertex", "TessellationEvaluation"), Values("Output"), - Values("%u32"), + Values("%u32"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "requires the ShaderViewportIndexLayerEXT capability")))); @@ -1118,7 +1219,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine( Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Output"), - Values("%u32"), + Values("%u32"), Values(nullptr), Values(TestResult(SPV_ERROR_INVALID_DATA, "Output storage class if execution model is Fragment", "which is called with execution model Fragment")))); @@ -1129,7 +1230,7 @@ INSTANTIATE_TEST_SUITE_P( Combine( Values("Layer", "ViewportIndex"), Values("Vertex", "TessellationEvaluation", "Geometry"), Values("Input"), - Values("%u32"), + Values("%u32"), Values(nullptr), Values(TestResult(SPV_ERROR_INVALID_DATA, "Input storage class if execution model is Vertex, " "TessellationEvaluation, or Geometry", @@ -1138,27 +1239,32 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexNotIntScalar, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, - Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), - Values("Input"), Values("%f32", "%u32vec3"), - Values(TestResult(SPV_ERROR_INVALID_DATA, - "needs to be a 32-bit int scalar", - "is not an int scalar")))); + Combine( + Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Input"), + Values("%f32", "%u32vec3"), + Values("VUID-Layer-Layer-04276 VUID-ViewportIndex-ViewportIndex-04408"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar")))); INSTANTIATE_TEST_SUITE_P( LayerAndViewportIndexNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, - Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), - Values("Input"), Values("%u64"), - Values(TestResult(SPV_ERROR_INVALID_DATA, - "needs to be a 32-bit int scalar", - "has bit width 64")))); + Combine( + Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Input"), + Values("%u64"), + Values("VUID-Layer-Layer-04276 VUID-ViewportIndex-ViewportIndex-04408"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64")))); INSTANTIATE_TEST_SUITE_P( PatchVerticesSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PatchVertices"), Values("TessellationEvaluation", "TessellationControl"), - Values("Input"), Values("%u32"), Values(TestResult()))); + Values("Input"), Values("%u32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PatchVerticesInvalidExecutionModel, @@ -1166,6 +1272,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PatchVertices"), Values("Vertex", "Fragment", "GLCompute", "Geometry"), Values("Input"), Values("%u32"), + Values("VUID-PatchVertices-PatchVertices-04308"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with TessellationControl or " "TessellationEvaluation execution models")))); @@ -1176,6 +1283,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PatchVertices"), Values("TessellationEvaluation", "TessellationControl"), Values("Output"), Values("%u32"), + Values("VUID-PatchVertices-PatchVertices-04309"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -1187,6 +1295,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PatchVertices"), Values("TessellationEvaluation", "TessellationControl"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-PatchVertices-PatchVertices-04310"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -1197,6 +1306,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PatchVertices"), Values("TessellationEvaluation", "TessellationControl"), Values("Input"), Values("%u64"), + Values("VUID-PatchVertices-PatchVertices-04310"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1204,7 +1314,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( PointCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), - Values("%f32vec2"), Values(TestResult()))); + Values("%f32vec2"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PointCoordNotFragment, @@ -1214,6 +1324,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%f32vec2"), + Values("VUID-PointCoord-PointCoord-04311"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -1221,7 +1332,7 @@ INSTANTIATE_TEST_SUITE_P( PointCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointCoord"), Values("Fragment"), Values("Output"), - Values("%f32vec2"), + Values("%f32vec2"), Values("VUID-PointCoord-PointCoord-04312"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -1232,6 +1343,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), Values("%f32arr2", "%u32vec2"), + Values("VUID-PointCoord-PointCoord-04313"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "is not a float vector")))); @@ -1240,7 +1352,7 @@ INSTANTIATE_TEST_SUITE_P( PointCoordNotFloatVec3, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), - Values("%f32vec3"), + Values("%f32vec3"), Values("VUID-PointCoord-PointCoord-04313"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "has 3 components")))); @@ -1249,7 +1361,7 @@ INSTANTIATE_TEST_SUITE_P( PointCoordNotF32Vec4, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), - Values("%f64vec2"), + Values("%f64vec2"), Values("VUID-PointCoord-PointCoord-04313"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "has components with bit width 64")))); @@ -1260,20 +1372,22 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PointSize"), Values("Vertex", "Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Output"), Values("%f32"), Values(TestResult()))); + Values("Output"), Values("%f32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PointSizeInputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointSize"), Values("Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Input"), Values("%f32"), Values(TestResult()))); + Values("Input"), Values("%f32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PointSizeVertexInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointSize"), Values("Vertex"), Values("Input"), - Values("%f32"), + Values("%f32"), Values("VUID-PointSize-PointSize-04315"), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow BuiltIn PointSize " @@ -1286,6 +1400,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointSize"), Values("GLCompute", "Fragment"), Values("Input", "Output"), Values("%f32"), + Values("VUID-PointSize-PointSize-04314"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be used only with Vertex, TessellationControl, " @@ -1296,6 +1411,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointSize"), Values("Vertex"), Values("Output"), Values("%f32vec4", "%u32"), + Values("VUID-PointSize-PointSize-04317"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float scalar", "is not a float scalar")))); @@ -1303,7 +1419,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( PointSizeNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PointSize"), Values("Vertex"), Values("Output"), - Values("%f64"), + Values("%f64"), Values("VUID-PointSize-PointSize-04317"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit float scalar", "has bit width 64")))); @@ -1314,7 +1430,8 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("Position"), Values("Vertex", "Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Output"), Values("%f32vec4"), Values(TestResult()))); + Values("Output"), Values("%f32vec4"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PositionOutputSuccess, @@ -1336,7 +1453,8 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Input"), Values("%f32vec4"), Values(TestResult()))); + Values("Input"), Values("%f32vec4"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PositionInputFailure, @@ -1352,7 +1470,7 @@ INSTANTIATE_TEST_SUITE_P( PositionVertexInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("Vertex"), Values("Input"), - Values("%f32vec4"), + Values("%f32vec4"), Values("VUID-Position-Position-04320"), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow BuiltIn Position " @@ -1365,6 +1483,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("GLCompute", "Fragment"), Values("Input", "Output"), Values("%f32vec4"), + Values("VUID-Position-Position-04318"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be used only with Vertex, TessellationControl, " @@ -1375,6 +1494,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("Geometry"), Values("Input"), Values("%f32arr4", "%u32vec4"), + Values("VUID-Position-Position-04321"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "is not a float vector")))); @@ -1392,7 +1512,7 @@ INSTANTIATE_TEST_SUITE_P( PositionNotFloatVec4, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("Geometry"), Values("Input"), - Values("%f32vec3"), + Values("%f32vec3"), Values("VUID-Position-Position-04321"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "has 3 components")))); @@ -1410,7 +1530,7 @@ INSTANTIATE_TEST_SUITE_P( PositionNotF32Vec4, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("Position"), Values("Geometry"), Values("Input"), - Values("%f64vec4"), + Values("%f64vec4"), Values("VUID-Position-Position-04321"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float vector", "has components with bit width 64")))); @@ -1421,19 +1541,21 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PrimitiveId"), Values("Fragment", "TessellationControl", "TessellationEvaluation", "Geometry"), - Values("Input"), Values("%u32"), Values(TestResult()))); + Values("Input"), Values("%u32"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PrimitiveIdOutputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PrimitiveId"), Values("Geometry"), Values("Output"), - Values("%u32"), Values(TestResult()))); + Values("%u32"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( PrimitiveIdInvalidExecutionModel, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"), Values("Input"), Values("%u32"), + Values("VUID-PrimitiveId-PrimitiveId-04330"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be used only with Fragment, TessellationControl, " @@ -1444,7 +1566,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine( Values("PrimitiveId"), Values("Fragment"), Values("Output"), - Values("%u32"), + Values("%u32"), Values("VUID-PrimitiveId-PrimitiveId-04334"), Values(TestResult(SPV_ERROR_INVALID_DATA, "Output storage class if execution model is Fragment", "which is called with execution model Fragment")))); @@ -1455,6 +1577,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("PrimitiveId"), Values("TessellationControl", "TessellationEvaluation"), Values("Output"), Values("%u32"), + Values("VUID-PrimitiveId-PrimitiveId-04334"), Values(TestResult( SPV_ERROR_INVALID_DATA, "Output storage class if execution model is Tessellation", @@ -1465,6 +1588,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-PrimitiveId-PrimitiveId-04337"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -1473,7 +1597,7 @@ INSTANTIATE_TEST_SUITE_P( PrimitiveIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"), - Values("%u64"), + Values("%u64"), Values("VUID-PrimitiveId-PrimitiveId-04337"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1481,7 +1605,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( SampleIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleId"), Values("Fragment"), Values("Input"), - Values("%u32"), Values(TestResult()))); + Values("%u32"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SampleIdInvalidExecutionModel, @@ -1490,7 +1614,7 @@ INSTANTIATE_TEST_SUITE_P( Values("SampleId"), Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), - Values("Input"), Values("%u32"), + Values("Input"), Values("%u32"), Values("VUID-SampleId-SampleId-04354"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -1498,7 +1622,7 @@ INSTANTIATE_TEST_SUITE_P( SampleIdNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine( Values("SampleId"), Values("Fragment"), Values("Output"), - Values("%u32"), + Values("%u32"), Values("VUID-SampleId-SampleId-04355"), Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows BuiltIn SampleId to be only used " "for variables with Input storage class")))); @@ -1507,7 +1631,7 @@ INSTANTIATE_TEST_SUITE_P( SampleIdNotIntScalar, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleId"), Values("Fragment"), Values("Input"), - Values("%f32", "%u32vec3"), + Values("%f32", "%u32vec3"), Values("VUID-SampleId-SampleId-04356"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -1515,7 +1639,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( SampleIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleId"), Values("Fragment"), Values("Input"), - Values("%u64"), + Values("%u64"), Values("VUID-SampleId-SampleId-04356"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1523,7 +1647,8 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( SampleMaskSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleMask"), Values("Fragment"), Values("Input", "Output"), - Values("%u32arr2", "%u32arr4"), Values(TestResult()))); + Values("%u32arr2", "%u32arr4"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SampleMaskInvalidExecutionModel, @@ -1533,6 +1658,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%u32arr2"), + Values("VUID-SampleMask-SampleMask-04357"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -1540,7 +1666,7 @@ INSTANTIATE_TEST_SUITE_P( SampleMaskWrongStorageClass, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"), - Values("%u32arr2"), + Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec allows BuiltIn SampleMask to be only used for " @@ -1551,6 +1677,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-SampleMask-SampleMask-04359"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int array", "is not an array")))); @@ -1559,7 +1686,7 @@ INSTANTIATE_TEST_SUITE_P( SampleMaskNotIntArray, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), - Values("%f32arr2"), + Values("%f32arr2"), Values("VUID-SampleMask-SampleMask-04359"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int array", "components are not int scalar")))); @@ -1568,7 +1695,7 @@ INSTANTIATE_TEST_SUITE_P( SampleMaskNotInt32Array, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), - Values("%u64arr2"), + Values("%u64arr2"), Values("VUID-SampleMask-SampleMask-04359"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int array", "has components with bit width 64")))); @@ -1577,7 +1704,7 @@ INSTANTIATE_TEST_SUITE_P( SamplePositionSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), - Values("%f32vec2"), Values(TestResult()))); + Values("%f32vec2"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SamplePositionNotFragment, @@ -1587,6 +1714,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%f32vec2"), + Values("VUID-SamplePosition-SamplePosition-04360"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); @@ -1595,6 +1723,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SamplePosition"), Values("Fragment"), Values("Output"), Values("%f32vec2"), + Values("VUID-SamplePosition-SamplePosition-04361"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -1605,6 +1734,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), Values("%f32arr2", "%u32vec4"), + Values("VUID-SamplePosition-SamplePosition-04362"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "is not a float vector")))); @@ -1614,6 +1744,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), Values("%f32vec3"), + Values("VUID-SamplePosition-SamplePosition-04362"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "has 3 components")))); @@ -1623,6 +1754,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), Values("%f64vec2"), + Values("VUID-SamplePosition-SamplePosition-04362"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float vector", "has components with bit width 64")))); @@ -1630,7 +1762,8 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( TessCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessCoord"), Values("TessellationEvaluation"), - Values("Input"), Values("%f32vec3"), Values(TestResult()))); + Values("Input"), Values("%f32vec3"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( TessCoordNotFragment, @@ -1640,6 +1773,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Vertex", "GLCompute", "Geometry", "TessellationControl", "Fragment"), Values("Input"), Values("%f32vec3"), + Values("VUID-TessCoord-TessCoord-04387"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be used only with TessellationEvaluation execution model")))); @@ -1647,7 +1781,7 @@ INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P( TessCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessCoord"), Values("Fragment"), Values("Output"), - Values("%f32vec3"), + Values("%f32vec3"), Values("VUID-TessCoord-TessCoord-04388"), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -1658,6 +1792,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), Values("%f32arr3", "%u32vec4"), + Values("VUID-TessCoord-TessCoord-04389"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit float vector", "is not a float vector")))); @@ -1666,7 +1801,7 @@ INSTANTIATE_TEST_SUITE_P( TessCoordNotFloatVec3, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), - Values("%f32vec2"), + Values("%f32vec2"), Values("VUID-TessCoord-TessCoord-04389"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit float vector", "has 2 components")))); @@ -1675,7 +1810,7 @@ INSTANTIATE_TEST_SUITE_P( TessCoordNotF32Vec3, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), - Values("%f64vec3"), + Values("%f64vec3"), Values("VUID-TessCoord-TessCoord-04389"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 3-component 32-bit float vector", "has components with bit width 64")))); @@ -1684,13 +1819,15 @@ INSTANTIATE_TEST_SUITE_P( TessLevelOuterTeseInputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), - Values("Input"), Values("%f32arr4"), Values(TestResult()))); + Values("Input"), Values("%f32arr4"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( TessLevelOuterTescOutputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationControl"), - Values("Output"), Values("%f32arr4"), Values(TestResult()))); + Values("Output"), Values("%f32arr4"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( TessLevelOuterInvalidExecutionModel, @@ -1698,6 +1835,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("TessLevelOuter"), Values("Vertex", "GLCompute", "Geometry", "Fragment"), Values("Input"), Values("%f32arr4"), + Values("VUID-TessLevelOuter-TessLevelOuter-04390"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with TessellationControl or " "TessellationEvaluation execution models.")))); @@ -1706,7 +1844,7 @@ INSTANTIATE_TEST_SUITE_P( TessLevelOuterOutputTese, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), - Values("Output"), Values("%f32arr4"), + Values("Output"), Values("%f32arr4"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " @@ -1717,7 +1855,7 @@ INSTANTIATE_TEST_SUITE_P( TessLevelOuterInputTesc, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationControl"), - Values("Input"), Values("%f32arr4"), + Values("Input"), Values("%f32arr4"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " @@ -1729,6 +1867,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), Values("Input"), Values("%f32vec4", "%f32"), + Values("VUID-TessLevelOuter-TessLevelOuter-04393"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float array", "is not an array")))); @@ -1738,6 +1877,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), Values("Input"), Values("%u32arr4"), + Values("VUID-TessLevelOuter-TessLevelOuter-04393"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float array", "components are not float scalar")))); @@ -1747,6 +1887,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), Values("Input"), Values("%f32arr3"), + Values("VUID-TessLevelOuter-TessLevelOuter-04393"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float array", "has 3 components")))); @@ -1756,6 +1897,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), Values("Input"), Values("%f64arr4"), + Values("VUID-TessLevelOuter-TessLevelOuter-04393"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 4-component 32-bit float array", "has components with bit width 64")))); @@ -1764,13 +1906,15 @@ INSTANTIATE_TEST_SUITE_P( TessLevelInnerTeseInputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), - Values("Input"), Values("%f32arr2"), Values(TestResult()))); + Values("Input"), Values("%f32arr2"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( TessLevelInnerTescOutputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationControl"), - Values("Output"), Values("%f32arr2"), Values(TestResult()))); + Values("Output"), Values("%f32arr2"), Values(nullptr), + Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( TessLevelInnerInvalidExecutionModel, @@ -1778,6 +1922,7 @@ INSTANTIATE_TEST_SUITE_P( Combine(Values("TessLevelInner"), Values("Vertex", "GLCompute", "Geometry", "Fragment"), Values("Input"), Values("%f32arr2"), + Values("VUID-TessLevelInner-TessLevelInner-04394"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with TessellationControl or " "TessellationEvaluation execution models.")))); @@ -1786,7 +1931,7 @@ INSTANTIATE_TEST_SUITE_P( TessLevelInnerOutputTese, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), - Values("Output"), Values("%f32arr2"), + Values("Output"), Values("%f32arr2"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " @@ -1797,7 +1942,7 @@ INSTANTIATE_TEST_SUITE_P( TessLevelInnerInputTesc, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationControl"), - Values("Input"), Values("%f32arr2"), + Values("Input"), Values("%f32arr2"), Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " @@ -1809,6 +1954,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), Values("Input"), Values("%f32vec2", "%f32"), + Values("VUID-TessLevelInner-TessLevelInner-04397"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float array", "is not an array")))); @@ -1818,6 +1964,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), Values("Input"), Values("%u32arr2"), + Values("VUID-TessLevelInner-TessLevelInner-04397"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float array", "components are not float scalar")))); @@ -1827,6 +1974,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), Values("Input"), Values("%f32arr3"), + Values("VUID-TessLevelInner-TessLevelInner-04397"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float array", "has 3 components")))); @@ -1836,6 +1984,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), Values("Input"), Values("%f64arr2"), + Values("VUID-TessLevelInner-TessLevelInner-04397"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 2-component 32-bit float array", "has components with bit width 64")))); @@ -1844,7 +1993,7 @@ INSTANTIATE_TEST_SUITE_P( VertexIndexSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), - Values("%u32"), Values(TestResult()))); + Values("%u32"), Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( VertexIndexSuccess, @@ -1859,6 +2008,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Fragment", "GLCompute", "Geometry", "TessellationControl", "TessellationEvaluation"), Values("Input"), Values("%u32"), + Values("VUID-VertexIndex-VertexIndex-04398"), Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Vertex execution model")))); @@ -1875,7 +2025,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine( Values("VertexIndex"), Values("Vertex"), Values("Output"), - Values("%u32"), + Values("%u32"), Values("VUID-VertexIndex-VertexIndex-04399"), Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows BuiltIn VertexIndex to be only " "used for variables with Input storage class")))); @@ -1895,6 +2045,7 @@ INSTANTIATE_TEST_SUITE_P( ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), Values("%f32", "%u32vec3"), + Values("VUID-VertexIndex-VertexIndex-04400"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -1912,7 +2063,7 @@ INSTANTIATE_TEST_SUITE_P( VertexIndexNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), - Values("%u64"), + Values("%u64"), Values("VUID-VertexIndex-VertexIndex-04400"), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -1961,6 +2112,183 @@ INSTANTIATE_TEST_SUITE_P( Values(TestResult(SPV_ERROR_INVALID_DATA, "WebGPU does not allow BuiltIn")))); +INSTANTIATE_TEST_SUITE_P( + BaseInstanceOrVertexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"), + Values("Input"), Values("%u32"), + Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values(nullptr), Values(TestResult()))); + +INSTANTIATE_TEST_SUITE_P( + BaseInstanceOrVertexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("BaseInstance", "BaseVertex"), + Values("Fragment", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-BaseInstance-BaseInstance-04181 " + "VUID-BaseVertex-BaseVertex-04184"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Vertex execution model")))); + +INSTANTIATE_TEST_SUITE_P( + BaseInstanceOrVertexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"), + Values("Output"), Values("%u32"), + Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-BaseInstance-BaseInstance-04182 " + "VUID-BaseVertex-BaseVertex-04185"), + Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows", + "used for variables with Input storage class")))); + +INSTANTIATE_TEST_SUITE_P( + BaseInstanceOrVertexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"), + Values("Input"), Values("%f32", "%u32vec3"), + Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-BaseInstance-BaseInstance-04183 " + "VUID-BaseVertex-BaseVertex-04186"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar")))); + +INSTANTIATE_TEST_SUITE_P( + DrawIndexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DrawIndex"), Values("Vertex"), Values("Input"), + Values("%u32"), Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values(nullptr), Values(TestResult()))); + +INSTANTIATE_TEST_SUITE_P( + DrawIndexMeshSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine( + Values("DrawIndex"), Values("MeshNV", "TaskNV"), Values("Input"), + Values("%u32"), Values("OpCapability MeshShadingNV\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\nOpExtension " + "\"SPV_NV_mesh_shader\"\n"), + Values(nullptr), Values(TestResult()))); + +INSTANTIATE_TEST_SUITE_P( + DrawIndexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DrawIndex"), + Values("Fragment", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-DrawIndex-DrawIndex-04207"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Vertex, MeshNV, or TaskNV " + "execution model")))); + +INSTANTIATE_TEST_SUITE_P( + DrawIndexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DrawIndex"), Values("Vertex"), Values("Output"), + Values("%u32"), Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-DrawIndex-DrawIndex-04208"), + Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows", + "used for variables with Input storage class")))); + +INSTANTIATE_TEST_SUITE_P( + DrawIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DrawIndex"), Values("Vertex"), Values("Input"), + Values("%f32", "%u32vec3"), Values("OpCapability DrawParameters\n"), + Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"), + Values("VUID-DrawIndex-DrawIndex-04209"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar")))); + +INSTANTIATE_TEST_SUITE_P( + ViewIndexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("ViewIndex"), + Values("Fragment", "Vertex", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), Values("OpCapability MultiView\n"), + Values("OpExtension \"SPV_KHR_multiview\"\n"), Values(nullptr), + Values(TestResult()))); + +INSTANTIATE_TEST_SUITE_P( + ViewIndexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("ViewIndex"), Values("GLCompute"), Values("Input"), + Values("%u32"), Values("OpCapability MultiView\n"), + Values("OpExtension \"SPV_KHR_multiview\"\n"), + Values("VUID-ViewIndex-ViewIndex-04401"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be not be used with GLCompute execution model")))); + +INSTANTIATE_TEST_SUITE_P( + ViewIndexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("ViewIndex"), Values("Vertex"), Values("Output"), + Values("%u32"), Values("OpCapability MultiView\n"), + Values("OpExtension \"SPV_KHR_multiview\"\n"), + Values("VUID-ViewIndex-ViewIndex-04402"), + Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows", + "used for variables with Input storage class")))); + +INSTANTIATE_TEST_SUITE_P( + ViewIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("ViewIndex"), Values("Vertex"), Values("Input"), + Values("%f32", "%u32vec3"), Values("OpCapability MultiView\n"), + Values("OpExtension \"SPV_KHR_multiview\"\n"), + Values("VUID-ViewIndex-ViewIndex-04403"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar")))); + +INSTANTIATE_TEST_SUITE_P( + DeviceIndexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DeviceIndex"), + Values("Fragment", "Vertex", "Geometry", "TessellationControl", + "TessellationEvaluation", "GLCompute"), + Values("Input"), Values("%u32"), + Values("OpCapability DeviceGroup\n"), + Values("OpExtension \"SPV_KHR_device_group\"\n"), Values(nullptr), + Values(TestResult()))); + +INSTANTIATE_TEST_SUITE_P( + DeviceIndexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DeviceIndex"), Values("Fragment", "Vertex", "GLCompute"), + Values("Output"), Values("%u32"), + Values("OpCapability DeviceGroup\n"), + Values("OpExtension \"SPV_KHR_device_group\"\n"), + Values("VUID-DeviceIndex-DeviceIndex-04205"), + Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows", + "used for variables with Input storage class")))); + +INSTANTIATE_TEST_SUITE_P( + DeviceIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, + Combine(Values("DeviceIndex"), Values("Fragment", "Vertex", "GLCompute"), + Values("Input"), Values("%f32", "%u32vec3"), + Values("OpCapability DeviceGroup\n"), + Values("OpExtension \"SPV_KHR_device_group\"\n"), + Values("VUID-DeviceIndex-DeviceIndex-04206"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar")))); + CodeGenerator GetArrayedVariableCodeGenerator(spv_target_env env, const char* const built_in, const char* const execution_model, @@ -2153,7 +2481,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Input"), Values("%u32"), Values("OpCapability ShaderSMBuiltinsNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"), - Values(TestResult()))); + Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SMBuiltinsInputMeshSuccess, @@ -2164,7 +2492,7 @@ INSTANTIATE_TEST_SUITE_P( Values("OpCapability ShaderSMBuiltinsNV\nOpCapability MeshShadingNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension " "\"SPV_NV_mesh_shader\"\n"), - Values(TestResult()))); + Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SMBuiltinsInputRaySuccess, @@ -2177,7 +2505,7 @@ INSTANTIATE_TEST_SUITE_P( Values("OpCapability ShaderSMBuiltinsNV\nOpCapability RayTracingNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension " "\"SPV_NV_ray_tracing\"\n"), - Values(TestResult()))); + Values(nullptr), Values(TestResult()))); INSTANTIATE_TEST_SUITE_P( SMBuiltinsNotInput, @@ -2188,6 +2516,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Output"), Values("%u32"), Values("OpCapability ShaderSMBuiltinsNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"), + Values(nullptr), Values(TestResult( SPV_ERROR_INVALID_DATA, "to be only used for variables with Input storage class", @@ -2202,6 +2531,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Input"), Values("%f32", "%u32vec3"), Values("OpCapability ShaderSMBuiltinsNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"), + Values(nullptr), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "is not an int scalar")))); @@ -2215,6 +2545,7 @@ INSTANTIATE_TEST_SUITE_P( Values("Input"), Values("%u64"), Values("OpCapability ShaderSMBuiltinsNV\n"), Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"), + Values(nullptr), Values(TestResult(SPV_ERROR_INVALID_DATA, "needs to be a 32-bit int scalar", "has bit width 64")))); @@ -2294,6 +2625,9 @@ TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeFragment) { HasSubstr("is referencing ID <2> (OpConstantComposite) which is " "decorated with BuiltIn WorkgroupSize in function <1> " "called with execution model Fragment")); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04425 " + "VUID-WorkgroupSize-WorkgroupSize-04427")); } TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeFragment) { @@ -2369,6 +2703,8 @@ TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVector) { HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " "variable needs to be a 3-component 32-bit int vector. " "ID <2> (OpConstant) is not an int vector.")); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427")); } TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVector) { @@ -2417,6 +2753,8 @@ TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotIntVector) { HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " "variable needs to be a 3-component 32-bit int vector. " "ID <2> (OpConstantComposite) is not an int vector.")); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427")); } TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotIntVector) { @@ -2465,6 +2803,8 @@ TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVec3) { HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " "variable needs to be a 3-component 32-bit int vector. " "ID <2> (OpConstantComposite) has 2 components.")); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427")); } TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVec3) { @@ -2503,6 +2843,8 @@ OpDecorate %workgroup_size BuiltIn WorkgroupSize HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize variable " "needs to be a 3-component 32-bit int vector. ID <2> " "(OpConstantComposite) has components with bit width 64.")); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427")); } TEST_F(ValidateBuiltIns, WorkgroupSizePrivateVar) { @@ -2806,6 +3148,8 @@ TEST_F(ValidateBuiltIns, VulkanFragmentFragDepthNoDepthReplacing) { EXPECT_THAT(getDiagnosticString(), HasSubstr("Vulkan spec requires DepthReplacing execution mode to " "be declared when using BuiltIn FragDepth")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VUID-FragDepth-FragDepth-04216")); } TEST_F(ValidateBuiltIns, WebGPUFragmentFragDepthNoDepthReplacing) { @@ -2885,6 +3229,8 @@ TEST_F(ValidateBuiltIns, EXPECT_THAT(getDiagnosticString(), HasSubstr("Vulkan spec requires DepthReplacing execution mode to " "be declared when using BuiltIn FragDepth")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VUID-FragDepth-FragDepth-04216")); } TEST_F(ValidateBuiltIns, diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp index 756f762a..9705cb8f 100644 --- a/test/val/val_capability_test.cpp +++ b/test/val/val_capability_test.cpp @@ -2288,11 +2288,30 @@ OpMemoryModel Physical64 OpenCL "Embedded Profile")); } +TEST_F(ValidateCapability, OpenCL12EmbeddedNoLongerEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Pipes +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + std::string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_1_2)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Pipes is not allowed by OpenCL 1.2 " + "Embedded Profile")); +} + TEST_F(ValidateCapability, OpenCL20FullCapability) { const std::string spirv = R"( OpCapability Kernel OpCapability Addresses OpCapability Linkage +OpCapability Groups OpCapability Pipes OpMemoryModel Physical64 OpenCL %u32 = OpTypeInt 32 0 diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp index 630a19d2..247a91f8 100644 --- a/test/val/val_cfg_test.cpp +++ b/test/val/val_cfg_test.cpp @@ -1063,10 +1063,10 @@ std::string GetUnreachableMergeWithComplexBody(SpvCapability cap, Block merge("merge", SpvOpUnreachable); entry.AppendBody(spvIsWebGPUEnv(env) - ? "%dummy = OpVariable %intptrt Function %two\n" - : "%dummy = OpVariable %intptrt Function\n"); + ? "%placeholder = OpVariable %intptrt Function %two\n" + : "%placeholder = OpVariable %intptrt Function\n"); entry.AppendBody("%cond = OpSLessThan %boolt %one %two\n"); - merge.AppendBody("OpStore %dummy %one\n"); + merge.AppendBody("OpStore %placeholder %one\n"); std::string str = header; if (spvIsWebGPUEnv(env)) { @@ -1120,9 +1120,9 @@ std::string GetUnreachableContinueWithComplexBody(SpvCapability cap, target >> branch; entry.AppendBody(spvIsWebGPUEnv(env) - ? "%dummy = OpVariable %intptrt Function %two\n" - : "%dummy = OpVariable %intptrt Function\n"); - target.AppendBody("OpStore %dummy %one\n"); + ? "%placeholder = OpVariable %intptrt Function %two\n" + : "%placeholder = OpVariable %intptrt Function\n"); + target.AppendBody("OpStore %placeholder %one\n"); std::string str = header; if (spvIsWebGPUEnv(env)) { @@ -1279,8 +1279,8 @@ std::string GetUnreachableContinueWithBranchUse(SpvCapability cap, target >> branch; entry.AppendBody(spvIsWebGPUEnv(env) - ? "%dummy = OpVariable %intptrt Function %two\n" - : "%dummy = OpVariable %intptrt Function\n"); + ? "%placeholder = OpVariable %intptrt Function %two\n" + : "%placeholder = OpVariable %intptrt Function\n"); std::string str = header; if (spvIsWebGPUEnv(env)) { diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp index d8d00104..683a76f5 100644 --- a/test/val/val_ext_inst_test.cpp +++ b/test/val/val_ext_inst_test.cpp @@ -54,8 +54,12 @@ using ValidateOpenCL100DebugInfoDebugLexicalBlock = spvtest::ValidateBase<std::pair<std::string, std::string>>; using ValidateOpenCL100DebugInfoDebugLocalVariable = spvtest::ValidateBase<std::pair<std::string, std::string>>; +using ValidateOpenCL100DebugInfoDebugGlobalVariable = + spvtest::ValidateBase<std::pair<std::string, std::string>>; using ValidateOpenCL100DebugInfoDebugDeclare = spvtest::ValidateBase<std::pair<std::string, std::string>>; +using ValidateOpenCL100DebugInfoDebugValue = + spvtest::ValidateBase<std::pair<std::string, std::string>>; using ValidateGlslStd450SqrtLike = spvtest::ValidateBase<std::string>; using ValidateGlslStd450FMinLike = spvtest::ValidateBase<std::string>; using ValidateGlslStd450FClampLike = spvtest::ValidateBase<std::string>; @@ -83,6 +87,7 @@ using ValidateOpenCLStdFractLike = spvtest::ValidateBase<std::string>; using ValidateOpenCLStdFrexpLike = spvtest::ValidateBase<std::string>; using ValidateOpenCLStdLdexpLike = spvtest::ValidateBase<std::string>; using ValidateOpenCLStdUpsampleLike = spvtest::ValidateBase<std::string>; +using ValidateClspvReflection = spvtest::ValidateBase<bool>; // Returns number of components in Pack/Unpack extended instructions. // |ext_inst_name| is expected to be of the format "PackHalf2x16". @@ -755,9 +760,8 @@ TEST_P(ValidateLocalDebugInfoOutOfFunction, OpenCLDebugInfo100DebugScope) { const std::string dbg_inst_header = R"( %dbg_src = OpExtInst %void %DbgExt DebugSource %src %code %comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL -%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified %int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_0 Signed -%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main %foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %int_info %dbg_src 1 1 %main_info FlagIsLocal %expr = OpExtInst %void %DbgExt DebugExpression @@ -800,8 +804,7 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionForwardReference) { const std::string dbg_inst_header = R"( %dbg_src = OpExtInst %void %DbgExt DebugSource %src %code %comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL -%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified -%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main )"; @@ -831,8 +834,7 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionMissingOpFunction) { %dbgNone = OpExtInst %void %DbgExt DebugInfoNone %dbg_src = OpExtInst %void %DbgExt DebugSource %src %code %comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL -%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified -%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %dbgNone )"; @@ -1343,6 +1345,40 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArray) { ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayWithVariableSize) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "main() {}" +%float_name = OpString "float" +%int_name = OpString "int" +%main_name = OpString "main" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%uint_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Unsigned +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main +%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %uint_info %dbg_src 1 1 %main_info FlagIsLocal +%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailBaseType) { const std::string src = R"( %src = OpString "simple.hlsl" @@ -1399,8 +1435,10 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCount) { src, size_const, dbg_inst_header, "", extension, "Vertex")); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("expected operand Component Count must be a result id " - "of OpConstant")); + HasSubstr("Component Count must be OpConstant with a 32- or " + "64-bits integer scalar type or DebugGlobalVariable or " + "DebugLocalVariable with a 32- or 64-bits unsigned " + "integer scalar type")); } TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountFloat) { @@ -1429,7 +1467,10 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountFloat) { src, size_const, dbg_inst_header, "", extension, "Vertex")); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Component Count must be positive integer")); + HasSubstr("Component Count must be OpConstant with a 32- or " + "64-bits integer scalar type or DebugGlobalVariable or " + "DebugLocalVariable with a 32- or 64-bits unsigned " + "integer scalar type")); } TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountZero) { @@ -1458,7 +1499,47 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountZero) { src, size_const, dbg_inst_header, "", extension, "Vertex")); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Component Count must be positive integer")); + HasSubstr("Component Count must be OpConstant with a 32- or " + "64-bits integer scalar type or DebugGlobalVariable or " + "DebugLocalVariable with a 32- or 64-bits unsigned " + "integer scalar type")); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "main() {}" +%float_name = OpString "float" +%main_name = OpString "main" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main +%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 1 %main_info FlagIsLocal +%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Component Count must be OpConstant with a 32- or " + "64-bits integer scalar type or DebugGlobalVariable or " + "DebugLocalVariable with a 32- or 64-bits unsigned " + "integer scalar type")); } TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVector) { @@ -2602,6 +2683,581 @@ TEST_F(ValidateOpenCL100DebugInfo, DebugExpressionFail) { "expected operand Operation must be a result id of DebugOperation")); } +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplate) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "OpaqueType foo; +main() {} +" +%float_name = OpString "float" +%ty_name = OpString "Texture" +%t_name = OpString "T" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +%int_128 = OpConstant %u32 128 +)"; + + const std::string dbg_inst_header = R"( +%dbg_none = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic +%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0 +%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateUsedForVariableType) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "OpaqueType foo; +main() {} +" +%float_name = OpString "float" +%ty_name = OpString "Texture" +%t_name = OpString "T" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +%int_128 = OpConstant %u32 128 +)"; + + const std::string dbg_inst_header = R"( +%dbg_none = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic +%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0 +%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param +%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %temp %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFunction) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "OpaqueType foo; +main() {} +" +%float_name = OpString "float" +%ty_name = OpString "Texture" +%t_name = OpString "T" +%main_name = OpString "main" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +%int_128 = OpConstant %u32 128 +)"; + + const std::string dbg_inst_header = R"( +%dbg_none = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0 +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main +%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %param +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailTarget) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "OpaqueType foo; +main() {} +" +%float_name = OpString "float" +%ty_name = OpString "Texture" +%t_name = OpString "T" +%main_name = OpString "main" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +%int_128 = OpConstant %u32 128 +)"; + + const std::string dbg_inst_header = R"( +%dbg_none = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0 +%temp = OpExtInst %void %DbgExt DebugTypeTemplate %float_info %param +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected operand Target must be DebugTypeComposite or " + "DebugFunction")); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailParam) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "OpaqueType foo; +main() {} +" +%float_name = OpString "float" +%ty_name = OpString "Texture" +%t_name = OpString "T" +%main_name = OpString "main" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +%int_128 = OpConstant %u32 128 +)"; + + const std::string dbg_inst_header = R"( +%dbg_none = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0 +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main +%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %float_info +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "expected operand Parameters must be DebugTypeTemplateParameter or " + "DebugTypeTemplateTemplateParameter")); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariable) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "float foo; void main() {}" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableStaticMember) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "float foo; void main() {}" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%t = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Class %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsPublic %a +%a = OpExtInst %void %DbgExt DebugTypeMember %foo_name %float_info %dbg_src 0 0 %t %u32_0 %int_32 FlagIsPublic +%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate %a +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableDebugInfoNone) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "float foo; void main() {}" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbgNone = OpExtInst %void %DbgExt DebugInfoNone +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %dbgNone FlagIsProtected|FlagIsPrivate +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableConst) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "float foo; void main() {}" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsProtected|FlagIsPrivate +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, "", extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCL100DebugInfoDebugGlobalVariable, Fail) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "float foo; void main() {}" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const auto& param = GetParam(); + + std::ostringstream ss; + ss << R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%foo = OpExtInst %void %DbgExt DebugGlobalVariable )" + << param.first; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(), + "", extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected operand " + param.second)); +} + +INSTANTIATE_TEST_SUITE_P( + AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugGlobalVariable, + ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{ + std::make_pair( + R"(%void %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)", + "Name"), + std::make_pair( + R"(%foo_name %dbg_src %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)", + "Type"), + std::make_pair( + R"(%foo_name %float_info %comp_unit 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)", + "Source"), + std::make_pair( + R"(%foo_name %float_info %dbg_src 0 0 %dbg_src %foo_name %f32_input FlagIsProtected|FlagIsPrivate)", + "Scope"), + std::make_pair( + R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %void %f32_input FlagIsProtected|FlagIsPrivate)", + "Linkage Name"), + std::make_pair( + R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %void FlagIsProtected|FlagIsPrivate)", + "Variable"), + })); + +TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAt) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() {}" +%void_name = OpString "void" +%main_name = OpString "main" +%main_linkage_name = OpString "v_main" +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main +%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info +%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %inlined_at +)"; + + const std::string body = R"( +%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, "", dbg_inst_header, body, extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() {}" +%void_name = OpString "void" +%main_name = OpString "main" +%main_linkage_name = OpString "v_main" +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main +%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info +%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %inlined_at +)"; + + const std::string body = R"( +%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, "", dbg_inst_header, body, extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope")); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail2) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() {}" +%void_name = OpString "void" +%main_name = OpString "main" +%main_linkage_name = OpString "v_main" +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void +%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main +%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info +%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %main_info +)"; + + const std::string body = R"( +%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, "", dbg_inst_header, body, extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined")); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugValue) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() { float foo; }" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_3 = OpConstant %u32 3 +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%null_expr = OpExtInst %void %DbgExt DebugExpression +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4 +%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0 +)"; + + const std::string body = R"( +%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %int_3 +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, body, extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpenCL100DebugInfo, DebugValueWithVariableIndex) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() { float foo; }" +%float_name = OpString "float" +%int_name = OpString "int" +%foo_name = OpString "foo" +%len_name = OpString "length" +)"; + + const std::string size_const = R"( +%int_3 = OpConstant %u32 3 +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%null_expr = OpExtInst %void %DbgExt DebugExpression +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Signed +%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4 +%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal +%len_info = OpExtInst %void %DbgExt DebugLocalVariable %len_name %int_info %dbg_src 0 0 %comp_unit FlagIsLocal +)"; + + const std::string body = R"( +%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %len_info +)"; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, body, extension, "Vertex")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCL100DebugInfoDebugValue, Fail) { + const std::string src = R"( +%src = OpString "simple.hlsl" +%code = OpString "void main() { float foo; }" +%float_name = OpString "float" +%foo_name = OpString "foo" +)"; + + const std::string size_const = R"( +%int_32 = OpConstant %u32 32 +)"; + + const std::string dbg_inst_header = R"( +%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code +%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL +%null_expr = OpExtInst %void %DbgExt DebugExpression +%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float +%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0 +)"; + + const auto& param = GetParam(); + + std::ostringstream ss; + ss << R"( +%decl = OpExtInst %void %DbgExt DebugValue )" + << param.first; + + const std::string extension = R"( +%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100" +)"; + + CompileSuccessfully(GenerateShaderCodeForDebugInfo( + src, size_const, dbg_inst_header, ss.str(), extension, "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("expected operand " + param.second)); +} + +INSTANTIATE_TEST_SUITE_P( + AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugValue, + ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{ + std::make_pair(R"(%dbg_src %int_32 %null_expr)", "Local Variable"), + std::make_pair(R"(%foo_info %int_32 %dbg_src)", "Expression"), + std::make_pair(R"(%foo_info %int_32 %null_expr %dbg_src)", "Indexes"), + })); + TEST_P(ValidateGlslStd450SqrtLike, IntResultType) { const std::string ext_inst_name = GetParam(); const std::string body = @@ -7979,6 +8635,703 @@ INSTANTIATE_TEST_SUITE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike, "s_upsample", })); +TEST_F(ValidateClspvReflection, RequiresNonSemanticExtension) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NonSemantic extended instruction sets cannot be " + "declared without SPV_KHR_non_semantic_info")); +} + +TEST_F(ValidateClspvReflection, MissingVersion) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.ClspvReflection." +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1 +%5 = OpExtInst %2 %1 SpecConstantWorkDim %4 +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Missing NonSemantic.ClspvReflection import version")); +} + +TEST_F(ValidateClspvReflection, BadVersion0) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.ClspvReflection.0" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1 +%5 = OpExtInst %2 %1 SpecConstantWorkDim %4 +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Unknown NonSemantic.ClspvReflection import version")); +} + +TEST_F(ValidateClspvReflection, BadVersionNotANumber) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.ClspvReflection.1a" +OpMemoryModel Logical GLSL450 +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1 +%5 = OpExtInst %2 %1 SpecConstantWorkDim %4 +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NonSemantic.ClspvReflection import does not encode " + "the version correctly")); +} + +TEST_F(ValidateClspvReflection, Kernel) { + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateClspvReflection, KernelNotAFunction) { + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo_name %foo_name +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Kernel does not reference a function")); +} + +TEST_F(ValidateClspvReflection, KernelNotAnEntryPoint) { + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%bar = OpFunction %void None %void_fn +%bar_entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %bar %foo_name +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Kernel does not reference an entry-point")); +} + +TEST_F(ValidateClspvReflection, KernelNotGLCompute) { + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %foo "foo" +OpExecutionMode %foo OriginUpperLeft +%foo_name = OpString "foo" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Kernel must refer only to GLCompute entry-points")); +} + +TEST_F(ValidateClspvReflection, KernelNameMismatch) { + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "bar" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +)"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Name must match an entry-point for Kernel")); +} + +using ArgumentBasics = + spvtest::ValidateBase<std::pair<std::string, std::string>>; + +INSTANTIATE_TEST_SUITE_P( + ValidateClspvReflectionArgumentKernel, ArgumentBasics, + ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{ + std::make_pair("ArgumentStorageBuffer", "%int_0 %int_0"), + std::make_pair("ArgumentUniform", "%int_0 %int_0"), + std::make_pair("ArgumentPodStorageBuffer", + "%int_0 %int_0 %int_0 %int_4"), + std::make_pair("ArgumentPodUniform", "%int_0 %int_0 %int_0 %int_4"), + std::make_pair("ArgumentPodPushConstant", "%int_0 %int_4"), + std::make_pair("ArgumentSampledImage", "%int_0 %int_0"), + std::make_pair("ArgumentStorageImage", "%int_0 %int_0"), + std::make_pair("ArgumentSampler", "%int_0 %int_0"), + std::make_pair("ArgumentWorkgroup", "%int_0 %int_0")})); + +TEST_P(ArgumentBasics, KernelNotAnExtendedInstruction) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string extra = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%in = OpExtInst %void %ext )" + + ext_inst + " %int_0 %int_0 " + extra; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Kernel must be a Kernel extended instruction")); +} + +TEST_P(ArgumentBasics, KernelFromDifferentImport) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string extra = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +%ext2 = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext2 Kernel %foo %foo_name +%in = OpExtInst %void %ext )" + + ext_inst + " %decl %int_0 " + extra; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Kernel must be from the same extended instruction import")); +} + +TEST_P(ArgumentBasics, KernelWrongExtendedInstruction) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string extra = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext ArgumentInfo %foo_name +%in = OpExtInst %void %ext )" + + ext_inst + " %decl %int_0 " + extra; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Kernel must be a Kernel extended instruction")); +} + +TEST_P(ArgumentBasics, ArgumentInfo) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string operands = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%in_name = OpString "in" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +%info = OpExtInst %void %ext ArgumentInfo %in_name +%in = OpExtInst %void %ext )" + + ext_inst + " %decl %int_0 " + operands + " %info"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ArgumentBasics, ArgumentInfoNotAnExtendedInstruction) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string operands = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +%in = OpExtInst %void %ext )" + + ext_inst + " %decl %int_0 " + operands + " %int_0"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ArgInfo must be an ArgumentInfo extended instruction")); +} + +TEST_P(ArgumentBasics, ArgumentInfoFromDifferentImport) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string operands = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +%ext2 = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%in_name = OpString "in" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +%info = OpExtInst %void %ext2 ArgumentInfo %in_name +%in = OpExtInst %void %ext )" + + ext_inst + " %decl %int_0 " + operands + " %info"; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ArgInfo must be from the same extended instruction import")); +} + +using Uint32Constant = + spvtest::ValidateBase<std::pair<std::string, std::string>>; + +INSTANTIATE_TEST_SUITE_P( + ValidateClspvReflectionUint32Constants, Uint32Constant, + ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{ + std::make_pair("ArgumentStorageBuffer %decl %float_0 %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentStorageBuffer %decl %null %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentStorageBuffer %decl %int_0 %float_0 %int_0", + "DescriptorSet"), + std::make_pair("ArgumentStorageBuffer %decl %int_0 %null %int_0", + "DescriptorSet"), + std::make_pair("ArgumentStorageBuffer %decl %int_0 %int_0 %float_0", + "Binding"), + std::make_pair("ArgumentStorageBuffer %decl %int_0 %int_0 %null", + "Binding"), + std::make_pair("ArgumentUniform %decl %float_0 %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentUniform %decl %null %int_0 %int_0", "Ordinal"), + std::make_pair("ArgumentUniform %decl %int_0 %float_0 %int_0", + "DescriptorSet"), + std::make_pair("ArgumentUniform %decl %int_0 %null %int_0", + "DescriptorSet"), + std::make_pair("ArgumentUniform %decl %int_0 %int_0 %float_0", + "Binding"), + std::make_pair("ArgumentUniform %decl %int_0 %int_0 %null", "Binding"), + std::make_pair("ArgumentSampledImage %decl %float_0 %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentSampledImage %decl %null %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentSampledImage %decl %int_0 %float_0 %int_0", + "DescriptorSet"), + std::make_pair("ArgumentSampledImage %decl %int_0 %null %int_0", + "DescriptorSet"), + std::make_pair("ArgumentSampledImage %decl %int_0 %int_0 %float_0", + "Binding"), + std::make_pair("ArgumentSampledImage %decl %int_0 %int_0 %null", + "Binding"), + std::make_pair("ArgumentStorageImage %decl %float_0 %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentStorageImage %decl %null %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentStorageImage %decl %int_0 %float_0 %int_0", + "DescriptorSet"), + std::make_pair("ArgumentStorageImage %decl %int_0 %null %int_0", + "DescriptorSet"), + std::make_pair("ArgumentStorageImage %decl %int_0 %int_0 %float_0", + "Binding"), + std::make_pair("ArgumentStorageImage %decl %int_0 %int_0 %null", + "Binding"), + std::make_pair("ArgumentSampler %decl %float_0 %int_0 %int_0", + "Ordinal"), + std::make_pair("ArgumentSampler %decl %null %int_0 %int_0", "Ordinal"), + std::make_pair("ArgumentSampler %decl %int_0 %float_0 %int_0", + "DescriptorSet"), + std::make_pair("ArgumentSampler %decl %int_0 %null %int_0", + "DescriptorSet"), + std::make_pair("ArgumentSampler %decl %int_0 %int_0 %float_0", + "Binding"), + std::make_pair("ArgumentSampler %decl %int_0 %int_0 %null", "Binding"), + std::make_pair("ArgumentPodStorageBuffer %decl %float_0 %int_0 %int_0 " + "%int_0 %int_4", + "Ordinal"), + std::make_pair( + "ArgumentPodStorageBuffer %decl %null %int_0 %int_0 %int_0 %int_4", + "Ordinal"), + std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %float_0 %int_0 " + "%int_0 %int_4", + "DescriptorSet"), + std::make_pair( + "ArgumentPodStorageBuffer %decl %int_0 %null %int_0 %int_0 %int_4", + "DescriptorSet"), + std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %float_0 " + "%int_0 %int_4", + "Binding"), + std::make_pair( + "ArgumentPodStorageBuffer %decl %int_0 %int_0 %null %int_0 %int_4", + "Binding"), + std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 " + "%float_0 %int_4", + "Offset"), + std::make_pair( + "ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 %null %int_4", + "Offset"), + std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 " + "%int_0 %float_0", + "Size"), + std::make_pair( + "ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 %int_0 %null", + "Size"), + std::make_pair( + "ArgumentPodUniform %decl %float_0 %int_0 %int_0 %int_0 %int_4", + "Ordinal"), + std::make_pair( + "ArgumentPodUniform %decl %null %int_0 %int_0 %int_0 %int_4", + "Ordinal"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %float_0 %int_0 %int_0 %int_4", + "DescriptorSet"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %null %int_0 %int_0 %int_4", + "DescriptorSet"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %float_0 %int_0 %int_4", + "Binding"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %null %int_0 %int_4", + "Binding"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %float_0 %int_4", + "Offset"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %null %int_4", + "Offset"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %int_0 %float_0", + "Size"), + std::make_pair( + "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %int_0 %null", + "Size"), + std::make_pair("ArgumentPodPushConstant %decl %float_0 %int_0 %int_4", + "Ordinal"), + std::make_pair("ArgumentPodPushConstant %decl %null %int_0 %int_4", + "Ordinal"), + std::make_pair("ArgumentPodPushConstant %decl %int_0 %float_0 %int_4", + "Offset"), + std::make_pair("ArgumentPodPushConstant %decl %int_0 %null %int_4", + "Offset"), + std::make_pair("ArgumentPodPushConstant %decl %int_0 %int_0 %float_0", + "Size"), + std::make_pair("ArgumentPodPushConstant %decl %int_0 %int_0 %null", + "Size"), + std::make_pair("ArgumentWorkgroup %decl %float_0 %int_0 %int_4", + "Ordinal"), + std::make_pair("ArgumentWorkgroup %decl %null %int_0 %int_4", + "Ordinal"), + std::make_pair("ArgumentWorkgroup %decl %int_0 %float_0 %int_4", + "SpecId"), + std::make_pair("ArgumentWorkgroup %decl %int_0 %null %int_4", "SpecId"), + std::make_pair("ArgumentWorkgroup %decl %int_0 %int_0 %float_0", + "ElemSize"), + std::make_pair("ArgumentWorkgroup %decl %int_0 %int_0 %null", + "ElemSize"), + std::make_pair("SpecConstantWorkgroupSize %float_0 %int_0 %int_4", "X"), + std::make_pair("SpecConstantWorkgroupSize %null %int_0 %int_4", "X"), + std::make_pair("SpecConstantWorkgroupSize %int_0 %float_0 %int_4", "Y"), + std::make_pair("SpecConstantWorkgroupSize %int_0 %null %int_4", "Y"), + std::make_pair("SpecConstantWorkgroupSize %int_0 %int_0 %float_0", "Z"), + std::make_pair("SpecConstantWorkgroupSize %int_0 %int_0 %null", "Z"), + std::make_pair("SpecConstantGlobalOffset %float_0 %int_0 %int_4", "X"), + std::make_pair("SpecConstantGlobalOffset %null %int_0 %int_4", "X"), + std::make_pair("SpecConstantGlobalOffset %int_0 %float_0 %int_4", "Y"), + std::make_pair("SpecConstantGlobalOffset %int_0 %null %int_4", "Y"), + std::make_pair("SpecConstantGlobalOffset %int_0 %int_0 %float_0", "Z"), + std::make_pair("SpecConstantGlobalOffset %int_0 %int_0 %null", "Z"), + std::make_pair("SpecConstantWorkDim %float_0", "Dim"), + std::make_pair("SpecConstantWorkDim %null", "Dim"), + std::make_pair("PushConstantGlobalOffset %float_0 %int_0", "Offset"), + std::make_pair("PushConstantGlobalOffset %null %int_0", "Offset"), + std::make_pair("PushConstantGlobalOffset %int_0 %float_0", "Size"), + std::make_pair("PushConstantGlobalOffset %int_0 %null", "Size"), + std::make_pair("PushConstantEnqueuedLocalSize %float_0 %int_0", + "Offset"), + std::make_pair("PushConstantEnqueuedLocalSize %null %int_0", "Offset"), + std::make_pair("PushConstantEnqueuedLocalSize %int_0 %float_0", "Size"), + std::make_pair("PushConstantEnqueuedLocalSize %int_0 %null", "Size"), + std::make_pair("PushConstantGlobalSize %float_0 %int_0", "Offset"), + std::make_pair("PushConstantGlobalSize %null %int_0", "Offset"), + std::make_pair("PushConstantGlobalSize %int_0 %float_0", "Size"), + std::make_pair("PushConstantGlobalSize %int_0 %null", "Size"), + std::make_pair("PushConstantRegionOffset %float_0 %int_0", "Offset"), + std::make_pair("PushConstantRegionOffset %null %int_0", "Offset"), + std::make_pair("PushConstantRegionOffset %int_0 %float_0", "Size"), + std::make_pair("PushConstantRegionOffset %int_0 %null", "Size"), + std::make_pair("PushConstantNumWorkgroups %float_0 %int_0", "Offset"), + std::make_pair("PushConstantNumWorkgroups %null %int_0", "Offset"), + std::make_pair("PushConstantNumWorkgroups %int_0 %float_0", "Size"), + std::make_pair("PushConstantNumWorkgroups %int_0 %null", "Size"), + std::make_pair("PushConstantRegionGroupOffset %float_0 %int_0", + "Offset"), + std::make_pair("PushConstantRegionGroupOffset %null %int_0", "Offset"), + std::make_pair("PushConstantRegionGroupOffset %int_0 %float_0", "Size"), + std::make_pair("PushConstantRegionGroupOffset %int_0 %null", "Size"), + std::make_pair("ConstantDataStorageBuffer %float_0 %int_0 %data", + "DescriptorSet"), + std::make_pair("ConstantDataStorageBuffer %null %int_0 %data", + "DescriptorSet"), + std::make_pair("ConstantDataStorageBuffer %int_0 %float_0 %data", + "Binding"), + std::make_pair("ConstantDataStorageBuffer %int_0 %null %data", + "Binding"), + std::make_pair("ConstantDataUniform %float_0 %int_0 %data", + "DescriptorSet"), + std::make_pair("ConstantDataUniform %null %int_0 %data", + "DescriptorSet"), + std::make_pair("ConstantDataUniform %int_0 %float_0 %data", "Binding"), + std::make_pair("ConstantDataUniform %int_0 %null %data", "Binding"), + std::make_pair("LiteralSampler %float_0 %int_0 %int_4", + "DescriptorSet"), + std::make_pair("LiteralSampler %null %int_0 %int_4", "DescriptorSet"), + std::make_pair("LiteralSampler %int_0 %float_0 %int_4", "Binding"), + std::make_pair("LiteralSampler %int_0 %null %int_4", "Binding"), + std::make_pair("LiteralSampler %int_0 %int_0 %float_0", "Mask"), + std::make_pair("LiteralSampler %int_0 %int_0 %null", "Mask"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %float_0 %int_1 %int_4", "X"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %null %int_1 %int_4", "X"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %int_1 %float_0 %int_4", "Y"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %int_1 %null %int_4", "Y"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %int_1 %int_1 %float_0", "Z"), + std::make_pair( + "PropertyRequiredWorkgroupSize %decl %int_1 %int_1 %null", "Z")})); + +TEST_P(Uint32Constant, Invalid) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string name = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%data = OpString "1234" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_4 = OpConstant %int 4 +%null = OpConstantNull %int +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +%inst = OpExtInst %void %ext )" + + ext_inst; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr(name + " must be a 32-bit unsigned integer OpConstant")); +} + +using StringOperand = + spvtest::ValidateBase<std::pair<std::string, std::string>>; + +INSTANTIATE_TEST_SUITE_P( + ValidateClspvReflectionStringOperands, StringOperand, + ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{ + std::make_pair("ConstantDataStorageBuffer %int_0 %int_0 %int_0", + "Data"), + std::make_pair("ConstantDataUniform %int_0 %int_0 %int_0", "Data")})); + +TEST_P(StringOperand, Invalid) { + const std::string ext_inst = std::get<0>(GetParam()); + const std::string name = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%ext = OpExtInstImport "NonSemantic.ClspvReflection.1" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %foo "foo" +OpExecutionMode %foo LocalSize 1 1 1 +%foo_name = OpString "foo" +%data = OpString "1234" +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_4 = OpConstant %int 4 +%null = OpConstantNull %int +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +%decl = OpExtInst %void %ext Kernel %foo %foo_name +%inst = OpExtInst %void %ext )" + + ext_inst; + + CompileSuccessfully(text); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(name + " must be an OpString")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp index f2fb45a2..6869e794 100644 --- a/test/val/val_interfaces_test.cpp +++ b/test/val/val_interfaces_test.cpp @@ -1220,9 +1220,11 @@ OpFunctionEnd TEST_F(ValidateInterfacesTest, VulkanLocationsIndexGLCompute) { const std::string text = R"( OpCapability Shader +OpCapability Geometry OpMemoryModel Logical GLSL450 -OpEntryPoint GLCompute %main "main" %var1 -OpExecutionMode %main LocalSize 1 1 1 +OpEntryPoint Geometry %main "main" %var1 +OpExecutionMode %main Triangles +OpExecutionMode %main OutputPoints OpDecorate %var1 Location 1 OpDecorate %var1 Index 1 %void = OpTypeVoid @@ -1379,6 +1381,35 @@ TEST_F(ValidateInterfacesTest, VulkanLocationsLargeLocation) { EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); } +TEST_F(ValidateInterfacesTest, VulkanLocationMeshShader) { + const std::string text = R"( +OpCapability Shader +OpCapability MeshShadingNV +OpExtension "SPV_NV_mesh_shader" +OpMemoryModel Logical GLSL450 +OpEntryPoint MeshNV %foo "foo" %in +OpExecutionMode %foo LocalSize 1 1 1 +OpDecorate %block Block +OpMemberDecorate %block 0 PerTaskNV +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_32 = OpConstant %int 32 +%array = OpTypeArray %int %int_32 +%block = OpTypeStruct %array +%ptr_input_block = OpTypePointer Input %block +%in = OpVariable %ptr_input_block Input +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_primitives_test.cpp b/test/val/val_primitives_test.cpp index 04d0a4f8..8a617230 100644 --- a/test/val/val_primitives_test.cpp +++ b/test/val/val_primitives_test.cpp @@ -77,7 +77,7 @@ OpFunctionEnd)"; std::string CallAndCallee(const std::string& body) { std::ostringstream ss; ss << R"( -%dummy = OpFunctionCall %void %foo +%placeholder = OpFunctionCall %void %foo OpReturn OpFunctionEnd diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index b3a4cc1a..67d606a8 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -40,19 +40,19 @@ function(add_spvtools_tool) endfunction() if (NOT ${SPIRV_SKIP_EXECUTABLES}) - add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS}) - add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS}) - add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS}) - add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS}-static) + add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS}-static) + add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS}-static) + add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}-static) if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires - add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS}-static) endif() - add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS}-static) add_spvtools_tool(TARGET spirv-cfg SRCS cfg/cfg.cpp cfg/bin_to_dot.h cfg/bin_to_dot.cpp - LIBS ${SPIRV_TOOLS}) + LIBS ${SPIRV_TOOLS}-static) target_include_directories(spirv-cfg PRIVATE ${spirv-tools_SOURCE_DIR} ${SPIRV_HEADER_INCLUDE_DIR}) set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt @@ -62,7 +62,7 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES}) endif() if(SPIRV_BUILD_FUZZER) - add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS}-static) set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-fuzz) endif(SPIRV_BUILD_FUZZER) diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp index 61064d1e..80ac9f51 100644 --- a/tools/fuzz/fuzz.cpp +++ b/tools/fuzz/fuzz.cpp @@ -16,7 +16,7 @@ #include <cerrno> #include <cstring> #include <fstream> -#include <functional> +#include <memory> #include <random> #include <sstream> #include <string> @@ -25,12 +25,14 @@ #include "source/fuzz/fuzzer.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/pseudo_random_generator.h" #include "source/fuzz/replayer.h" #include "source/fuzz/shrinker.h" #include "source/opt/build_module.h" #include "source/opt/ir_context.h" #include "source/opt/log.h" #include "source/spirv_fuzzer_options.h" +#include "source/util/make_unique.h" #include "source/util/string_utils.h" #include "tools/io.h" #include "tools/util/cli_consumer.h" @@ -107,6 +109,14 @@ Options (in lexicographical order): provided if the tool is invoked in fuzzing mode; incompatible with replay and shrink modes. The file should be empty if no donors are to be used. + --enable-all-passes + By default, spirv-fuzz follows the philosophy of "swarm testing" + (Groce et al., 2012): only a subset of fuzzer passes are enabled + on any given fuzzer run, with the subset being chosen randomly. + This flag instead forces *all* fuzzer passes to be enabled. When + running spirv-fuzz many times this is likely to produce *less* + diverse fuzzed modules than when swarm testing is used. The + purpose of the flag is to allow that hypothesis to be tested. --force-render-red Transforms the input shader into a shader that writes red to the output buffer, and then captures the original shader as the body @@ -118,6 +128,19 @@ Options (in lexicographical order): Run the validator after applying each fuzzer pass during fuzzing. Aborts fuzzing early if an invalid binary is created. Useful for debugging spirv-fuzz. + --repeated-pass-strategy= + Available strategies are: + - looped (the default): a sequence of fuzzer passes is chosen at + the start of fuzzing, via randomly choosing enabled passes, and + augmenting these choices with fuzzer passes that it is + recommended to run subsequently. Fuzzing then involves + repeatedly applying this fixed sequence of passes. + - random: each time a fuzzer pass is requested, this strategy + either provides one at random from the set of enabled passes, + or provides a pass that has been recommended based on a pass + that was used previously. + - simple: each time a fuzzer pass is requested, one is provided + at random from the set of enabled passes. --replay File from which to read a sequence of transformations to replay (instead of fuzzing) @@ -174,18 +197,23 @@ void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/, fprintf(stderr, "%s\n", message); } -FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, - std::string* out_binary_file, std::string* donors_file, - std::string* replay_transformations_file, - std::vector<std::string>* interestingness_test, - std::string* shrink_transformations_file, - std::string* shrink_temp_file_prefix, - spvtools::FuzzerOptions* fuzzer_options, - spvtools::ValidatorOptions* validator_options) { +FuzzStatus ParseFlags( + int argc, const char** argv, std::string* in_binary_file, + std::string* out_binary_file, std::string* donors_file, + std::string* replay_transformations_file, + std::vector<std::string>* interestingness_test, + std::string* shrink_transformations_file, + std::string* shrink_temp_file_prefix, + spvtools::fuzz::Fuzzer::RepeatedPassStrategy* repeated_pass_strategy, + spvtools::FuzzerOptions* fuzzer_options, + spvtools::ValidatorOptions* validator_options) { uint32_t positional_arg_index = 0; bool only_positional_arguments_remain = false; bool force_render_red = false; + *repeated_pass_strategy = + spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations; + for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; if ('-' == cur_arg[0] && !only_positional_arguments_remain) { @@ -206,6 +234,9 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); *donors_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--enable-all-passes", + sizeof("--enable-all-passes") - 1)) { + fuzzer_options->enable_all_passes(); } else if (0 == strncmp(cur_arg, "--force-render-red", sizeof("--force-render-red") - 1)) { force_render_red = true; @@ -215,6 +246,26 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); *replay_transformations_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--repeated-pass-strategy=", + sizeof("--repeated-pass-strategy=") - 1)) { + std::string strategy = spvtools::utils::SplitFlagArgs(cur_arg).second; + if (strategy == "looped") { + *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: + RepeatedPassStrategy::kLoopedWithRecommendations; + } else if (strategy == "random") { + *repeated_pass_strategy = spvtools::fuzz::Fuzzer:: + RepeatedPassStrategy::kRandomWithRecommendations; + } else if (strategy == "simple") { + *repeated_pass_strategy = + spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kSimple; + } else { + std::stringstream ss; + ss << "Unknown repeated pass strategy '" << strategy << "'" + << std::endl; + ss << "Valid options are 'looped', 'random' and 'simple'."; + spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str()); + return {FuzzActions::STOP, 1}; + } } else if (0 == strncmp(cur_arg, "--replay-range=", sizeof("--replay-range=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); @@ -405,9 +456,6 @@ bool Replay(const spv_target_env& target_env, &transformation_sequence)) { return false; } - spvtools::fuzz::Replayer replayer( - target_env, fuzzer_options->replay_validation_enabled, validator_options); - replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); uint32_t num_transformations_to_apply; if (fuzzer_options->replay_range > 0) { @@ -426,11 +474,17 @@ bool Replay(const spv_target_env& target_env, fuzzer_options->replay_range)); } - auto replay_result_status = replayer.Run( - binary_in, initial_facts, transformation_sequence, - num_transformations_to_apply, binary_out, transformations_applied); - return !(replay_result_status != - spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete); + auto replay_result = + spvtools::fuzz::Replayer( + target_env, spvtools::utils::CLIMessageConsumer, binary_in, + initial_facts, transformation_sequence, num_transformations_to_apply, + 0, fuzzer_options->replay_validation_enabled, validator_options) + .Run(); + + *binary_out = std::move(replay_result.transformed_binary); + *transformations_applied = std::move(replay_result.applied_transformations); + return replay_result.status == + spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete; } bool Shrink(const spv_target_env& target_env, @@ -449,11 +503,6 @@ bool Shrink(const spv_target_env& target_env, &transformation_sequence)) { return false; } - spvtools::fuzz::Shrinker shrinker( - target_env, fuzzer_options->shrinker_step_limit, - fuzzer_options->replay_validation_enabled, validator_options); - shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); - assert(!interestingness_command.empty() && "An error should have been raised because the interestingness_command " "is empty."); @@ -479,13 +528,20 @@ bool Shrink(const spv_target_env& target_env, return ExecuteCommand(command); }; - auto shrink_result_status = shrinker.Run( - binary_in, initial_facts, transformation_sequence, - interestingness_function, binary_out, transformations_applied); + auto shrink_result = + spvtools::fuzz::Shrinker( + target_env, spvtools::utils::CLIMessageConsumer, binary_in, + initial_facts, transformation_sequence, interestingness_function, + fuzzer_options->shrinker_step_limit, + fuzzer_options->replay_validation_enabled, validator_options) + .Run(); + + *binary_out = std::move(shrink_result.transformed_binary); + *transformations_applied = std::move(shrink_result.applied_transformations); return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete == - shrink_result_status || + shrink_result.status || spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached == - shrink_result_status; + shrink_result.status; } bool Fuzz(const spv_target_env& target_env, @@ -493,7 +549,9 @@ bool Fuzz(const spv_target_env& target_env, spv_validator_options validator_options, const std::vector<uint32_t>& binary_in, const spvtools::fuzz::protobufs::FactSequence& initial_facts, - const std::string& donors, std::vector<uint32_t>* binary_out, + const std::string& donors, + spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy, + std::vector<uint32_t>* binary_out, spvtools::fuzz::protobufs::TransformationSequence* transformations_applied) { auto message_consumer = spvtools::utils::CLIMessageConsumer; @@ -521,17 +579,20 @@ bool Fuzz(const spv_target_env& target_env, }); } - spvtools::fuzz::Fuzzer fuzzer( - target_env, - fuzzer_options->has_random_seed - ? fuzzer_options->random_seed - : static_cast<uint32_t>(std::random_device()()), - fuzzer_options->fuzzer_pass_validation_enabled, validator_options); - fuzzer.SetMessageConsumer(message_consumer); - auto fuzz_result_status = - fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out, - transformations_applied); - if (fuzz_result_status != + auto fuzz_result = + spvtools::fuzz::Fuzzer( + target_env, message_consumer, binary_in, initial_facts, + donor_suppliers, + spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>( + fuzzer_options->has_random_seed + ? fuzzer_options->random_seed + : static_cast<uint32_t>(std::random_device()())), + fuzzer_options->all_passes_enabled, repeated_pass_strategy, + fuzzer_options->fuzzer_pass_validation_enabled, validator_options) + .Run(); + *binary_out = std::move(fuzz_result.transformed_binary); + *transformations_applied = std::move(fuzz_result.applied_transformations); + if (fuzz_result.status != spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) { spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer"); return false; @@ -568,6 +629,7 @@ int main(int argc, const char** argv) { std::vector<std::string> interestingness_test; std::string shrink_transformations_file; std::string shrink_temp_file_prefix = "temp_"; + spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy; spvtools::FuzzerOptions fuzzer_options; spvtools::ValidatorOptions validator_options; @@ -576,7 +638,7 @@ int main(int argc, const char** argv) { ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file, &replay_transformations_file, &interestingness_test, &shrink_transformations_file, &shrink_temp_file_prefix, - &fuzzer_options, &validator_options); + &repeated_pass_strategy, &fuzzer_options, &validator_options); if (status.action == FuzzActions::STOP) { return status.code; @@ -622,7 +684,7 @@ int main(int argc, const char** argv) { break; case FuzzActions::FUZZ: if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in, - initial_facts, donors_file, &binary_out, + initial_facts, donors_file, repeated_pass_strategy, &binary_out, &transformations_applied)) { return 1; } @@ -17,6 +17,7 @@ #include <cstdint> #include <cstdio> +#include <cstring> #include <vector> // Appends the content from the file named as |filename| to |data|, assuming diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp index 0bdeb82a..49a5efef 100644 --- a/tools/reduce/reduce.cpp +++ b/tools/reduce/reduce.cpp @@ -16,6 +16,7 @@ #include <cerrno> #include <cstring> #include <functional> +#include <sstream> #include "source/opt/build_module.h" #include "source/opt/ir_context.h" @@ -102,6 +103,14 @@ Options (in lexicographical order): --step-limit= 32-bit unsigned integer specifying maximum number of steps the reducer will take before giving up. + --target-function= + 32-bit unsigned integer specifying the id of a function in the + input module. The reducer will restrict attention to this + function, and will not make changes to other functions or to + instructions outside of functions, except that some global + instructions may be added in support of reducing the target + function. If 0 is specified (the default) then all functions are + reduced. --temp-file-prefix= Specifies a temporary file prefix that will be used to output temporary shader files during reduction. A number and .spv @@ -169,6 +178,15 @@ ReduceStatus ParseFlags(int argc, const char** argv, static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10)); assert(end != split_flag.second.c_str() && errno == 0); reducer_options->set_step_limit(step_limit); + } else if (0 == strncmp(cur_arg, "--target-function=", + sizeof("--target-function=") - 1)) { + const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); + char* end = nullptr; + errno = 0; + const auto target_function = + static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10)); + assert(end != split_flag.second.c_str() && errno == 0); + reducer_options->set_target_function(target_function); } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) { reducer_options->set_fail_on_validation_error(true); } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) { @@ -304,6 +322,29 @@ int main(int argc, const char** argv) { return 1; } + const uint32_t target_function = (*reducer_options).target_function; + if (target_function) { + // A target function was specified; check that it exists. + std::unique_ptr<spvtools::opt::IRContext> context = spvtools::BuildModule( + kDefaultEnvironment, spvtools::utils::CLIMessageConsumer, + binary_in.data(), binary_in.size()); + bool found_target_function = false; + for (auto& function : *context->module()) { + if (function.result_id() == target_function) { + found_target_function = true; + break; + } + } + if (!found_target_function) { + std::stringstream strstr; + strstr << "Target function with id " << target_function + << " was requested, but not found in the module; stopping."; + spvtools::utils::CLIMessageConsumer(SPV_MSG_ERROR, nullptr, {}, + strstr.str().c_str()); + return 1; + } + } + std::vector<uint32_t> binary_out; const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out, reducer_options, validator_options); diff --git a/tools/sva/yarn.lock b/tools/sva/yarn.lock index 11ba12f7..34a1808e 100644 --- a/tools/sva/yarn.lock +++ b/tools/sva/yarn.lock @@ -938,9 +938,9 @@ locate-path@^3.0.0: path-exists "^3.0.0" lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@2.2.0: version "2.2.0" diff --git a/utils/roll_deps.sh b/utils/roll_deps.sh index d5c547fe..7ecfdd3b 100755 --- a/utils/roll_deps.sh +++ b/utils/roll_deps.sh @@ -17,13 +17,13 @@ # # Depends on roll-dep from depot_path being in PATH. -effcee_dir="third_party/effcee/" +effcee_dir="external/effcee/" effcee_trunk="origin/main" -googletest_dir="third_party/googletest/" +googletest_dir="external/googletest/" googletest_trunk="origin/master" -re2_dir="third_party/re2/" +re2_dir="external/re2/" re2_trunk="origin/master" -spirv_headers_dir="third_party/spirv-headers/" +spirv_headers_dir="external/spirv-headers/" spirv_headers_trunk="origin/master" # This script assumes it's parent directory is the repo root. diff --git a/utils/vscode/src/parser/parser.go b/utils/vscode/src/parser/parser.go index 1775b0f1..260a616c 100644 --- a/utils/vscode/src/parser/parser.go +++ b/utils/vscode/src/parser/parser.go @@ -356,7 +356,7 @@ func lex(source string) ([]*Token, []Diagnostic, error) { lastPos := Position{} for l.e == nil { - // Sanity check the parser is making progress + // Integrity check that the parser is making progress if l.pos == lastPos { log.Panicf("Parsing stuck at %v", l.pos) } |