aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Neto <dneto@google.com>2020-09-28 10:29:14 -0400
committerDavid Neto <dneto@google.com>2020-09-28 10:29:14 -0400
commit59c2b3935ce57a20a9cc0cc5e0a2f5b0075ba9fe (patch)
tree9a4b3ff0c457ea62961e72c112586bfe347e10ff
parenta38c7b3dd8657c6fc3fe0a19f1973a7f7813606b (diff)
parentb27e039c68dd9eb959bb9249fcb2c9a54841474b (diff)
downloadspirv-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
-rw-r--r--Android.mk14
-rw-r--r--BUILD.bazel3
-rw-r--r--BUILD.gn24
-rw-r--r--CHANGES55
-rw-r--r--CMakeLists.txt2
-rw-r--r--DEPS8
-rw-r--r--README.md5
-rw-r--r--build_defs.bzl6
-rw-r--r--docs/syntax.md4
-rw-r--r--external/CMakeLists.txt66
-rw-r--r--include/spirv-tools/instrument.hpp26
-rw-r--r--include/spirv-tools/libspirv.h32
-rw-r--r--include/spirv-tools/libspirv.hpp8
-rw-r--r--include/spirv-tools/optimizer.hpp2
-rw-r--r--kokoro/scripts/linux/build.sh8
-rw-r--r--kokoro/scripts/macos/build.sh6
-rw-r--r--kokoro/scripts/windows/build.bat6
-rw-r--r--source/CMakeLists.txt38
-rw-r--r--source/binary.cpp8
-rw-r--r--source/cfa.h2
-rw-r--r--source/ext_inst.cpp10
-rw-r--r--source/extinst.debuginfo.grammar.json568
-rw-r--r--source/extinst.opencl.debuginfo.100.grammar.json632
-rw-r--r--source/extinst.spv-amd-gcn-shader.grammar.json26
-rw-r--r--source/extinst.spv-amd-shader-ballot.grammar.json41
-rw-r--r--source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json14
-rw-r--r--source/extinst.spv-amd-shader-trinary-minmax.grammar.json95
-rw-r--r--source/fuzz/CMakeLists.txt128
-rw-r--r--source/fuzz/call_graph.cpp104
-rw-r--r--source/fuzz/call_graph.h48
-rw-r--r--source/fuzz/comparator_deep_blocks_first.h53
-rw-r--r--source/fuzz/counter_overflow_id_source.cpp30
-rw-r--r--source/fuzz/counter_overflow_id_source.h45
-rw-r--r--source/fuzz/data_descriptor.h4
-rw-r--r--source/fuzz/fact_manager.cpp1561
-rw-r--r--source/fuzz/fact_manager/constant_uniform_facts.cpp234
-rw-r--r--source/fuzz/fact_manager/constant_uniform_facts.h90
-rw-r--r--source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp892
-rw-r--r--source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h173
-rw-r--r--source/fuzz/fact_manager/dead_block_facts.cpp35
-rw-r--r--source/fuzz/fact_manager/dead_block_facts.h47
-rw-r--r--source/fuzz/fact_manager/fact_manager.cpp249
-rw-r--r--source/fuzz/fact_manager/fact_manager.h (renamed from source/fuzz/fact_manager.h)81
-rw-r--r--source/fuzz/fact_manager/irrelevant_value_facts.cpp117
-rw-r--r--source/fuzz/fact_manager/irrelevant_value_facts.h77
-rw-r--r--source/fuzz/fact_manager/livesafe_function_facts.cpp32
-rw-r--r--source/fuzz/fact_manager/livesafe_function_facts.h44
-rw-r--r--source/fuzz/force_render_red.cpp14
-rw-r--r--source/fuzz/fuzzer.cpp489
-rw-r--r--source/fuzz/fuzzer.h140
-rw-r--r--source/fuzz/fuzzer_context.cpp97
-rw-r--r--source/fuzz/fuzzer_context.h111
-rw-r--r--source/fuzz/fuzzer_pass.cpp224
-rw-r--r--source/fuzz/fuzzer_pass.h68
-rw-r--r--source/fuzz/fuzzer_pass_add_access_chains.cpp38
-rw-r--r--source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp84
-rw-r--r--source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h41
-rw-r--r--source/fuzz/fuzzer_pass_add_composite_inserts.cpp235
-rw-r--r--source/fuzz/fuzzer_pass_add_composite_inserts.h45
-rw-r--r--source/fuzz/fuzzer_pass_add_composite_types.cpp9
-rw-r--r--source/fuzz/fuzzer_pass_add_copy_memory.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_add_dead_blocks.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_add_dead_breaks.cpp3
-rw-r--r--source/fuzz/fuzzer_pass_add_dead_continues.cpp3
-rw-r--r--source/fuzz/fuzzer_pass_add_equation_instructions.cpp108
-rw-r--r--source/fuzz/fuzzer_pass_add_equation_instructions.h8
-rw-r--r--source/fuzz/fuzzer_pass_add_function_calls.cpp4
-rw-r--r--source/fuzz/fuzzer_pass_add_global_variables.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_add_local_variables.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_add_loop_preheaders.cpp66
-rw-r--r--source/fuzz/fuzzer_pass_add_loop_preheaders.h43
-rw-r--r--source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp247
-rw-r--r--source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h53
-rw-r--r--source/fuzz/fuzzer_pass_add_no_contraction_decorations.h6
-rw-r--r--source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp306
-rw-r--r--source/fuzz/fuzzer_pass_add_opphi_synonyms.h73
-rw-r--r--source/fuzz/fuzzer_pass_add_parameters.cpp62
-rw-r--r--source/fuzz/fuzzer_pass_add_relaxed_decorations.h6
-rw-r--r--source/fuzz/fuzzer_pass_add_synonyms.cpp14
-rw-r--r--source/fuzz/fuzzer_pass_adjust_function_controls.h6
-rw-r--r--source/fuzz/fuzzer_pass_adjust_loop_controls.h6
-rw-r--r--source/fuzz/fuzzer_pass_adjust_selection_controls.h6
-rw-r--r--source/fuzz/fuzzer_pass_apply_id_synonyms.cpp36
-rw-r--r--source/fuzz/fuzzer_pass_apply_id_synonyms.h17
-rw-r--r--source/fuzz/fuzzer_pass_construct_composites.h4
-rw-r--r--source/fuzz/fuzzer_pass_copy_objects.cpp20
-rw-r--r--source/fuzz/fuzzer_pass_donate_modules.cpp238
-rw-r--r--source/fuzz/fuzzer_pass_donate_modules.h22
-rw-r--r--source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp134
-rw-r--r--source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h42
-rw-r--r--source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp124
-rw-r--r--source/fuzz/fuzzer_pass_flatten_conditional_branches.h36
-rw-r--r--source/fuzz/fuzzer_pass_inline_functions.cpp104
-rw-r--r--source/fuzz/fuzzer_pass_inline_functions.h41
-rw-r--r--source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp156
-rw-r--r--source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h51
-rw-r--r--source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp30
-rw-r--r--source/fuzz/fuzzer_pass_interchange_zero_like_constants.h16
-rw-r--r--source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp71
-rw-r--r--source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h40
-rw-r--r--source/fuzz/fuzzer_pass_mutate_pointers.cpp74
-rw-r--r--source/fuzz/fuzzer_pass_mutate_pointers.h39
-rw-r--r--source/fuzz/fuzzer_pass_obfuscate_constants.cpp9
-rw-r--r--source/fuzz/fuzzer_pass_obfuscate_constants.h6
-rw-r--r--source/fuzz/fuzzer_pass_outline_functions.cpp107
-rw-r--r--source/fuzz/fuzzer_pass_outline_functions.h25
-rw-r--r--source/fuzz/fuzzer_pass_permute_blocks.h6
-rw-r--r--source/fuzz/fuzzer_pass_permute_function_parameters.cpp3
-rw-r--r--source/fuzz/fuzzer_pass_permute_instructions.cpp64
-rw-r--r--source/fuzz/fuzzer_pass_permute_instructions.h40
-rw-r--r--source/fuzz/fuzzer_pass_permute_phi_operands.cpp3
-rw-r--r--source/fuzz/fuzzer_pass_propagate_instructions_up.cpp64
-rw-r--r--source/fuzz/fuzzer_pass_propagate_instructions_up.h40
-rw-r--r--source/fuzz/fuzzer_pass_push_ids_through_variables.cpp10
-rw-r--r--source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp79
-rw-r--r--source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h42
-rw-r--r--source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp58
-rw-r--r--source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h41
-rw-r--r--source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp90
-rw-r--r--source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h41
-rw-r--r--source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp165
-rw-r--r--source/fuzz/fuzzer_pass_replace_irrelevant_ids.h39
-rw-r--r--source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp49
-rw-r--r--source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp105
-rw-r--r--source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h41
-rw-r--r--source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp117
-rw-r--r--source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h39
-rw-r--r--source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp162
-rw-r--r--source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h53
-rw-r--r--source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_replace_params_with_struct.cpp2
-rw-r--r--source/fuzz/fuzzer_pass_split_blocks.h6
-rw-r--r--source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp1
-rw-r--r--source/fuzz/fuzzer_util.cpp629
-rw-r--r--source/fuzz/fuzzer_util.h139
-rw-r--r--source/fuzz/id_use_descriptor.cpp2
-rw-r--r--source/fuzz/overflow_id_source.cpp23
-rw-r--r--source/fuzz/overflow_id_source.h106
-rw-r--r--source/fuzz/pass_management/repeated_pass_instances.h175
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager.cpp27
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager.h55
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp49
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h58
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp47
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h59
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_simple.cpp32
-rw-r--r--source/fuzz/pass_management/repeated_pass_manager_simple.h38
-rw-r--r--source/fuzz/pass_management/repeated_pass_recommender.cpp23
-rw-r--r--source/fuzz/pass_management/repeated_pass_recommender.h42
-rw-r--r--source/fuzz/pass_management/repeated_pass_recommender_standard.cpp334
-rw-r--r--source/fuzz/pass_management/repeated_pass_recommender_standard.h50
-rw-r--r--source/fuzz/protobufs/spirvfuzz_protobufs.h1
-rw-r--r--source/fuzz/protobufs/spvtoolsfuzz.proto620
-rw-r--r--source/fuzz/pseudo_random_generator.cpp8
-rw-r--r--source/fuzz/pseudo_random_generator.h2
-rw-r--r--source/fuzz/random_generator.h3
-rw-r--r--source/fuzz/replayer.cpp153
-rw-r--r--source/fuzz/replayer.h76
-rw-r--r--source/fuzz/shrinker.cpp196
-rw-r--r--source/fuzz/shrinker.h81
-rw-r--r--source/fuzz/transformation.cpp92
-rw-r--r--source/fuzz/transformation_access_chain.cpp258
-rw-r--r--source/fuzz/transformation_access_chain.h43
-rw-r--r--source/fuzz/transformation_add_bit_instruction_synonym.cpp228
-rw-r--r--source/fuzz/transformation_add_bit_instruction_synonym.h141
-rw-r--r--source/fuzz/transformation_add_constant_boolean.h2
-rw-r--r--source/fuzz/transformation_add_constant_composite.h2
-rw-r--r--source/fuzz/transformation_add_constant_scalar.cpp9
-rw-r--r--source/fuzz/transformation_add_constant_scalar.h2
-rw-r--r--source/fuzz/transformation_add_copy_memory.cpp8
-rw-r--r--source/fuzz/transformation_add_dead_block.cpp25
-rw-r--r--source/fuzz/transformation_add_dead_break.cpp5
-rw-r--r--source/fuzz/transformation_add_dead_continue.cpp6
-rw-r--r--source/fuzz/transformation_add_function.cpp122
-rw-r--r--source/fuzz/transformation_add_function.h13
-rw-r--r--source/fuzz/transformation_add_global_variable.cpp10
-rw-r--r--source/fuzz/transformation_add_local_variable.cpp3
-rw-r--r--source/fuzz/transformation_add_loop_preheader.cpp225
-rw-r--r--source/fuzz/transformation_add_loop_preheader.h57
-rw-r--r--source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp427
-rw-r--r--source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h69
-rw-r--r--source/fuzz/transformation_add_opphi_synonym.cpp197
-rw-r--r--source/fuzz/transformation_add_opphi_synonym.h72
-rw-r--r--source/fuzz/transformation_add_parameter.cpp168
-rw-r--r--source/fuzz/transformation_add_parameter.h17
-rw-r--r--source/fuzz/transformation_add_relaxed_decoration.h6
-rw-r--r--source/fuzz/transformation_add_spec_constant_op.cpp3
-rw-r--r--source/fuzz/transformation_add_synonym.cpp23
-rw-r--r--source/fuzz/transformation_add_type_float.cpp22
-rw-r--r--source/fuzz/transformation_add_type_int.cpp28
-rw-r--r--source/fuzz/transformation_add_type_struct.cpp8
-rw-r--r--source/fuzz/transformation_add_type_struct.h3
-rw-r--r--source/fuzz/transformation_composite_construct.cpp23
-rw-r--r--source/fuzz/transformation_composite_extract.cpp3
-rw-r--r--source/fuzz/transformation_composite_insert.cpp224
-rw-r--r--source/fuzz/transformation_composite_insert.h72
-rw-r--r--source/fuzz/transformation_compute_data_synonym_fact_closure.cpp4
-rw-r--r--source/fuzz/transformation_context.cpp33
-rw-r--r--source/fuzz/transformation_context.h22
-rw-r--r--source/fuzz/transformation_duplicate_region_with_selection.cpp593
-rw-r--r--source/fuzz/transformation_duplicate_region_with_selection.h78
-rw-r--r--source/fuzz/transformation_equation_instruction.cpp3
-rw-r--r--source/fuzz/transformation_flatten_conditional_branch.cpp703
-rw-r--r--source/fuzz/transformation_flatten_conditional_branch.h120
-rw-r--r--source/fuzz/transformation_inline_function.cpp296
-rw-r--r--source/fuzz/transformation_inline_function.h75
-rw-r--r--source/fuzz/transformation_make_vector_operation_dynamic.cpp111
-rw-r--r--source/fuzz/transformation_make_vector_operation_dynamic.h63
-rw-r--r--source/fuzz/transformation_move_instruction_down.cpp729
-rw-r--r--source/fuzz/transformation_move_instruction_down.h105
-rw-r--r--source/fuzz/transformation_mutate_pointer.cpp164
-rw-r--r--source/fuzz/transformation_mutate_pointer.h77
-rw-r--r--source/fuzz/transformation_outline_function.cpp95
-rw-r--r--source/fuzz/transformation_outline_function.h2
-rw-r--r--source/fuzz/transformation_permute_function_parameters.cpp54
-rw-r--r--source/fuzz/transformation_permute_phi_operands.cpp3
-rw-r--r--source/fuzz/transformation_propagate_instruction_up.cpp402
-rw-r--r--source/fuzz/transformation_propagate_instruction_up.h89
-rw-r--r--source/fuzz/transformation_push_id_through_variable.cpp21
-rw-r--r--source/fuzz/transformation_push_id_through_variable.h5
-rw-r--r--source/fuzz/transformation_record_synonymous_constants.cpp20
-rw-r--r--source/fuzz/transformation_record_synonymous_constants.h21
-rw-r--r--source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp232
-rw-r--r--source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h69
-rw-r--r--source/fuzz/transformation_replace_constant_with_uniform.cpp53
-rw-r--r--source/fuzz/transformation_replace_constant_with_uniform.h6
-rw-r--r--source/fuzz/transformation_replace_copy_memory_with_load_store.cpp127
-rw-r--r--source/fuzz/transformation_replace_copy_memory_with_load_store.h57
-rw-r--r--source/fuzz/transformation_replace_copy_object_with_store_load.cpp147
-rw-r--r--source/fuzz/transformation_replace_copy_object_with_store_load.h63
-rw-r--r--source/fuzz/transformation_replace_id_with_synonym.cpp149
-rw-r--r--source/fuzz/transformation_replace_id_with_synonym.h33
-rw-r--r--source/fuzz/transformation_replace_irrelevant_id.cpp110
-rw-r--r--source/fuzz/transformation_replace_irrelevant_id.h58
-rw-r--r--source/fuzz/transformation_replace_linear_algebra_instruction.cpp216
-rw-r--r--source/fuzz/transformation_replace_linear_algebra_instruction.h8
-rw-r--r--source/fuzz/transformation_replace_load_store_with_copy_memory.cpp184
-rw-r--r--source/fuzz/transformation_replace_load_store_with_copy_memory.h76
-rw-r--r--source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp110
-rw-r--r--source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h56
-rw-r--r--source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp204
-rw-r--r--source/fuzz/transformation_replace_opselect_with_conditional_branch.h61
-rw-r--r--source/fuzz/transformation_replace_parameter_with_global.cpp79
-rw-r--r--source/fuzz/transformation_replace_params_with_struct.cpp149
-rw-r--r--source/fuzz/transformation_replace_params_with_struct.h11
-rw-r--r--source/fuzz/transformation_set_loop_control.cpp4
-rw-r--r--source/fuzz/transformation_split_block.cpp33
-rw-r--r--source/fuzz/transformation_swap_commutable_operands.cpp6
-rw-r--r--source/fuzz/transformation_swap_conditional_branch_operands.cpp1
-rw-r--r--source/fuzz/transformation_toggle_access_chain_instruction.cpp6
-rw-r--r--source/fuzz/transformation_vector_shuffle.cpp10
-rw-r--r--source/link/CMakeLists.txt2
-rw-r--r--source/name_mapper.cpp2
-rw-r--r--source/opcode.cpp29
-rw-r--r--source/opcode.h10
-rw-r--r--source/operand.cpp31
-rw-r--r--source/opt/CMakeLists.txt6
-rw-r--r--source/opt/aggressive_dead_code_elim_pass.cpp27
-rw-r--r--source/opt/aggressive_dead_code_elim_pass.h3
-rw-r--r--source/opt/ccp_pass.cpp23
-rw-r--r--source/opt/ccp_pass.h3
-rw-r--r--source/opt/compact_ids_pass.cpp49
-rw-r--r--source/opt/constants.cpp6
-rw-r--r--source/opt/constants.h3
-rw-r--r--source/opt/dead_insert_elim_pass.cpp1
-rw-r--r--source/opt/debug_info_manager.cpp256
-rw-r--r--source/opt/debug_info_manager.h46
-rw-r--r--source/opt/dominator_tree.cpp54
-rw-r--r--source/opt/eliminate_dead_functions_util.cpp30
-rw-r--r--source/opt/folding_rules.cpp2
-rw-r--r--source/opt/function.cpp66
-rw-r--r--source/opt/function.h38
-rw-r--r--source/opt/graphics_robust_access_pass.cpp4
-rw-r--r--source/opt/inline_pass.cpp8
-rw-r--r--source/opt/inst_bindless_check_pass.cpp283
-rw-r--r--source/opt/inst_bindless_check_pass.h79
-rw-r--r--source/opt/instruction.cpp77
-rw-r--r--source/opt/instruction.h69
-rw-r--r--source/opt/instrument_pass.cpp81
-rw-r--r--source/opt/instrument_pass.h41
-rw-r--r--source/opt/ir_builder.h8
-rw-r--r--source/opt/ir_context.cpp46
-rw-r--r--source/opt/ir_context.h8
-rw-r--r--source/opt/ir_loader.cpp15
-rw-r--r--source/opt/local_access_chain_convert_pass.cpp28
-rw-r--r--source/opt/local_single_block_elim_pass.cpp4
-rw-r--r--source/opt/local_single_store_elim_pass.cpp5
-rw-r--r--source/opt/loop_descriptor.cpp38
-rw-r--r--source/opt/loop_descriptor.h36
-rw-r--r--source/opt/loop_peeling.cpp2
-rw-r--r--source/opt/loop_unroller.cpp38
-rw-r--r--source/opt/loop_unswitch_pass.cpp6
-rw-r--r--source/opt/merge_return_pass.cpp13
-rw-r--r--source/opt/merge_return_pass.h14
-rw-r--r--source/opt/module.cpp10
-rw-r--r--source/opt/optimizer.cpp16
-rw-r--r--source/opt/private_to_local_pass.cpp21
-rw-r--r--source/opt/private_to_local_pass.h4
-rw-r--r--source/opt/register_pressure.cpp2
-rw-r--r--source/opt/scalar_replacement_pass.cpp86
-rw-r--r--source/opt/scalar_replacement_pass.h15
-rw-r--r--source/opt/simplification_pass.cpp4
-rw-r--r--source/opt/ssa_rewrite_pass.cpp12
-rw-r--r--source/opt/struct_cfg_analysis.cpp26
-rw-r--r--source/opt/struct_cfg_analysis.h12
-rw-r--r--source/opt/wrap_opkill.cpp2
-rw-r--r--source/reduce/CMakeLists.txt7
-rw-r--r--source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp14
-rw-r--r--source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h2
-rw-r--r--source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp10
-rw-r--r--source/reduce/merge_blocks_reduction_opportunity.cpp12
-rw-r--r--source/reduce/merge_blocks_reduction_opportunity_finder.cpp10
-rw-r--r--source/reduce/merge_blocks_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/operand_to_const_reduction_opportunity_finder.cpp8
-rw-r--r--source/reduce/operand_to_const_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp24
-rw-r--r--source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/operand_to_undef_reduction_opportunity_finder.cpp8
-rw-r--r--source/reduce/operand_to_undef_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/reducer.cpp3
-rw-r--r--source/reduce/reduction_opportunity_finder.cpp34
-rw-r--r--source/reduce/reduction_opportunity_finder.h19
-rw-r--r--source/reduce/reduction_pass.cpp4
-rw-r--r--source/reduce/reduction_pass.h7
-rw-r--r--source/reduce/reduction_util.cpp74
-rw-r--r--source/reduce/reduction_util.h10
-rw-r--r--source/reduce/remove_block_reduction_opportunity.cpp5
-rw-r--r--source/reduce/remove_block_reduction_opportunity_finder.cpp39
-rw-r--r--source/reduce/remove_block_reduction_opportunity_finder.h6
-rw-r--r--source/reduce/remove_function_reduction_opportunity_finder.cpp9
-rw-r--r--source/reduce/remove_function_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/remove_selection_reduction_opportunity_finder.cpp16
-rw-r--r--source/reduce/remove_selection_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp91
-rw-r--r--source/reduce/remove_unused_instruction_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp9
-rw-r--r--source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h2
-rw-r--r--source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp11
-rw-r--r--source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h2
-rw-r--r--source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp4
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity.cpp93
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity.h18
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp20
-rw-r--r--source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h2
-rw-r--r--source/spirv_fuzzer_options.cpp8
-rw-r--r--source/spirv_fuzzer_options.h3
-rw-r--r--source/spirv_reducer_options.cpp9
-rw-r--r--source/spirv_reducer_options.h3
-rw-r--r--source/val/validate_builtins.cpp510
-rw-r--r--source/val/validate_capability.cpp7
-rw-r--r--source/val/validate_extensions.cpp730
-rw-r--r--source/val/validate_image.cpp2
-rw-r--r--source/val/validate_interfaces.cpp13
-rw-r--r--source/val/validation_state.cpp204
-rw-r--r--source/val/validation_state.h11
-rw-r--r--test/CMakeLists.txt6
-rw-r--r--test/binary_header_get_test.cpp32
-rw-r--r--test/binary_parse_test.cpp2
-rw-r--r--test/fuzz/CMakeLists.txt26
-rw-r--r--test/fuzz/call_graph_test.cpp365
-rw-r--r--test/fuzz/comparator_deep_blocks_first_test.cpp134
-rw-r--r--test/fuzz/data_synonym_transformation_test.cpp98
-rw-r--r--test/fuzz/fact_manager_test.cpp612
-rw-r--r--test/fuzz/fuzz_test_util.h3
-rw-r--r--test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp175
-rw-r--r--test/fuzz/fuzzer_pass_construct_composites_test.cpp5
-rw-r--r--test/fuzz/fuzzer_pass_donate_modules_test.cpp339
-rw-r--r--test/fuzz/fuzzer_pass_outline_functions_test.cpp603
-rw-r--r--test/fuzz/fuzzer_pass_test.cpp103
-rw-r--r--test/fuzz/fuzzer_replayer_test.cpp56
-rw-r--r--test/fuzz/fuzzer_shrinker_test.cpp79
-rw-r--r--test/fuzz/instruction_descriptor_test.cpp1
-rw-r--r--test/fuzz/replayer_test.cpp72
-rw-r--r--test/fuzz/transformation_access_chain_test.cpp303
-rw-r--r--test/fuzz/transformation_add_bit_instruction_synonym_test.cpp464
-rw-r--r--test/fuzz/transformation_add_constant_boolean_test.cpp66
-rw-r--r--test/fuzz/transformation_add_constant_composite_test.cpp63
-rw-r--r--test/fuzz/transformation_add_constant_null_test.cpp3
-rw-r--r--test/fuzz/transformation_add_constant_scalar_test.cpp399
-rw-r--r--test/fuzz/transformation_add_copy_memory_test.cpp3
-rw-r--r--test/fuzz/transformation_add_dead_block_test.cpp172
-rw-r--r--test/fuzz/transformation_add_dead_break_test.cpp40
-rw-r--r--test/fuzz/transformation_add_dead_continue_test.cpp31
-rw-r--r--test/fuzz/transformation_add_function_test.cpp187
-rw-r--r--test/fuzz/transformation_add_global_undef_test.cpp3
-rw-r--r--test/fuzz/transformation_add_global_variable_test.cpp7
-rw-r--r--test/fuzz/transformation_add_image_sample_unused_components_test.cpp5
-rw-r--r--test/fuzz/transformation_add_local_variable_test.cpp3
-rw-r--r--test/fuzz/transformation_add_loop_preheader_test.cpp292
-rw-r--r--test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp957
-rw-r--r--test/fuzz/transformation_add_no_contraction_decoration_test.cpp3
-rw-r--r--test/fuzz/transformation_add_opphi_synonym_test.cpp423
-rw-r--r--test/fuzz/transformation_add_parameter_test.cpp989
-rw-r--r--test/fuzz/transformation_add_relaxed_decoration_test.cpp6
-rw-r--r--test/fuzz/transformation_add_synonym_test.cpp242
-rw-r--r--test/fuzz/transformation_add_type_array_test.cpp3
-rw-r--r--test/fuzz/transformation_add_type_boolean_test.cpp3
-rw-r--r--test/fuzz/transformation_add_type_float_test.cpp148
-rw-r--r--test/fuzz/transformation_add_type_function_test.cpp3
-rw-r--r--test/fuzz/transformation_add_type_int_test.cpp201
-rw-r--r--test/fuzz/transformation_add_type_matrix_test.cpp3
-rw-r--r--test/fuzz/transformation_add_type_pointer_test.cpp3
-rw-r--r--test/fuzz/transformation_add_type_struct_test.cpp46
-rw-r--r--test/fuzz/transformation_add_type_vector_test.cpp3
-rw-r--r--test/fuzz/transformation_adjust_branch_weights_test.cpp5
-rw-r--r--test/fuzz/transformation_composite_construct_test.cpp179
-rw-r--r--test/fuzz/transformation_composite_extract_test.cpp184
-rw-r--r--test/fuzz/transformation_composite_insert_test.cpp814
-rw-r--r--test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp49
-rw-r--r--test/fuzz/transformation_duplicate_region_with_selection_test.cpp1686
-rw-r--r--test/fuzz/transformation_equation_instruction_test.cpp84
-rw-r--r--test/fuzz/transformation_flatten_conditional_branch_test.cpp793
-rw-r--r--test/fuzz/transformation_function_call_test.cpp5
-rw-r--r--test/fuzz/transformation_inline_function_test.cpp832
-rw-r--r--test/fuzz/transformation_invert_comparison_operator_test.cpp2
-rw-r--r--test/fuzz/transformation_load_test.cpp3
-rw-r--r--test/fuzz/transformation_make_vector_operation_dynamic_test.cpp365
-rw-r--r--test/fuzz/transformation_merge_blocks_test.cpp20
-rw-r--r--test/fuzz/transformation_move_block_down_test.cpp13
-rw-r--r--test/fuzz/transformation_move_instruction_down_test.cpp706
-rw-r--r--test/fuzz/transformation_mutate_pointer_test.cpp326
-rw-r--r--test/fuzz/transformation_outline_function_test.cpp513
-rw-r--r--test/fuzz/transformation_permute_function_parameters_test.cpp5
-rw-r--r--test/fuzz/transformation_permute_phi_operands_test.cpp3
-rw-r--r--test/fuzz/transformation_propagate_instruction_up_test.cpp895
-rw-r--r--test/fuzz/transformation_push_id_through_variable_test.cpp209
-rw-r--r--test/fuzz/transformation_record_synonymous_constants_test.cpp249
-rw-r--r--test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp609
-rw-r--r--test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp8
-rw-r--r--test/fuzz/transformation_replace_constant_with_uniform_test.cpp222
-rw-r--r--test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp152
-rw-r--r--test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp199
-rw-r--r--test/fuzz/transformation_replace_id_with_synonym_test.cpp442
-rw-r--r--test/fuzz/transformation_replace_irrelevant_id_test.cpp193
-rw-r--r--test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp632
-rw-r--r--test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp281
-rw-r--r--test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp206
-rw-r--r--test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp277
-rw-r--r--test/fuzz/transformation_replace_parameter_with_global_test.cpp61
-rw-r--r--test/fuzz/transformation_replace_params_with_struct_test.cpp144
-rw-r--r--test/fuzz/transformation_set_function_control_test.cpp3
-rw-r--r--test/fuzz/transformation_set_loop_control_test.cpp70
-rw-r--r--test/fuzz/transformation_set_memory_operands_mask_test.cpp5
-rw-r--r--test/fuzz/transformation_set_selection_control_test.cpp3
-rw-r--r--test/fuzz/transformation_split_block_test.cpp17
-rw-r--r--test/fuzz/transformation_store_test.cpp5
-rw-r--r--test/fuzz/transformation_swap_commutable_operands_test.cpp5
-rw-r--r--test/fuzz/transformation_swap_conditional_branch_operands_test.cpp3
-rw-r--r--test/fuzz/transformation_toggle_access_chain_instruction_test.cpp5
-rw-r--r--test/fuzz/transformation_vector_shuffle_test.cpp238
-rw-r--r--test/fuzz/uniform_buffer_element_descriptor_test.cpp1
-rw-r--r--test/link/binary_version_test.cpp8
-rw-r--r--test/opcode_table_get_test.cpp2
-rw-r--r--test/operand_test.cpp54
-rw-r--r--test/opt/aggressive_dead_code_elim_test.cpp56
-rw-r--r--test/opt/ccp_test.cpp106
-rw-r--r--test/opt/compact_ids_test.cpp36
-rw-r--r--test/opt/dead_insert_elim_test.cpp107
-rw-r--r--test/opt/debug_info_manager_test.cpp114
-rw-r--r--test/opt/eliminate_dead_functions_test.cpp95
-rw-r--r--test/opt/fold_test.cpp13
-rw-r--r--test/opt/function_test.cpp128
-rw-r--r--test/opt/graphics_robust_access_test.cpp165
-rw-r--r--test/opt/inline_opaque_test.cpp6
-rw-r--r--test/opt/inline_test.cpp70
-rw-r--r--test/opt/inst_bindless_check_test.cpp860
-rw-r--r--test/opt/ir_context_test.cpp109
-rw-r--r--test/opt/local_access_chain_convert_test.cpp31
-rw-r--r--test/opt/local_ssa_elim_test.cpp322
-rw-r--r--test/opt/loop_optimizations/unroll_simple.cpp333
-rw-r--r--test/opt/module_test.cpp41
-rw-r--r--test/opt/pass_fixture.h7
-rw-r--r--test/opt/pass_merge_return_test.cpp30
-rw-r--r--test/opt/private_to_local_test.cpp44
-rw-r--r--test/opt/scalar_replacement_test.cpp180
-rw-r--r--test/opt/strip_reflect_info_test.cpp94
-rw-r--r--test/opt/struct_cfg_analysis_test.cpp128
-rw-r--r--test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp20
-rw-r--r--test/reduce/merge_blocks_test.cpp8
-rw-r--r--test/reduce/operand_to_constant_test.cpp152
-rw-r--r--test/reduce/operand_to_dominating_id_test.cpp2
-rw-r--r--test/reduce/operand_to_undef_test.cpp4
-rw-r--r--test/reduce/reducer_test.cpp225
-rw-r--r--test/reduce/remove_block_test.cpp12
-rw-r--r--test/reduce/remove_function_test.cpp12
-rw-r--r--test/reduce/remove_selection_test.cpp22
-rw-r--r--test/reduce/remove_unused_instruction_test.cpp28
-rw-r--r--test/reduce/remove_unused_struct_member_test.cpp6
-rw-r--r--test/reduce/simple_conditional_branch_to_branch_test.cpp20
-rw-r--r--test/reduce/structured_loop_to_selection_test.cpp54
-rw-r--r--test/reduce/validation_during_reduction_test.cpp10
-rwxr-xr-xtest/tools/spirv_test_framework.py8
-rw-r--r--test/val/CMakeLists.txt10
-rw-r--r--test/val/val_adjacency_test.cpp14
-rw-r--r--test/val/val_builtins_test.cpp534
-rw-r--r--test/val/val_capability_test.cpp19
-rw-r--r--test/val/val_cfg_test.cpp16
-rw-r--r--test/val/val_ext_inst_test.cpp1373
-rw-r--r--test/val/val_interfaces_test.cpp35
-rw-r--r--test/val/val_primitives_test.cpp2
-rw-r--r--tools/CMakeLists.txt16
-rw-r--r--tools/fuzz/fuzz.cpp144
-rw-r--r--tools/io.h1
-rw-r--r--tools/reduce/reduce.cpp41
-rw-r--r--tools/sva/yarn.lock6
-rwxr-xr-xutils/roll_deps.sh8
-rw-r--r--utils/vscode/src/parser/parser.go2
508 files changed, 45188 insertions, 7180 deletions
diff --git a/Android.mk b/Android.mk
index 5c495cdc..54360f0d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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",
diff --git a/BUILD.gn b/BUILD.gn
index fae79572..de4e48ef 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -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",
diff --git a/CHANGES b/CHANGES
index a63ffb27..31c6cb34 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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)
diff --git a/DEPS b/DEPS
index a27b921b..96d556e2 100644
--- a/DEPS
+++ b/DEPS
@@ -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 = {
diff --git a/README.md b/README.md
index c82ca192..44f582fb 100644
--- a/README.md
+++ b/README.md
@@ -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(),
- &current_best_binary, &current_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,
+ &region_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,
+ &parameter_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, &region_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,
[&region_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;
}
diff --git a/tools/io.h b/tools/io.h
index 97a31636..f9cfd9d1 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -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)
}