From d86146c96b732e0a87ce2e961afed3cc604ffaf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Tue, 4 Feb 2020 21:33:12 -0800 Subject: [PATCH] Updated spirv-tools. --- 3rdparty/spirv-tools/CHANGES | 1011 +++++++++++++++++ 3rdparty/spirv-tools/README.md | 685 +++++++++++ .../include/generated/OpenCLDebugInfo100.h | 149 +++ .../include/generated/build-version.inc | 2 +- .../include/generated/enum_string_mapping.inc | 6 +- .../include/generated/extension_enum.inc | 1 + .../generated/opencl.debuginfo.100.insts.inc | 39 + .../generated/operand.kinds-unified1.inc | 69 ++ .../include/spirv-tools/instrument.hpp | 13 +- .../include/spirv-tools/libspirv.h | 68 +- .../include/spirv-tools/libspirv.hpp | 25 +- .../include/spirv-tools/optimizer.hpp | 35 +- 3rdparty/spirv-tools/source/CMakeLists.txt | 28 +- 3rdparty/spirv-tools/source/binary.cpp | 27 +- 3rdparty/spirv-tools/source/disassemble.cpp | 22 +- 3rdparty/spirv-tools/source/ext_inst.cpp | 27 + 3rdparty/spirv-tools/source/ext_inst.h | 6 + .../extinst.opencl.debuginfo.100.grammar.json | 619 ++++++++++ .../spirv-tools/source/fuzz/CMakeLists.txt | 36 + .../spirv-tools/source/fuzz/fact_manager.cpp | 132 ++- .../spirv-tools/source/fuzz/fact_manager.h | 53 + 3rdparty/spirv-tools/source/fuzz/fuzzer.cpp | 33 +- 3rdparty/spirv-tools/source/fuzz/fuzzer.h | 8 +- .../source/fuzz/fuzzer_context.cpp | 37 +- .../spirv-tools/source/fuzz/fuzzer_context.h | 45 + .../spirv-tools/source/fuzz/fuzzer_pass.cpp | 126 ++ .../spirv-tools/source/fuzz/fuzzer_pass.h | 54 + .../fuzz/fuzzer_pass_add_composite_types.cpp | 138 +++ .../fuzz/fuzzer_pass_add_composite_types.h | 61 + .../fuzz/fuzzer_pass_add_dead_blocks.cpp | 64 ++ .../source/fuzz/fuzzer_pass_add_dead_blocks.h | 39 + .../fuzz/fuzzer_pass_construct_composites.cpp | 1 - .../fuzz/fuzzer_pass_donate_modules.cpp | 748 ++++++++++++ .../source/fuzz/fuzzer_pass_donate_modules.h | 93 ++ .../source/fuzz/fuzzer_pass_merge_blocks.cpp | 65 ++ .../source/fuzz/fuzzer_pass_merge_blocks.h | 38 + .../fuzz/fuzzer_pass_outline_functions.cpp | 99 ++ .../fuzz/fuzzer_pass_outline_functions.h | 40 + .../spirv-tools/source/fuzz/fuzzer_util.cpp | 153 ++- .../spirv-tools/source/fuzz/fuzzer_util.h | 27 +- .../source/fuzz/instruction_message.cpp | 68 ++ .../source/fuzz/instruction_message.h | 43 + .../source/fuzz/protobufs/spvtoolsfuzz.proto | 386 ++++++- .../source/fuzz/transformation.cpp | 55 + .../spirv-tools/source/fuzz/transformation.h | 9 + .../transformation_add_constant_composite.cpp | 130 +++ .../transformation_add_constant_composite.h | 58 + .../fuzz/transformation_add_dead_block.cpp | 169 +++ .../fuzz/transformation_add_dead_block.h | 63 + .../fuzz/transformation_add_dead_break.cpp | 11 +- .../fuzz/transformation_add_dead_continue.cpp | 11 +- .../fuzz/transformation_add_function.cpp | 921 +++++++++++++++ .../source/fuzz/transformation_add_function.h | 124 ++ .../fuzz/transformation_add_global_undef.cpp | 62 + .../fuzz/transformation_add_global_undef.h | 51 + .../transformation_add_global_variable.cpp | 137 +++ .../fuzz/transformation_add_global_variable.h | 61 + .../fuzz/transformation_add_type_array.cpp | 88 ++ .../fuzz/transformation_add_type_array.h | 55 + .../fuzz/transformation_add_type_function.cpp | 113 ++ .../fuzz/transformation_add_type_function.h | 59 + .../fuzz/transformation_add_type_matrix.cpp | 71 ++ .../fuzz/transformation_add_type_matrix.h | 53 + .../fuzz/transformation_add_type_struct.cpp | 73 ++ .../fuzz/transformation_add_type_struct.h | 54 + .../fuzz/transformation_add_type_vector.cpp | 69 ++ .../fuzz/transformation_add_type_vector.h | 53 + .../source/fuzz/transformation_copy_object.h | 2 + .../fuzz/transformation_merge_blocks.cpp | 81 ++ .../source/fuzz/transformation_merge_blocks.h | 54 + .../fuzz/transformation_outline_function.cpp | 943 +++++++++++++++ .../fuzz/transformation_outline_function.h | 221 ++++ .../fuzz/transformation_split_block.cpp | 9 +- .../source/fuzz/transformation_split_block.h | 1 + 3rdparty/spirv-tools/source/link/linker.cpp | 45 +- 3rdparty/spirv-tools/source/operand.cpp | 55 + 3rdparty/spirv-tools/source/operand.h | 7 + .../spirv-tools/source/opt/amd_ext_to_khr.cpp | 45 +- 3rdparty/spirv-tools/source/opt/basic_block.h | 2 +- .../source/opt/block_merge_util.cpp | 11 +- .../source/opt/const_folding_rules.cpp | 102 +- 3rdparty/spirv-tools/source/opt/constants.cpp | 2 +- 3rdparty/spirv-tools/source/opt/constants.h | 3 +- .../source/opt/convert_to_half_pass.cpp | 189 +-- .../source/opt/convert_to_half_pass.h | 36 +- .../source/opt/dead_branch_elim_pass.cpp | 20 +- .../source/opt/dead_branch_elim_pass.h | 13 +- .../spirv-tools/source/opt/folding_rules.cpp | 102 +- .../source/opt/inst_bindless_check_pass.h | 13 +- .../source/opt/inst_buff_addr_check_pass.h | 8 +- 3rdparty/spirv-tools/source/opt/instruction.h | 3 + .../source/opt/instrument_pass.cpp | 107 +- .../spirv-tools/source/opt/instrument_pass.h | 11 +- 3rdparty/spirv-tools/source/opt/ir_context.h | 31 + 3rdparty/spirv-tools/source/opt/ir_loader.cpp | 46 +- .../source/opt/merge_return_pass.cpp | 126 +- .../source/opt/merge_return_pass.h | 76 +- 3rdparty/spirv-tools/source/opt/module.cpp | 2 + 3rdparty/spirv-tools/source/opt/module.h | 33 + .../source/opt/strip_debug_info_pass.cpp | 60 +- .../source/opt/strip_reflect_info_pass.cpp | 46 + ...struction_reduction_opportunity_finder.cpp | 7 + .../spirv-tools/source/spirv_target_env.cpp | 39 +- 3rdparty/spirv-tools/source/table.cpp | 1 + 3rdparty/spirv-tools/source/text.cpp | 40 +- 3rdparty/spirv-tools/source/val/construct.cpp | 23 +- 3rdparty/spirv-tools/source/val/instruction.h | 12 + .../source/val/validate_adjacency.cpp | 10 + .../source/val/validate_annotation.cpp | 3 +- .../source/val/validate_capability.cpp | 50 + .../spirv-tools/source/val/validate_cfg.cpp | 8 +- .../source/val/validate_decorations.cpp | 1 + .../source/val/validate_extensions.cpp | 12 +- .../source/val/validate_function.cpp | 3 +- .../spirv-tools/source/val/validate_id.cpp | 16 +- .../source/val/validate_layout.cpp | 146 ++- .../source/val/validate_scopes.cpp | 7 +- .../spirv-tools/source/val/validate_type.cpp | 2 +- .../source/val/validation_state.cpp | 3 + 3rdparty/spirv-tools/utils/check_copyright.py | 4 +- .../utils/generate_grammar_tables.py | 78 +- 121 files changed, 10404 insertions(+), 535 deletions(-) create mode 100644 3rdparty/spirv-tools/CHANGES create mode 100644 3rdparty/spirv-tools/README.md create mode 100644 3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h create mode 100644 3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc create mode 100644 3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.h create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.h create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.h create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h create mode 100644 3rdparty/spirv-tools/source/fuzz/instruction_message.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/instruction_message.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_function.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_outline_function.h diff --git a/3rdparty/spirv-tools/CHANGES b/3rdparty/spirv-tools/CHANGES new file mode 100644 index 000000000..48c93a4d6 --- /dev/null +++ b/3rdparty/spirv-tools/CHANGES @@ -0,0 +1,1011 @@ +Revision history for SPIRV-Tools + +v2020.2-dev 2020-02-03 + - Start v2020.2-dev + +v2020.1 2020-02-03 + - General: + - Add support for SPV_KHR_non_semantic_info (#3110) + - Support OpenCL.DebugInfo.100 extended instruction set (#3080) + - Added support for Vulkan 1.2 + - Add API function to better handle getting the necessary environment (#3142) + - Clarify mapping of target env to SPIR-V version (#3150) + - Implement constant folding for many transcendentals (#3166) + - 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) + - Handle TimeAMD in AmdExtensionToKhrPass. (#3168) + - Validator + - Fix structured exit validation (#3141) + - Reduce + - Fuzz + - Fuzzer pass to merge blocks (#3097) + - Transformation to add a new function to a module (#3114) + - Add fuzzer pass to perform module donation (#3117) + - Fuzzer passes to create and branch to new dead blocks (#3135) + - Fuzzer pass to add composite types (#3171) + - Linker: + - Remove names and decorations of imported symbols (#3081) + +v2019.5 2019-12-11 + - General: + - Export SPIRV-Tools targets on installation + - SPIRV-Tools support for SPIR-V 1.5 (#2865) + - Add WebGPU SPIR-V Assembler in JavaScript. (#2876) + - Add Bazel build configuration. (#2891) + - Add support for building with emscripten (#2948) + - Update SPIR-V binary header test for SPIR-V 1.5 (#2967) + - Add fuzzer for spirv-as call path (#2976) + - Improved CMake install step. (#2963) + - Add fuzzer for spirv-dis call path (#2977) + - Ensure timestamp does not vary with timezone. (#2982) + - Add a vscode extension for SPIR-V disassembly files (#2987) + - Add iOS as a supported platform (#3001) + - utils/vscode: Add SPIR-V language server support + - Respect CMAKE_INSTALL_LIBDIR in installed CMake files (#3054) + - Permit the debug instructions in WebGPU SPIR-V (#3063) + - Add support for Fuchsia. (#3062) + - Optimizer + - Add descriptor array scalar replacement (#2742) + - Add pass to wrap OpKill in a function call (#2790) + - Fold FMix during constant folding. (#2818) + - Add pass to replace AMD shader ballot extension (#2811) + - Add pass to make Float32 operation relax precision (#2808) + - Add pass to make relax precision operation Float16 (#2808) + - Add pass to replace uses of 3 AMD extensions (#2814) + - Fold Min, Max, and Clamp instructions. (#2836) + - Better handling of OpKill in continues (#2842,#2922,#2933) + - Enable OpTypeCooperativeMatrix specialization (#2927) + - Support constant-folding UConvert and SConvert (#2960) + - Update Offset to ConstOffset bitmask if operand is constant. (#3024) + - Improve RegisterSizePasses (#3059) + - Folding: perform add and sub on mismatched integer types (#3084) + - Graphics robust access: use signed clamp (#3073) + Fixes: + - Instrument: Fix version 2 output record write for tess eval shaders. (#2782) + - Instrument: Add support for Buffer Device Address extension (#2792) + - Fix check for changed binary in API call. (#2798) + - For WebGPU<->Vulkan optimization, set correct execution environment (#2834) + - Handle OpConstantNull in copy-prop-arrays. (#2870) + - Use OpReturn* in wrap-opkill (#2886) + - Validator + - Add generic builtin validation of target (#2843) + - Extra resource interface validation (#2864) + - Adding valilidation checks for OpEntryPoint duplicate names and execution mode (#2862) + - Relaxed bitcast with pointers (#2878) + - Validate physical storage buffer restrictions (#2930) + - Add SPV_KHR_shader_clock validation (#2879, #3013) + - Validate that selections are structured (#2962) + - Disallow use of OpCompositeExtract/OpCompositeInsert with no indices (#2980) + - Check that derivatives operate on 32-bit values (#2983) + - Validate array stride does not cause overlap (#3028) + - Validate nested constructs (#3068) + Fixes: + - Fix validation of constant matrices (#2794) + - Update "remquor" validation + - Only allow previously declared forward refs in structs (#2920) + - Reduce + - Remove relaxed precision decorations (#2797) + - Reduce/fuzz: improve command line args (#2932) + - Improve remove unref instr pass (#2945) + Fixes: + - Fuzz + - Fix add-dead-break and add-dead-continue passes to respect dominance (#2838) + - Add fuzzer pass to copy objects (#2853) + - Add fuzzer pass to replace ids with synonyms (#2857) + - Allow validation during spirv-fuzz replay (#2873) + - Employ the "swarm testing" idea in spirv-fuzz (#2890) + - reduce/fuzz: improve command line args (#2932) + - option to convert shader into a form that renders red (#2934) + - Add fuzzer pass to change selection controls (#2944) + - add transformation and pass to construct composites (#2941) + - Add fuzzer pass to change loop controls (#2949) + - Add fuzzer pass to change function controls (#2951) + - Add fuzzer pass to add NoContraction decorations (#2950) + - Add missing functionality for matrix composites (#2974) + - Fuzzer pass to adjust memory access operands (#2968) + - Transformation to extract from a composite object (#2991) + - Vector shuffle transformation (#3015) + - Improve debugging facilities (#3074) + - Function outlining fuzzer pass (#3078) + + +v2019.4 2019-08-08 + - General: + - Memory model support for SPIR-V 1.4 + - Add new spirv-fuzz tool + - Add option for base branch in check_code_format.sh + - Removed MarkV and Stats code. (#2576) + - Instrument: Add version 2 of record formats (#2630) + - Linker: Better type comparison for OpTypeArray and OpTypeForwardPointer (#2580) + - Optimizer + - Bindless Validation: Instrument descriptor-based loads and stores (#2583) + - Better folding for OpSpecConstantOp (#2585, #2614) + - Add in individual flags for Vulkan <-> WebGPU passes (#2615) + - Handle nested breaks from switches. (#2624) + - Optimizer: Handle array type with OpSpecConstantOp length (#2652) + - Perform merge return with single return in loop. (#2714) + - Add --preserve-bindings and --preserve-spec-constants (#2693) + - Remove Common Uniform Elimination Pass (#2731) + - Allow ray tracing shaders in inst bindle check pass. (#2733) + - Add pass to inject code for robust-buffer-access semantics (#2771) + - Treat access chain indexes as signed in SROA (#2776) + - Handle RelaxedPrecision in SROA (#2788) + - Add descriptor array scalar replacement (#2742) + Fixes: + - Handle decorations better in some optimizations (#2716) + - Change the order branches are simplified in dead branch elim (#2728) + - Fix bug in merge return (#2734) + - SSA rewriter: Don't use trivial phis (#2757) + - Record correct dominators in merge return (#2760) + - Process OpDecorateId in ADCE (#2761) + - Fix check for unreachable blocks in merge-return (#2762) + - Handle out-of-bounds scalar replacements. (#2767) + - Don't move debug or decorations when folding (#2772) + - Protect against out-of-bounds references when folding OpCompositeExtract (#2774) + - Validator + - Validate loop merge (#2579) + - Validate construct exits (#2459) + - Validate OpenCL memory and addressing model environment rules (#2589) + - Validate OpenCL environment rules for OpTypeImage (#2606) + - Allow breaks to switch merge from nested construct (#2604) + - Validate OpenCL environment rules for OpImageWrite (#2619) + - Allow arrays of out per-primitive builtins for mesh shaders (#2617) + - Validate OpenCL rules for ImageRead and OpImageSampleExplicitLod (#2643) + - Add validation for SPV_EXT_fragment_shader_interlock (#2650) + - Add builtin validation for SPV_NV_shader_sm_builtins (#2656) + - Add validation for Subgroup builtins (#2637) + - Validate variable initializer type (#2668) + - Disallow stores to UBOs (#2651)A + - Validate Volatile memory semantics bit (#2672) + - Basic validation for Component decorations (#2679) + - Validate that in OpenGL env block variables have Binding (#2685) + - Validate usage of 8- and 16-bit types with only storage capabilities (#2704) + - Add validation for SPV_EXT_demote_to_helper_invocation (#2707) + - Extra small storage validation (#2732) + - For Vulkan, disallow structures containing opaque types (#2546) + - Validate storage class OpenCL environment rules for atomics (#2750) + - Update OpControlBarriers rules for WebGPU (#2769) + - Update OpMemoryBarriers rules for WebGPU (#2775) + - Update WebGPU validation rules of OpAtomic*s (#2777) + Fixes: + - Disallow merge targeting block with OpLoopMerge (#2610) + - Update vloadn and vstoren validation to match the OpenCL Extended + Instruction Set Specification (#2599) + - Update memory scope rules for WebGPU (#2725) + - Allow LOD ops in compute shaders with derivative group execution modes (#2752) + - Reduce + Fixes: + +v2019.3 2019-05-14 + - General: + - Require Python 3 since Python 2 will out of service soon. + - Add a continuous test that does memory checks using the address sanitizer. + - Fix the build files so the SPIRV_USE_SANITIZER=address build works. + - Packaging top of tree build artifacts again. + - Added support for SPIR-V 1.4. (#2550) + - Optimizer + - Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449) + - Bindless Validation: Descriptor Initialization Check (#2419) + - Add option to validate after each pass (#2462) + - Add legalization pass to fix mismatched pointer (#2430, #2535) + - Add error messages when the input contains unknown instructions. (#2487) + - Add pass to convert from WebGPU Spir-V to Vulkan Spir-V and back. (#2495) + Fixes: + - #2412: Dead memeber elimination should not change input and output variables. + - #2405: Fix OpDot folding of half float vectors. + - #2391: Dead branch elim should not fold away back edges. + - #2441: Removing decorations when doing constant propagation. + - #2455: Maintain inst to block mapping in merge return. + - #2453: Fix merge return in the face of breaks. + - #2456: Handle dead infinite loops in DCE. + - #2458: Handle variable pointer in some optimizations. + - #2452: Fix dead branch elimination to handle unreachable blocks better. + - #2528: Fix undefined bit shift in sroa. + - #2539: Change implementation of post order CFG traversal. + - Validator + - Add validation of storage classes for WebGPU (#2446) + - Add validation for ExecutionMode in WebGPU (#2443) + - Implement WebGPU specific CFG validation (#2386) + - Allow NonWritable to target struct members. (#2420) + - Allow storage type mismatch for parameter in relaxed addressing mode. + - Allow non memory objects as parameter in relaxed addressing mode. + - Disallow nested Blocks and buffer blocks (#2410). + - Add validation for SPV_NV_cooperative_matrix (#2404) + - Add --strip-atomic-counter-memory (#2413) + - Check OpSampledImage is only passed into valid instructions (#2467) + - Handle function decls in Structured CFG analysis (#2474) + - Validate that OpUnreacahble is not statically reachable (#2473) + - Add pass to generate needed initializers for WebGPU (#2481) + - Allow images without format for OpenCL. (#2470) + - Remove unreachable block validation (#2525) + - Reduce runtime of array layout checks (#2534) + - Add validation specific to OpExecutionModeId (#2536) + - Validate sign of int types. (#2549) + - VK_KHR_uniform_buffer_standard_layout validation (#2562) + Fixes: + - #2439: Add missing DepthGreater case to Fragment only check. + - #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan. + - #2408: Restrict and Aliased decorations cannot be applied to the same id. + - #2447: Improve function call parameter check. + - Reduce + - Add Pass to remove unreferenced blocks. (#2398) + - Allows passing options to the validator. (#2401) + - Improve reducer algorithm and other changes (#2472) + - Add Pass to remove selections (#2485) + - Add passes to simplify branches (#2507) + Fixes: + - #2478: fix loop to selection pass for loops with combined header/continue block + +v2019.2 2019-02-20 + - General: + - Support SPV_EXT_physical_storage_buffer + - A number of memory leak have been fixed. + - Removed use of deprecated Google test macro: + - Changed the BUILD.gn to only build tests in Chromium. + - Optimizer + - Upgrade memory model improvments for modf and frexp. + - Add a new pass to move loads closer to their uses: code sinking. + - Invalidating the type manager now invalidates the constnat manager. + - Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays. + - Add a new pass that removes members from structs that are not used: dead member elimination. + Fixes: + - #2292: Remove undefined behaviour when folding bit shifts. + - #2294: Fixes for instrumentation code. + - #2293: Fix overflow when folding -INT_MIN. + - #2374: Don't merge unreachable blocks when merging blocks. + - Validator + - Support SPV_KHR_no_integer_wrap and related decorations. + - Validate Vulkan rules for OpTypeRuntimeArray. + - Validate NonWritable decoration. + - Many WebGPU specific validation rules were added. + - Validate variable pointer related function call rules. + - Better error messages. + Fixes: + - #2307: Check forwards references in OpTypeArray. + - #2315, #2303: Fixed the layout check for relaxed layout. + - #1628: Emit an error when an OpSwitch target is not an OpLabel. + - Reduce + - Added more documentation for spirv-reduce. + - Add ability to remove OpPhi instructions. + - Add ability to merge two basic blocks. + - Add ability to remove unused functions and unused basic blocks. + Fixes: + +v2019.1 2019-01-07 + - General: + - Created a new tool called spirv-reduce. + - Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103) + - New optimization pass to update the memory model from GLSL450 to VulkanKHR. + - Recognize OpTypeAccelerationStructureNV as a type instruction and ray tracing storage classes. + - Fix GCC8 build. + - Add --target-env flag to spirv-opt. + - Add --webgpu-mode flag to run optimizations for webgpu. + - The output disassembled line number stead of byte offset in validation errors. (#2091) + - Optimizer + - Added the instrumentation passes for bindless validation. + - Added passes to help preserve OpLine information (#2027) + - Add basic support for EXT_fragment_invocation_density (#2100) + - Fix invalid OpPhi generated by merge-return. (#2172) + - Constant and type manager have been turned into analysies. (#2251) + Fixes: + - #2018: Don't inline functions with a return in a structured CFG contstruct. + - #2047: Fix bug in folding when volatile stores are present. + - #2053: Fix check for when folding floating pointer values is allowed. + - #2130: Don't inline recursive functions. + - #2202: Handle multiple edges between two basic blocks in SSA-rewriter. + - #2205: Don't unswitch a latch condition during loop unswitch. + - #2245: Don't fold branch in loop unswitch. Run dead branch elimination to fold them. + - #2204: Fix eliminate common uniform to place OpPhi instructions correctly. + - #2247: Fix type mismatches caused by scalar replacement. + - #2248: Fix missing OpPhi after merge return. + - #2211: After merge return, fix invalid continue target. + - #2210: Fix loop invariant code motion to not place code between merge instruction and branch. + - #2258: Handle CompositeInsert with no indices in VDCE. + - #2261: Have replace load size handle extact with no index. + - Validator + - Changed the naming convention of outputing ids with names in diagnostic messages. + - Added validation rules for UniformConstant variables in Vulkan. + - #1949: Validate uniform variable type in Vulkan + - Ensure for OpVariable that result type and storage class operand agree (#2052) + - Validator: Support VK_EXT_scalar_block_layout + - Added Vulkan memory model semantics validation + - Added validation checkes spefic to WebGPU environment. + - Add support for VK_EXT_Transform_feedback capabilities (#2088) + - Add validation for OpArrayLength. (#2117) + - Ensure that function parameter's type is not void (#2118) + - Validate pointer variables (#2111) + - Add check for QueueFamilyKHMR memory scope (#2144) + - Validate PushConstants annotation and type (#2140) + - Allow Float16/Int8 for Vulkan 1.0 (#2153) + - Check binding annotations in resource variables (#2151, #2167) + - Validate OpForwardPointer (#2156) + - Validate operation for OpSpecConstantOp (#2260) + Fixes: + - #2049: Allow InstanceId for NV ray tracing + - Reduce + - Initial commit wit a few passes to reduce test cases. + - Validation is run after each reduction step. + Fixes: + + +v2018.6 2018-11-07 + - General: + - Added support for the Nvidia Turing and ray tracing extensions. + - Make C++11 the CXX standard in CMakeLists.txt. + - Enabled a parallel build for MSVC. + - Enable pre-compiled headers for MSVC. + - Added a code of conduct. + - EFFCEE and RE2 are now required when build the tests. + - 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. + - 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 + Fixes: + - #1917: Allow 0 (meaning unlimited) as a parameter to --scalar-replacement + - #1915: Improve handling of group decorations. + - #1942: Fix incorrect uses of the constant manager. Avoids type mismatches in generated code. + - #1997: Fix dead branch elimination when there is a loop in folded selection. + - #1991: Fixes legality check in if-conversion. + - #1987: Add nullptr check to array copy propagation. + - #1984: Better handling of OpUnreachable in ADCE. + - #1983: Run merge return on reachable functions only. + - #1956: Handled atomic operations in ADCE. + - #1963: Fold integer divisions by 0 to 0. + - #2019: Handle MemberDecorateStringGOOGLE in ADCE and strip reflect. + - Validator + - Added validation for OpGroupNonUniformBallotBitCount. + - Added validation for the Vulkan memory model. + - Added support for VK_KHR_shader_atddomic_int64. + - Added validation for execution modes. + - Added validation for runtime array layouts. + - Added validation for 8-bit storage. + - Added validation of OpPhi instructions with pointer result type. + - Added checks for the Vulkan memory model. + - Validate MakeTexelAvailableKHR and MakeTexelVisibleKHR + - Allow atomic function pointer for OpenCL. + - FPRounding mode checks were implemented. + - Added validation for the id bound with an option to set the max id bound. + Fixes: + - #1882: Improve the validation of decorations to reduce memory usage. + - #1891: Fix an potential infinite loop in dead-branch-elimination. + - #1405: Validate the storage class of boolean objects. + - #1880: Identify arrays of type void as invalid. + - #487: Validate OpImageTexelPointer. + - #1922: Validate OpPhi instructions are at the start of a block correctly. + - #1923: Validate function scope variable are at the start of the entry block. + +v2018.5 2018-09-07 + - General: + - Support SPV_KHR_vulkan_memory_model + - Update Dim capabilities, to match SPIR-V 1.3 Rev 4 + - Automated build bots no run tests for the VS2013 case + - Support Chromium GN build + - Use Kokoro bots: + - Disable Travis-CI bots + - Disable AppVeyor VisualStudio Release builds. Keep VS 2017 Debug build + - Don't check export symbols on OSX (Darwin): some installations don't have 'objdump' + - Reorganize source files and namespaces + - Fixes for ClangTidy, and whitespace (passes 'git cl presumit --all -uf') + - Fix unused param compile warnings/errors when Effcee not present + - Avoid including time headers when timer functionality is disabled + - Avoid too-stringent warnings flags for Clang on Windows + - Internal refactoring + - Add hooks for automated fuzzing + - Add testing of command line executables + - #1688: Use binary mode on stdin; fixes "spirv-dis . versioning, with "-dev" indicating + work in progress. The intent is to more easly report + and summarize functionality when SPIRV-Tools is incorporated + in downstream projects. + + - Summary of functionality (See the README.md for more): + - Supports SPIR-V 1.1 Rev 1 + - Supports SPIR-V 1.0 Rev 5 + - Supports GLSL std450 extended instructions 1.0 Rev 3 + - Supports OpenCL extended instructions 1.0 Rev 2 + - Assembler, disassembler are complete + - Supports floating point widths of 16, 32, 64 bits + - Supports integer widths up to 64 bits + - Validator is incomplete + - Checks capability requirements in most cases + - Checks module layout constraints + - Checks ID use-definition ordering constraints, + ignoring control flow + - Checks some control flow graph rules + - Optimizer is introduced, with few available transforms. + - Supported on Linux, OSX, Android, Windows + + - Fixes bugs: + - #143: OpenCL pow and pown arguments diff --git a/3rdparty/spirv-tools/README.md b/3rdparty/spirv-tools/README.md new file mode 100644 index 000000000..5714976d5 --- /dev/null +++ b/3rdparty/spirv-tools/README.md @@ -0,0 +1,685 @@ +# SPIR-V Tools + +## Overview + +The SPIR-V Tools project provides an API and commands for processing SPIR-V +modules. + +The project includes an assembler, binary module parser, disassembler, +validator, and optimizer for SPIR-V. Except for the optimizer, all are based +on a common static library. The library contains all of the implementation +details, and is used in the standalone tools whilst also enabling integration +into other code bases directly. The optimizer implementation resides in its +own library, which depends on the core library. + +The interfaces have stabilized: +We don't anticipate making a breaking change for existing features. + +SPIR-V is defined by the Khronos Group Inc. +See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification, +headers, and XML registry. + +## Downloads + +[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master) +Linux[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) +MacOS[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) +Windows[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) + +[More downloads](docs/downloads.md) + +## Versioning SPIRV-Tools + +See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version. + +SPIRV-Tools project version numbers are of the form `v`*year*`.`*index* and with +an optional `-dev` suffix to indicate work in progress. For example, the +following versions are ordered from oldest to newest: + +* `v2016.0` +* `v2016.1-dev` +* `v2016.1` +* `v2016.2-dev` +* `v2016.2` + +Use the `--version` option on each command line tool to see the software +version. An API call reports the software version as a C-style string. + +## Supported features + +### Assembler, binary parser, and disassembler + +* Support for SPIR-V 1.0, through 1.5 + * Based on SPIR-V syntax described by JSON grammar files in the + [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository. + * Usually, support for a new version of SPIR-V is ready within days after + publication. +* Support for extended instruction sets: + * GLSL std450 version 1.0 Rev 3 + * OpenCL version 1.0 Rev 2 +* Assembler only does basic syntax checking. No cross validation of + IDs or types is performed, except to check literal arguments to + `OpConstant`, `OpSpecConstant`, and `OpSwitch`. + +See [`docs/syntax.md`](docs/syntax.md) for the assembly language syntax. + +### Validator + +The validator checks validation rules described by the SPIR-V specification. + +Khronos recommends that tools that create or transform SPIR-V modules use the +validator to ensure their outputs are valid, and that tools that consume SPIR-V +modules optionally use the validator to protect themselves from bad inputs. +This is especially encouraged for debug and development scenarios. + +The validator has one-sided error: it will only return an error when it has +implemented a rule check and the module violates that rule. + +The validator is incomplete. +See the [CHANGES](CHANGES) file for reports on completed work, and +the [Validator +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/1) for planned +and in-progress work. + +*Note*: The validator checks some Universal Limits, from section 2.17 of the SPIR-V spec. +The validator will fail on a module that exceeds those minimum upper bound limits. +It is [future work](https://github.com/KhronosGroup/SPIRV-Tools/projects/1#card-1052403) +to parameterize the validator to allow larger +limits accepted by a more than minimally capable SPIR-V consumer. + + +### Optimizer + +The optimizer is a collection of code transforms, or "passes". +Transforms are written for a diverse set of reasons: + +* To restructure, simplify, or normalize the code for further processing. +* To eliminate undesirable code. +* To improve code quality in some metric such as size or performance. + **Note**: These transforms are not guaranteed to actually improve any + given metric. Users should always measure results for their own situation. + +As of this writing, there are 67 transforms including examples such as: +* Simplification + * Strip debug info + * Strip reflection info +* Specialization Constants + * Set spec constant default value + * Freeze spec constant to default value + * Fold `OpSpecConstantOp` and `OpSpecConstantComposite` + * Unify constants + * Eliminate dead constant +* Code Reduction + * Inline all function calls exhaustively + * Convert local access chains to inserts/extracts + * Eliminate local load/store in single block + * Eliminate local load/store with single store + * Eliminate local load/store with multiple stores + * Eliminate local extract from insert + * Eliminate dead instructions (aggressive) + * Eliminate dead branches + * Merge single successor / single predecessor block pairs + * Eliminate common uniform loads + * Remove duplicates: Capabilities, extended instruction imports, types, and + decorations. +* Normalization + * Compact IDs + * CFG cleanup + * Flatten decorations + * Merge returns + * Convert AMD-specific instructions to KHR instructions +* Code improvement + * Conditional constant propagation + * If-conversion + * Loop fission + * Loop fusion + * Loop-invariant code motion + * Loop unroll +* Other + * Generate WebGPU initializers + * Graphics robust access + * Upgrade memory model to VulkanKHR + +Additionally, certain sets of transformations have been packaged into +higher-level recipes. These include: + +* Optimization for size (`spirv-opt -Os`) +* Optimization for performance (`spirv-opt -O`) + +For the latest list with detailed documentation, please refer to +[`include/spirv-tools/optimizer.hpp`](include/spirv-tools/optimizer.hpp). + +For suggestions on using the code reduction options, please refer to this [white paper](https://www.lunarg.com/shader-compiler-technologies/white-paper-spirv-opt/). + + +### Linker + +*Note:* The linker is still under development. + +Current features: +* Combine multiple SPIR-V binary modules together. +* Combine into a library (exports are retained) or an executable (no symbols + are exported). + +See the [CHANGES](CHANGES) file for reports on completed work, and the [General +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for +planned and in-progress work. + + +### Reducer + +*Note:* The reducer is still under development. + +The reducer simplifies and shrinks a SPIR-V module with respect to a +user-supplied *interestingness function*. For example, given a large +SPIR-V module that cause some SPIR-V compiler to fail with a given +fatal error message, the reducer could be used to look for a smaller +version of the module that causes the compiler to fail with the same +fatal error message. + +To suggest an additional capability for the reducer, [file an +issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with +"Reducer:" as the start of its title. + + +### Fuzzer + +*Note:* The fuzzer is still under development. + +The fuzzer applies semantics-preserving transformations to a SPIR-V binary +module, to produce an equivalent module. The original and transformed modules +should produce essentially identical results when executed on identical inputs: +their results should differ only due to floating-point round-off, if at all. +Significant differences in results can pinpoint bugs in tools that process +SPIR-V binaries, such as miscompilations. This *metamorphic testing* approach +is similar to the method used by the [GraphicsFuzz +project](https://github.com/google/graphicsfuzz) for fuzzing of GLSL shaders. + +To suggest an additional capability for the fuzzer, [file an +issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with +"Fuzzer:" as the start of its title. + + +### Extras + +* [Utility filters](#utility-filters) +* Build target `spirv-tools-vimsyntax` generates file `spvasm.vim`. + Copy that file into your `$HOME/.vim/syntax` directory to get SPIR-V assembly syntax + highlighting in Vim. This build target is not built by default. + +## Contributing + +The SPIR-V Tools project is maintained by members of the The Khronos Group Inc., +and is hosted at https://github.com/KhronosGroup/SPIRV-Tools. + +Consider joining the `public_spirv_tools_dev@khronos.org` mailing list, via +[https://www.khronos.org/spir/spirv-tools-mailing-list/](https://www.khronos.org/spir/spirv-tools-mailing-list/). +The mailing list is used to discuss development plans for the SPIRV-Tools as an open source project. +Once discussion is resolved, +specific work is tracked via issues and sometimes in one of the +[projects][spirv-tools-projects]. + +(To provide feedback on the SPIR-V _specification_, file an issue on the +[SPIRV-Headers][spirv-headers] GitHub repository.) + +See [`docs/projects.md`](docs/projects.md) to see how we use the +[GitHub Project +feature](https://help.github.com/articles/tracking-the-progress-of-your-work-with-projects/) +to organize planned and in-progress work. + +Contributions via merge request are welcome. Changes should: +* Be provided under the [Apache 2.0](#license). +* You'll be prompted with a one-time "click-through" + [Khronos Open Source Contributor License Agreement][spirv-tools-cla] + (CLA) dialog as part of submitting your pull request or + other contribution to GitHub. +* Include tests to cover updated functionality. +* C++ code should follow the [Google C++ Style Guide][cpp-style-guide]. +* Code should be formatted with `clang-format`. + [kokoro/check-format/build.sh](kokoro/check-format/build.sh) + shows how to download it. Note that we currently use + `clang-format version 5.0.0` for SPIRV-Tools. Settings are defined by + the included [.clang-format](.clang-format) file. + +We intend to maintain a linear history on the GitHub `master` branch. + +### Source code organization + +* `example`: demo code of using SPIRV-Tools APIs +* `external/googletest`: Intended location for the + [googletest][googletest] sources, not provided +* `external/effcee`: Location of [Effcee][effcee] sources, if the `effcee` library + is not already configured by an enclosing project. +* `external/re2`: Location of [RE2][re2] sources, if the `re2` library is not already + configured by an enclosing project. + (The Effcee project already requires RE2.) +* `include/`: API clients should add this directory to the include search path +* `external/spirv-headers`: Intended location for + [SPIR-V headers][spirv-headers], not provided +* `include/spirv-tools/libspirv.h`: C API public interface +* `source/`: API implementation +* `test/`: Tests, using the [googletest][googletest] framework +* `tools/`: Command line executables + +Example of getting sources, assuming SPIRV-Tools is configured as a standalone project: + + git clone https://github.com/KhronosGroup/SPIRV-Tools.git spirv-tools + git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-tools/external/spirv-headers + git clone https://github.com/google/googletest.git spirv-tools/external/googletest + git clone https://github.com/google/effcee.git spirv-tools/external/effcee + git clone https://github.com/google/re2.git spirv-tools/external/re2 + +### Tests + +The project contains a number of tests, used to drive development +and ensure correctness. The tests are written using the +[googletest][googletest] framework. The `googletest` +source is not provided with this project. There are two ways to enable +tests: +* If SPIR-V Tools is configured as part of an enclosing project, then the + enclosing project should configure `googletest` before configuring SPIR-V Tools. +* If SPIR-V Tools is configured as a standalone project, then download the + `googletest` source into the `/external/googletest` directory before + configuring and building the project. + +*Note*: You must use a version of googletest that includes +[a fix][googletest-pull-612] for [googletest issue 610][googletest-issue-610]. +The fix is included on the googletest master branch any time after 2015-11-10. +In particular, googletest must be newer than version 1.7.0. + +### Dependency on Effcee + +Some tests depend on the [Effcee][effcee] library for stateful matching. +Effcee itself depends on [RE2][re2]. + +* If SPIRV-Tools is configured as part of a larger project that already uses + Effcee, then that project should include Effcee before SPIRV-Tools. +* Otherwise, SPIRV-Tools expects Effcee sources to appear in `external/effcee` + and RE2 sources to appear in `external/re2`. + + +## Build + +Instead of building manually, you can also download the binaries for your +platform directly from the [master-tot release][master-tot-release] on GitHub. +Those binaries are automatically uploaded by the buildbots after successful +testing and they always reflect the current top of the tree of the master +branch. + +In order to build the code, you first need to sync the external repositories +that it depends on. Assume that `` is the root directory of the +checked out code: + +```sh +cd +git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers +git clone https://github.com/google/effcee.git external/effcee +git clone https://github.com/google/re2.git external/re2 +git clone https://github.com/google/googletest.git external/googletest # optional + +``` + +*Note*: +The script `utils/git-sync-deps` can be used to checkout and/or update the +contents of the repos under `external/` instead of manually maintaining them. + +### Build using CMake +You can build The project using [CMake][cmake] to generate platform-specific +build configurations. + +```sh +cd +mkdir build && cd build +cmake [-G ] +``` + +Once the build files have been generated, build using your preferred +development environment. + +### Build using Bazel +You can also use [Bazel](https://bazel.build/) to build the project. +```sh +cd +bazel build :all +``` + +### Tools you'll need + +For building and testing SPIRV-Tools, the following tools should be +installed regardless of your OS: + +- [CMake](http://www.cmake.org/): if using CMake for generating compilation +targets, you need to install CMake Version 2.8.12 or later. +- [Python 3](http://www.python.org/): for utility scripts and running the test +suite. +- [Bazel](https://bazel.build/) (optional): if building the source with Bazel, +you need to install Bazel Version 0.29.1 on your machine. Other versions may +also work, but are not verified. + +SPIRV-Tools is regularly tested with the following compilers: + +On Linux +- GCC version 4.8.5 +- Clang version 3.8 + +On MacOS +- AppleClang 10.0 + +On Windows +- Visual Studio 2015 +- Visual Studio 2017 + +Other compilers or later versions may work, but they are not tested. + +### CMake options + +The following CMake options are supported: + +* `SPIRV_BUILD_FUZZER={ON|OFF}`, default `OFF` - Build the spirv-fuzz tool. +* `SPIRV_COLOR_TERMINAL={ON|OFF}`, default `ON` - Enables color console output. +* `SPIRV_SKIP_TESTS={ON|OFF}`, default `OFF`- Build only the library and + the command line tools. This will prevent the tests from being built. +* `SPIRV_SKIP_EXECUTABLES={ON|OFF}`, default `OFF`- Build only the library, not + the command line tools and tests. +* `SPIRV_USE_SANITIZER=`, default is no sanitizing - On UNIX + platforms with an appropriate version of `clang` this option enables the use + of the sanitizers documented [here][clang-sanitizers]. + This should only be used with a debug build. +* `SPIRV_WARN_EVERYTHING={ON|OFF}`, default `OFF` - On UNIX platforms enable + more strict warnings. The code might not compile with this option enabled. + For Clang, enables `-Weverything`. For GCC, enables `-Wpedantic`. + See [`CMakeLists.txt`](CMakeLists.txt) for details. +* `SPIRV_WERROR={ON|OFF}`, default `ON` - Forces a compilation error on any + warnings encountered by enabling the compiler-specific compiler front-end + option. No compiler front-end options are enabled when this option is OFF. + +Additionally, you can pass additional C preprocessor definitions to SPIRV-Tools +via setting `SPIRV_TOOLS_EXTRA_DEFINITIONS`. For example, by setting it to +`/D_ITERATOR_DEBUG_LEVEL=0` on Windows, you can disable checked iterators and +iterator debugging. + +### Android + +SPIR-V Tools supports building static libraries `libSPIRV-Tools.a` and +`libSPIRV-Tools-opt.a` for Android: + +``` +cd + +export ANDROID_NDK=/path/to/your/ndk + +mkdir build && cd build +mkdir libs +mkdir app + +$ANDROID_NDK/ndk-build -C ../android_test \ + NDK_PROJECT_PATH=. \ + NDK_LIBS_OUT=`pwd`/libs \ + NDK_APP_OUT=`pwd`/app +``` + +### Updating DEPS +Occasionally the entries in DEPS will need to be updated. This is done on demand +when there is a request to do this, often due to downstream breakages. There is +a script `utils/roll_deps.sh` provided, which will generate a patch with the +updated DEPS values. This will still need to be tested in your checkout to +confirm that there are no integration issues that need to be resolved. + +## Library + +### Usage + +The internals of the library use C++11 features, and are exposed via both a C +and C++ API. + +In order to use the library from an application, the include path should point +to `/include`, which will enable the application to include the +header `/include/spirv-tools/libspirv.h{|pp}` then linking against +the static library in `/source/libSPIRV-Tools.a` or +`/source/SPIRV-Tools.lib`. +For optimization, the header file is +`/include/spirv-tools/optimizer.hpp`, and the static library is +`/source/libSPIRV-Tools-opt.a` or +`/source/SPIRV-Tools-opt.lib`. + +* `SPIRV-Tools` CMake target: Creates the static library: + * `/source/libSPIRV-Tools.a` on Linux and OS X. + * `/source/libSPIRV-Tools.lib` on Windows. +* `SPIRV-Tools-opt` CMake target: Creates the static library: + * `/source/libSPIRV-Tools-opt.a` on Linux and OS X. + * `/source/libSPIRV-Tools-opt.lib` on Windows. + +#### Entry points + +The interfaces are still under development, and are expected to change. + +There are five main entry points into the library in the C interface: + +* `spvTextToBinary`: An assembler, translating text to a binary SPIR-V module. +* `spvBinaryToText`: A disassembler, translating a binary SPIR-V module to + text. +* `spvBinaryParse`: The entry point to a binary parser API. It issues callbacks + for the header and each parsed instruction. The disassembler is implemented + as a client of `spvBinaryParse`. +* `spvValidate` implements the validator functionality. *Incomplete* +* `spvValidateBinary` implements the validator functionality. *Incomplete* + +The C++ interface is comprised of three classes, `SpirvTools`, `Optimizer` and +`Linker`, all in the `spvtools` namespace. +* `SpirvTools` provides `Assemble`, `Disassemble`, and `Validate` methods. +* `Optimizer` provides methods for registering and running optimization passes. +* `Linker` provides methods for combining together multiple binaries. + +## Command line tools + +Command line tools, which wrap the above library functions, are provided to +assemble or disassemble shader files. It's a convention to name SPIR-V +assembly and binary files with suffix `.spvasm` and `.spv`, respectively. + +### Assembler tool + +The assembler reads the assembly language text, and emits the binary form. + +The standalone assembler is the exectuable called `spirv-as`, and is located in +`/tools/spirv-as`. The functionality of the assembler is implemented +by the `spvTextToBinary` library function. + +* `spirv-as` - the standalone assembler + * `/tools/as` + +Use option `-h` to print help. + +### Disassembler tool + +The disassembler reads the binary form, and emits assembly language text. + +The standalone disassembler is the executable called `spirv-dis`, and is located in +`/tools/spirv-dis`. The functionality of the disassembler is implemented +by the `spvBinaryToText` library function. + +* `spirv-dis` - the standalone disassembler + * `/tools/dis` + +Use option `-h` to print help. + +The output includes syntax colouring when printing to the standard output stream, +on Linux, Windows, and OS X. + +### Linker tool + +The linker combines multiple SPIR-V binary modules together, resulting in a single +binary module as output. + +This is a work in progress. +The linker does not support OpenCL program linking options related to math +flags. (See section 5.6.5.2 in OpenCL 1.2) + +* `spirv-link` - the standalone linker + * `/tools/link` + +### Optimizer tool + +The optimizer processes a SPIR-V binary module, applying transformations +in the specified order. + +This is a work in progress, with initially only few available transformations. + +* `spirv-opt` - the standalone optimizer + * `/tools/opt` + +### Validator tool + +*Warning:* This functionality is under development, and is incomplete. + +The standalone validator is the executable called `spirv-val`, and is located in +`/tools/spirv-val`. The functionality of the validator is implemented +by the `spvValidate` library function. + +The validator operates on the binary form. + +* `spirv-val` - the standalone validator + * `/tools/val` + +### Reducer tool + +The reducer shrinks a SPIR-V binary module, guided by a user-supplied +*interestingness test*. + +This is a work in progress, with initially only shrinks a module in a few ways. + +* `spirv-reduce` - the standalone reducer + * `/tools/reduce` + +Run `spirv-reduce --help` to see how to specify interestingness. + +### Fuzzer tool + +The fuzzer transforms a SPIR-V binary module into a semantically-equivalent +SPIR-V binary module by applying transformations in a randomized fashion. + +This is a work in progress, with initially only a few semantics-preserving +transformations. + +* `spirv-fuzz` - the standalone fuzzer + * `/tools/fuzz` + +Run `spirv-fuzz --help` for a detailed list of options. + +### Control flow dumper tool + +The control flow dumper prints the control flow graph for a SPIR-V module as a +[GraphViz](http://www.graphviz.org/) graph. + +This is experimental. + +* `spirv-cfg` - the control flow graph dumper + * `/tools/cfg` + +### Utility filters + +* `spirv-lesspipe.sh` - Automatically disassembles `.spv` binary files for the + `less` program, on compatible systems. For example, set the `LESSOPEN` + environment variable as follows, assuming both `spirv-lesspipe.sh` and + `spirv-dis` are on your executable search path: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s"' + ``` + Then you page through a disassembled module as follows: + ``` + less foo.spv + ``` + * The `spirv-lesspipe.sh` script will pass through any extra arguments to + `spirv-dis`. So, for example, you can turn off colours and friendly ID + naming as follows: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s" --no-color --raw-id' + ``` + +* [vim-spirv](https://github.com/kbenzie/vim-spirv) - A vim plugin which + supports automatic disassembly of `.spv` files using the `:edit` command and + assembly using the `:write` command. The plugin also provides additional + features which include; syntax highlighting; highlighting of all ID's matching + the ID under the cursor; and highlighting errors where the `Instruction` + operand of `OpExtInst` is used without an appropriate `OpExtInstImport`. + +* `50spirv-tools.el` - Automatically disassembles '.spv' binary files when + loaded into the emacs text editor, and re-assembles them when saved, + provided any modifications to the file are valid. This functionality + must be explicitly requested by defining the symbol + SPIRV_TOOLS_INSTALL_EMACS_HELPERS as follows: + ``` + cmake -DSPIRV_TOOLS_INSTALL_EMACS_HELPERS=true ... + ``` + + In addition, this helper is only installed if the directory /etc/emacs/site-start.d + exists, which is typically true if emacs is installed on the system. + + Note that symbol IDs are not currently preserved through a load/edit/save operation. + This may change if the ability is added to spirv-as. + + +### Tests + +Tests are only built when googletest is found. Use `ctest` to run all the +tests. + +## Future Work + + +_See the [projects pages](https://github.com/KhronosGroup/SPIRV-Tools/projects) +for more information._ + +### Assembler and disassembler + +* The disassembler could emit helpful annotations in comments. For example: + * Use variable name information from debug instructions to annotate + key operations on variables. + * Show control flow information by annotating `OpLabel` instructions with + that basic block's predecessors. +* Error messages could be improved. + +### Validator + +This is a work in progress. + +### Linker + +* The linker could accept math transformations such as allowing MADs, or other + math flags passed at linking-time in OpenCL. +* Linkage attributes can not be applied through a group. +* Check decorations of linked functions attributes. +* Remove dead instructions, such as OpName targeting imported symbols. + +## Licence + +Full license terms are in [LICENSE](LICENSE) +``` +Copyright (c) 2015-2016 The Khronos Group Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +[spirv-tools-cla]: https://cla-assistant.io/KhronosGroup/SPIRV-Tools +[spirv-tools-projects]: https://github.com/KhronosGroup/SPIRV-Tools/projects +[spirv-tools-mailing-list]: https://www.khronos.org/spir/spirv-tools-mailing-list +[spirv-registry]: https://www.khronos.org/registry/spir-v/ +[spirv-headers]: https://github.com/KhronosGroup/SPIRV-Headers +[googletest]: https://github.com/google/googletest +[googletest-pull-612]: https://github.com/google/googletest/pull/612 +[googletest-issue-610]: https://github.com/google/googletest/issues/610 +[effcee]: https://github.com/google/effcee +[re2]: https://github.com/google/re2 +[CMake]: https://cmake.org/ +[cpp-style-guide]: https://google.github.io/styleguide/cppguide.html +[clang-sanitizers]: http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation +[master-tot-release]: https://github.com/KhronosGroup/SPIRV-Tools/releases/tag/master-tot diff --git a/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h b/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h new file mode 100644 index 000000000..9a68f2761 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/OpenCLDebugInfo100.h @@ -0,0 +1,149 @@ +// 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. + +#ifndef SPIRV_EXTINST_OpenCLDebugInfo100_H_ +#define SPIRV_EXTINST_OpenCLDebugInfo100_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { OpenCLDebugInfo100Version = 200, OpenCLDebugInfo100Version_BitWidthPadding = 0x7fffffff }; +enum { OpenCLDebugInfo100Revision = 2, OpenCLDebugInfo100Revision_BitWidthPadding = 0x7fffffff }; + +enum OpenCLDebugInfo100Instructions { + OpenCLDebugInfo100DebugInfoNone = 0, + OpenCLDebugInfo100DebugCompilationUnit = 1, + OpenCLDebugInfo100DebugTypeBasic = 2, + OpenCLDebugInfo100DebugTypePointer = 3, + OpenCLDebugInfo100DebugTypeQualifier = 4, + OpenCLDebugInfo100DebugTypeArray = 5, + OpenCLDebugInfo100DebugTypeVector = 6, + OpenCLDebugInfo100DebugTypedef = 7, + OpenCLDebugInfo100DebugTypeFunction = 8, + OpenCLDebugInfo100DebugTypeEnum = 9, + OpenCLDebugInfo100DebugTypeComposite = 10, + OpenCLDebugInfo100DebugTypeMember = 11, + OpenCLDebugInfo100DebugTypeInheritance = 12, + OpenCLDebugInfo100DebugTypePtrToMember = 13, + OpenCLDebugInfo100DebugTypeTemplate = 14, + OpenCLDebugInfo100DebugTypeTemplateParameter = 15, + OpenCLDebugInfo100DebugTypeTemplateTemplateParameter = 16, + OpenCLDebugInfo100DebugTypeTemplateParameterPack = 17, + OpenCLDebugInfo100DebugGlobalVariable = 18, + OpenCLDebugInfo100DebugFunctionDeclaration = 19, + OpenCLDebugInfo100DebugFunction = 20, + OpenCLDebugInfo100DebugLexicalBlock = 21, + OpenCLDebugInfo100DebugLexicalBlockDiscriminator = 22, + OpenCLDebugInfo100DebugScope = 23, + OpenCLDebugInfo100DebugNoScope = 24, + OpenCLDebugInfo100DebugInlinedAt = 25, + OpenCLDebugInfo100DebugLocalVariable = 26, + OpenCLDebugInfo100DebugInlinedVariable = 27, + OpenCLDebugInfo100DebugDeclare = 28, + OpenCLDebugInfo100DebugValue = 29, + OpenCLDebugInfo100DebugOperation = 30, + OpenCLDebugInfo100DebugExpression = 31, + OpenCLDebugInfo100DebugMacroDef = 32, + OpenCLDebugInfo100DebugMacroUndef = 33, + OpenCLDebugInfo100DebugSource = 35, + OpenCLDebugInfo100InstructionsMax = 0x7ffffff +}; + + +enum OpenCLDebugInfo100DebugInfoFlags { + OpenCLDebugInfo100FlagIsProtected = 0x01, + OpenCLDebugInfo100FlagIsPrivate = 0x02, + OpenCLDebugInfo100FlagIsPublic = 0x03, + OpenCLDebugInfo100FlagIsLocal = 0x04, + OpenCLDebugInfo100FlagIsDefinition = 0x08, + OpenCLDebugInfo100FlagFwdDecl = 0x10, + OpenCLDebugInfo100FlagArtificial = 0x20, + OpenCLDebugInfo100FlagExplicit = 0x40, + OpenCLDebugInfo100FlagPrototyped = 0x80, + OpenCLDebugInfo100FlagObjectPointer = 0x100, + OpenCLDebugInfo100FlagStaticMember = 0x200, + OpenCLDebugInfo100FlagIndirectVariable = 0x400, + OpenCLDebugInfo100FlagLValueReference = 0x800, + OpenCLDebugInfo100FlagRValueReference = 0x1000, + OpenCLDebugInfo100FlagIsOptimized = 0x2000, + OpenCLDebugInfo100FlagIsEnumClass = 0x4000, + OpenCLDebugInfo100FlagTypePassByValue = 0x8000, + OpenCLDebugInfo100FlagTypePassByReference = 0x10000, + OpenCLDebugInfo100DebugInfoFlagsMax = 0x7ffffff +}; + +enum OpenCLDebugInfo100DebugBaseTypeAttributeEncoding { + OpenCLDebugInfo100Unspecified = 0, + OpenCLDebugInfo100Address = 1, + OpenCLDebugInfo100Boolean = 2, + OpenCLDebugInfo100Float = 3, + OpenCLDebugInfo100Signed = 4, + OpenCLDebugInfo100SignedChar = 5, + OpenCLDebugInfo100Unsigned = 6, + OpenCLDebugInfo100UnsignedChar = 7, + OpenCLDebugInfo100DebugBaseTypeAttributeEncodingMax = 0x7ffffff +}; + +enum OpenCLDebugInfo100DebugCompositeType { + OpenCLDebugInfo100Class = 0, + OpenCLDebugInfo100Structure = 1, + OpenCLDebugInfo100Union = 2, + OpenCLDebugInfo100DebugCompositeTypeMax = 0x7ffffff +}; + +enum OpenCLDebugInfo100DebugTypeQualifier { + OpenCLDebugInfo100ConstType = 0, + OpenCLDebugInfo100VolatileType = 1, + OpenCLDebugInfo100RestrictType = 2, + OpenCLDebugInfo100AtomicType = 3, + OpenCLDebugInfo100DebugTypeQualifierMax = 0x7ffffff +}; + +enum OpenCLDebugInfo100DebugOperation { + OpenCLDebugInfo100Deref = 0, + OpenCLDebugInfo100Plus = 1, + OpenCLDebugInfo100Minus = 2, + OpenCLDebugInfo100PlusUconst = 3, + OpenCLDebugInfo100BitPiece = 4, + OpenCLDebugInfo100Swap = 5, + OpenCLDebugInfo100Xderef = 6, + OpenCLDebugInfo100StackValue = 7, + OpenCLDebugInfo100Constu = 8, + OpenCLDebugInfo100Fragment = 9, + OpenCLDebugInfo100DebugOperationMax = 0x7ffffff +}; + +enum OpenCLDebugInfo100DebugImportedEntity { + OpenCLDebugInfo100ImportedModule = 0, + OpenCLDebugInfo100ImportedDeclaration = 1, + OpenCLDebugInfo100DebugImportedEntityMax = 0x7ffffff +}; + + +#ifdef __cplusplus +} +#endif + +#endif // SPIRV_EXTINST_OpenCLDebugInfo100_H_ \ No newline at end of file diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 8352e314c..a22227096 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-192-g983b5b4f" +"v2020.2-dev", "SPIRV-Tools v2020.2-dev b53f48f92c539b6f8561e36023bbf281d8dfeda8" diff --git a/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc b/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc index 49dc61907..ae6471c94 100644 --- a/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc +++ b/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc @@ -62,6 +62,8 @@ const char* ExtensionToString(Extension extension) { return "SPV_KHR_multiview"; case Extension::kSPV_KHR_no_integer_wrap_decoration: return "SPV_KHR_no_integer_wrap_decoration"; + case Extension::kSPV_KHR_non_semantic_info: + return "SPV_KHR_non_semantic_info"; case Extension::kSPV_KHR_physical_storage_buffer: return "SPV_KHR_physical_storage_buffer"; case Extension::kSPV_KHR_post_depth_coverage: @@ -119,8 +121,8 @@ const char* ExtensionToString(Extension extension) { bool GetExtensionFromString(const char* str, Extension* extension) { - static const char* known_ext_strs[] = { "SPV_AMD_gcn_shader", "SPV_AMD_gpu_shader_half_float", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_AMD_gpu_shader_int16", "SPV_AMD_shader_ballot", "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_fragment_mask", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_texture_gather_bias_lod", "SPV_EXT_demote_to_helper_invocation", "SPV_EXT_descriptor_indexing", "SPV_EXT_fragment_fully_covered", "SPV_EXT_fragment_invocation_density", "SPV_EXT_fragment_shader_interlock", "SPV_EXT_physical_storage_buffer", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_GOOGLE_user_type", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_media_block_io", "SPV_INTEL_shader_integer_functions2", "SPV_INTEL_subgroups", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_device_group", "SPV_KHR_float_controls", "SPV_KHR_multiview", "SPV_KHR_no_integer_wrap_decoration", "SPV_KHR_physical_storage_buffer", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_KHR_shader_ballot", "SPV_KHR_shader_clock", "SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_subgroup_vote", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_compute_shader_derivatives", "SPV_NV_cooperative_matrix", "SPV_NV_fragment_shader_barycentric", "SPV_NV_geometry_shader_passthrough", "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_NV_sample_mask_override_coverage", "SPV_NV_shader_image_footprint", "SPV_NV_shader_sm_builtins", "SPV_NV_shader_subgroup_partitioned", "SPV_NV_shading_rate", "SPV_NV_stereo_view_rendering", "SPV_NV_viewport_array2", "SPV_VALIDATOR_ignore_type_decl_unique" }; - static const Extension known_ext_ids[] = { Extension::kSPV_AMD_gcn_shader, Extension::kSPV_AMD_gpu_shader_half_float, Extension::kSPV_AMD_gpu_shader_half_float_fetch, Extension::kSPV_AMD_gpu_shader_int16, Extension::kSPV_AMD_shader_ballot, Extension::kSPV_AMD_shader_explicit_vertex_parameter, Extension::kSPV_AMD_shader_fragment_mask, Extension::kSPV_AMD_shader_image_load_store_lod, Extension::kSPV_AMD_shader_trinary_minmax, Extension::kSPV_AMD_texture_gather_bias_lod, Extension::kSPV_EXT_demote_to_helper_invocation, Extension::kSPV_EXT_descriptor_indexing, Extension::kSPV_EXT_fragment_fully_covered, Extension::kSPV_EXT_fragment_invocation_density, Extension::kSPV_EXT_fragment_shader_interlock, Extension::kSPV_EXT_physical_storage_buffer, Extension::kSPV_EXT_shader_stencil_export, Extension::kSPV_EXT_shader_viewport_index_layer, Extension::kSPV_GOOGLE_decorate_string, Extension::kSPV_GOOGLE_hlsl_functionality1, Extension::kSPV_GOOGLE_user_type, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_shader_integer_functions2, Extension::kSPV_INTEL_subgroups, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_multiview, Extension::kSPV_KHR_no_integer_wrap_decoration, Extension::kSPV_KHR_physical_storage_buffer, Extension::kSPV_KHR_post_depth_coverage, Extension::kSPV_KHR_shader_atomic_counter_ops, Extension::kSPV_KHR_shader_ballot, Extension::kSPV_KHR_shader_clock, Extension::kSPV_KHR_shader_draw_parameters, Extension::kSPV_KHR_storage_buffer_storage_class, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_compute_shader_derivatives, Extension::kSPV_NV_cooperative_matrix, Extension::kSPV_NV_fragment_shader_barycentric, Extension::kSPV_NV_geometry_shader_passthrough, Extension::kSPV_NV_mesh_shader, Extension::kSPV_NV_ray_tracing, Extension::kSPV_NV_sample_mask_override_coverage, Extension::kSPV_NV_shader_image_footprint, Extension::kSPV_NV_shader_sm_builtins, Extension::kSPV_NV_shader_subgroup_partitioned, Extension::kSPV_NV_shading_rate, Extension::kSPV_NV_stereo_view_rendering, Extension::kSPV_NV_viewport_array2, Extension::kSPV_VALIDATOR_ignore_type_decl_unique }; + static const char* known_ext_strs[] = { "SPV_AMD_gcn_shader", "SPV_AMD_gpu_shader_half_float", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_AMD_gpu_shader_int16", "SPV_AMD_shader_ballot", "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_fragment_mask", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_texture_gather_bias_lod", "SPV_EXT_demote_to_helper_invocation", "SPV_EXT_descriptor_indexing", "SPV_EXT_fragment_fully_covered", "SPV_EXT_fragment_invocation_density", "SPV_EXT_fragment_shader_interlock", "SPV_EXT_physical_storage_buffer", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_GOOGLE_user_type", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_media_block_io", "SPV_INTEL_shader_integer_functions2", "SPV_INTEL_subgroups", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_device_group", "SPV_KHR_float_controls", "SPV_KHR_multiview", "SPV_KHR_no_integer_wrap_decoration", "SPV_KHR_non_semantic_info", "SPV_KHR_physical_storage_buffer", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_KHR_shader_ballot", "SPV_KHR_shader_clock", "SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_subgroup_vote", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_compute_shader_derivatives", "SPV_NV_cooperative_matrix", "SPV_NV_fragment_shader_barycentric", "SPV_NV_geometry_shader_passthrough", "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_NV_sample_mask_override_coverage", "SPV_NV_shader_image_footprint", "SPV_NV_shader_sm_builtins", "SPV_NV_shader_subgroup_partitioned", "SPV_NV_shading_rate", "SPV_NV_stereo_view_rendering", "SPV_NV_viewport_array2", "SPV_VALIDATOR_ignore_type_decl_unique" }; + static const Extension known_ext_ids[] = { Extension::kSPV_AMD_gcn_shader, Extension::kSPV_AMD_gpu_shader_half_float, Extension::kSPV_AMD_gpu_shader_half_float_fetch, Extension::kSPV_AMD_gpu_shader_int16, Extension::kSPV_AMD_shader_ballot, Extension::kSPV_AMD_shader_explicit_vertex_parameter, Extension::kSPV_AMD_shader_fragment_mask, Extension::kSPV_AMD_shader_image_load_store_lod, Extension::kSPV_AMD_shader_trinary_minmax, Extension::kSPV_AMD_texture_gather_bias_lod, Extension::kSPV_EXT_demote_to_helper_invocation, Extension::kSPV_EXT_descriptor_indexing, Extension::kSPV_EXT_fragment_fully_covered, Extension::kSPV_EXT_fragment_invocation_density, Extension::kSPV_EXT_fragment_shader_interlock, Extension::kSPV_EXT_physical_storage_buffer, Extension::kSPV_EXT_shader_stencil_export, Extension::kSPV_EXT_shader_viewport_index_layer, Extension::kSPV_GOOGLE_decorate_string, Extension::kSPV_GOOGLE_hlsl_functionality1, Extension::kSPV_GOOGLE_user_type, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_shader_integer_functions2, Extension::kSPV_INTEL_subgroups, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_multiview, Extension::kSPV_KHR_no_integer_wrap_decoration, Extension::kSPV_KHR_non_semantic_info, Extension::kSPV_KHR_physical_storage_buffer, Extension::kSPV_KHR_post_depth_coverage, Extension::kSPV_KHR_shader_atomic_counter_ops, Extension::kSPV_KHR_shader_ballot, Extension::kSPV_KHR_shader_clock, Extension::kSPV_KHR_shader_draw_parameters, Extension::kSPV_KHR_storage_buffer_storage_class, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_compute_shader_derivatives, Extension::kSPV_NV_cooperative_matrix, Extension::kSPV_NV_fragment_shader_barycentric, Extension::kSPV_NV_geometry_shader_passthrough, Extension::kSPV_NV_mesh_shader, Extension::kSPV_NV_ray_tracing, Extension::kSPV_NV_sample_mask_override_coverage, Extension::kSPV_NV_shader_image_footprint, Extension::kSPV_NV_shader_sm_builtins, Extension::kSPV_NV_shader_subgroup_partitioned, Extension::kSPV_NV_shading_rate, Extension::kSPV_NV_stereo_view_rendering, Extension::kSPV_NV_viewport_array2, Extension::kSPV_VALIDATOR_ignore_type_decl_unique }; const auto b = std::begin(known_ext_strs); const auto e = std::end(known_ext_strs); const auto found = std::equal_range( diff --git a/3rdparty/spirv-tools/include/generated/extension_enum.inc b/3rdparty/spirv-tools/include/generated/extension_enum.inc index e34f942bb..fb66e2d63 100644 --- a/3rdparty/spirv-tools/include/generated/extension_enum.inc +++ b/3rdparty/spirv-tools/include/generated/extension_enum.inc @@ -29,6 +29,7 @@ kSPV_KHR_device_group, kSPV_KHR_float_controls, kSPV_KHR_multiview, kSPV_KHR_no_integer_wrap_decoration, +kSPV_KHR_non_semantic_info, kSPV_KHR_physical_storage_buffer, kSPV_KHR_post_depth_coverage, kSPV_KHR_shader_atomic_counter_ops, diff --git a/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc b/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc new file mode 100644 index 000000000..4bde097af --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/opencl.debuginfo.100.insts.inc @@ -0,0 +1,39 @@ + + +static const spv_ext_inst_desc_t opencl_debuginfo_100_entries[] = { + {"DebugInfoNone", 0, 0, nullptr, {SPV_OPERAND_TYPE_NONE}}, + {"DebugCompilationUnit", 1, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SOURCE_LANGUAGE, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeBasic", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypePointer", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeQualifier", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeArray", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeVector", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypedef", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeFunction", 8, 0, nullptr, {SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeEnum", 9, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeComposite", 10, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeMember", 11, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeInheritance", 12, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypePtrToMember", 13, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplate", 14, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateParameter", 15, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateTemplateParameter", 16, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateParameterPack", 17, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugGlobalVariable", 18, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugFunctionDeclaration", 19, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugFunction", 20, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLexicalBlock", 21, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLexicalBlockDiscriminator", 22, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugScope", 23, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugNoScope", 24, 0, nullptr, {SPV_OPERAND_TYPE_NONE}}, + {"DebugInlinedAt", 25, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLocalVariable", 26, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugInlinedVariable", 27, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugDeclare", 28, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugValue", 29, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugOperation", 30, 0, nullptr, {SPV_OPERAND_TYPE_DEBUG_OPERATION, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugExpression", 31, 0, nullptr, {SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugMacroDef", 32, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugMacroUndef", 33, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugSource", 35, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}} +}; \ No newline at end of file diff --git a/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc b/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc index eb4bc6712..5301332ce 100644 --- a/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc +++ b/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc @@ -918,6 +918,69 @@ static const spv_operand_desc_t pygen_variable_DebugOperationEntries[] = { {"Constu", 8, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} }; +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugInfoFlagsEntries[] = { + {"FlagIsProtected", 0x01, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsPrivate", 0x02, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsPublic", 0x03, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsLocal", 0x04, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsDefinition", 0x08, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagFwdDecl", 0x10, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagArtificial", 0x20, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagExplicit", 0x40, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagPrototyped", 0x80, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagObjectPointer", 0x100, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagStaticMember", 0x200, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIndirectVariable", 0x400, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagLValueReference", 0x800, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagRValueReference", 0x1000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsOptimized", 0x2000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagIsEnumClass", 0x4000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagTypePassByValue", 0x8000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"FlagTypePassByReference", 0x10000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugBaseTypeAttributeEncodingEntries[] = { + {"Unspecified", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Address", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Boolean", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Float", 3, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Signed", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"SignedChar", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Unsigned", 6, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"UnsignedChar", 7, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugCompositeTypeEntries[] = { + {"Class", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Structure", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Union", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugTypeQualifierEntries[] = { + {"ConstType", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"VolatileType", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"RestrictType", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"AtomicType", 3, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugOperationEntries[] = { + {"Deref", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Plus", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Minus", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"PlusUconst", 3, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"BitPiece", 4, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Swap", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Xderef", 6, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"StackValue", 7, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Constu", 8, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"Fragment", 9, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_CLDEBUG100_DebugImportedEntityEntries[] = { + {"ImportedModule", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}, + {"ImportedDeclaration", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu} +}; + static const spv_operand_desc_group_t pygen_variable_OperandInfoTable[] = { {SPV_OPERAND_TYPE_IMAGE, ARRAY_SIZE(pygen_variable_ImageOperandsEntries), pygen_variable_ImageOperandsEntries}, {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, ARRAY_SIZE(pygen_variable_FPFastMathModeEntries), pygen_variable_FPFastMathModeEntries}, @@ -954,6 +1017,12 @@ static const spv_operand_desc_group_t pygen_variable_OperandInfoTable[] = { {SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, ARRAY_SIZE(pygen_variable_DebugCompositeTypeEntries), pygen_variable_DebugCompositeTypeEntries}, {SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, ARRAY_SIZE(pygen_variable_DebugTypeQualifierEntries), pygen_variable_DebugTypeQualifierEntries}, {SPV_OPERAND_TYPE_DEBUG_OPERATION, ARRAY_SIZE(pygen_variable_DebugOperationEntries), pygen_variable_DebugOperationEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugInfoFlagsEntries), pygen_variable_CLDEBUG100_DebugInfoFlagsEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugBaseTypeAttributeEncodingEntries), pygen_variable_CLDEBUG100_DebugBaseTypeAttributeEncodingEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugCompositeTypeEntries), pygen_variable_CLDEBUG100_DebugCompositeTypeEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugTypeQualifierEntries), pygen_variable_CLDEBUG100_DebugTypeQualifierEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugOperationEntries), pygen_variable_CLDEBUG100_DebugOperationEntries}, + {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY, ARRAY_SIZE(pygen_variable_CLDEBUG100_DebugImportedEntityEntries), pygen_variable_CLDEBUG100_DebugImportedEntityEntries}, {SPV_OPERAND_TYPE_OPTIONAL_IMAGE, ARRAY_SIZE(pygen_variable_ImageOperandsEntries), pygen_variable_ImageOperandsEntries}, {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, ARRAY_SIZE(pygen_variable_MemoryAccessEntries), pygen_variable_MemoryAccessEntries}, {SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER, ARRAY_SIZE(pygen_variable_AccessQualifierEntries), pygen_variable_AccessQualifierEntries} diff --git a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp index 681d00887..2dcb33316 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/instrument.hpp @@ -35,10 +35,9 @@ namespace spvtools { // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized // by InstBindlessCheckPass. // -// kInst2* values support version 2 of the output record format. These should -// be used if available and version 2 is enabled. Version 1 is DEPRECATED. -// Specifically, version 1 uses two words for the stage-specific section of -// the output record; version 2 uses three words. +// kInst2* values support version 2 of the output record format and were used +// for the transition to this format. These values have now been transferred +// to the original kInst* values. The kInst2* values are therefore DEPRECATED. // // 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 @@ -124,7 +123,7 @@ static const int kInstRayTracingOutLaunchIdY = kInstCommonOutCnt + 1; static const int kInstRayTracingOutLaunchIdZ = kInstCommonOutCnt + 2; // Size of Common and Stage-specific Members -static const int kInstStageOutCnt = kInstCommonOutCnt + 2; +static const int kInstStageOutCnt = kInstCommonOutCnt + 3; static const int kInst2StageOutCnt = kInstCommonOutCnt + 3; // Validation Error Code Offset @@ -160,6 +159,10 @@ static const int kInst2BindlessUninitOutCnt = kInst2StageOutCnt + 3; // A buffer address unalloc error will output the 64-bit pointer in // two 32-bit pieces, lower bits first. +static const int kInstBuffAddrUnallocOutDescPtrLo = kInstStageOutCnt + 1; +static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2; +static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3; + static const int kInst2BuffAddrUnallocOutDescPtrLo = kInst2StageOutCnt + 1; static const int kInst2BuffAddrUnallocOutDescPtrHi = kInst2StageOutCnt + 2; static const int kInst2BuffAddrUnallocOutCnt = kInst2StageOutCnt + 3; diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h index 2f4c7d60b..d63f3634d 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h @@ -225,13 +225,23 @@ typedef enum spv_operand_type_t { // A sequence of zero or more pairs of (Id, Literal integer) LAST_VARIABLE(SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER), - // The following are concrete enum types. + // The following are concrete enum types from the DebugInfo extended + // instruction set. SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, // DebugInfo Sec 3.2. A mask. SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, // DebugInfo Sec 3.3 SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, // DebugInfo Sec 3.4 SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, // DebugInfo Sec 3.5 SPV_OPERAND_TYPE_DEBUG_OPERATION, // DebugInfo Sec 3.6 + // The following are concrete enum types from the OpenCL.DebugInfo.100 + // extended instruction set. + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS, // Sec 3.2. A Mask + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, // Sec 3.3 + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE, // Sec 3.4 + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER, // Sec 3.5 + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION, // Sec 3.6 + SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY, // Sec 3.7 + // This is a sentinel value, and does not represent an operand type. // It should come last. SPV_OPERAND_TYPE_NUM_OPERAND_TYPES, @@ -248,6 +258,12 @@ typedef enum spv_ext_inst_type_t { SPV_EXT_INST_TYPE_SPV_AMD_GCN_SHADER, SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT, SPV_EXT_INST_TYPE_DEBUGINFO, + SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, + + // Multiple distinct extended instruction set types could return this + // value, if they are prefixed with NonSemantic. and are otherwise + // unrecognised + SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN, SPV_FORCE_32_BIT_ENUM(spv_ext_inst_type_t) } spv_ext_inst_type_t; @@ -403,8 +419,17 @@ SPIRV_TOOLS_EXPORT const char* spvSoftwareVersionString(void); SPIRV_TOOLS_EXPORT const char* spvSoftwareVersionDetailsString(void); // Certain target environments impose additional restrictions on SPIR-V, so it's -// often necessary to specify which one applies. SPV_ENV_UNIVERSAL means +// often necessary to specify which one applies. SPV_ENV_UNIVERSAL_* implies an // environment-agnostic SPIR-V. +// +// When an API method needs to derive a SPIR-V version from a target environment +// (from the spv_context object), the method will choose the highest version of +// SPIR-V supported by the target environment. Examples: +// SPV_ENV_VULKAN_1_0 -> SPIR-V 1.0 +// SPV_ENV_VULKAN_1_1 -> SPIR-V 1.3 +// SPV_ENV_VULKAN_1_1_SPIRV_1_4 -> SPIR-V 1.4 +// SPV_ENV_VULKAN_1_2 -> SPIR-V 1.5 +// Consult the description of API entry points for specific rules. typedef enum { SPV_ENV_UNIVERSAL_1_0, // SPIR-V 1.0 latest revision, no other restrictions. SPV_ENV_VULKAN_1_0, // Vulkan 1.0 latest revision. @@ -432,8 +457,12 @@ typedef enum { SPV_ENV_VULKAN_1_1, // Vulkan 1.1 latest revision. SPV_ENV_WEBGPU_0, // Work in progress WebGPU 1.0. SPV_ENV_UNIVERSAL_1_4, // SPIR-V 1.4 latest revision, no other restrictions. - SPV_ENV_VULKAN_1_1_SPIRV_1_4, // Vulkan 1.1 with SPIR-V 1.4 binary. + + // Vulkan 1.1 with VK_KHR_spirv_1_4, i.e. SPIR-V 1.4 binary. + SPV_ENV_VULKAN_1_1_SPIRV_1_4, + SPV_ENV_UNIVERSAL_1_5, // SPIR-V 1.5 latest revision, no other restrictions. + SPV_ENV_VULKAN_1_2, // Vulkan 1.2 latest revision. } spv_target_env; // SPIR-V Validator can be parameterized with the following Universal Limits. @@ -456,7 +485,24 @@ SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env); // false and sets *env to SPV_ENV_UNIVERSAL_1_0. SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env); -// Creates a context object. Returns null if env is invalid. +// Determines the target env value with the least features but which enables +// the given Vulkan and SPIR-V versions. If such a target is supported, returns +// true and writes the value to |env|, otherwise returns false. +// +// The Vulkan version is given as an unsigned 32-bit number as specified in +// Vulkan section "29.2.1 Version Numbers": the major version number appears +// in bits 22 to 21, and the minor version is in bits 12 to 21. The SPIR-V +// version is given in the SPIR-V version header word: major version in bits +// 16 to 23, and minor version in bits 8 to 15. +SPIRV_TOOLS_EXPORT bool spvParseVulkanEnv(uint32_t vulkan_ver, + uint32_t spirv_ver, + spv_target_env* env); + +// Creates a context object for most of the SPIRV-Tools API. +// Returns null if env is invalid. +// +// See specific API calls for how the target environment is interpeted +// (particularly assembly and validation). SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env); // Destroys the given context object. @@ -637,6 +683,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation( // be stored into *binary. Any error will be written into *diagnostic if // diagnostic is non-null, otherwise the context's message consumer will be // used. The generated binary is independent of the context and may outlive it. +// The SPIR-V binary version is set to the highest version of SPIR-V supported +// by the context's target environment. SPIRV_TOOLS_EXPORT spv_result_t spvTextToBinary(const spv_const_context context, const char* text, const size_t length, @@ -674,6 +722,12 @@ SPIRV_TOOLS_EXPORT void spvBinaryDestroy(spv_binary binary); // Validates a SPIR-V binary for correctness. Any errors will be written into // *diagnostic if diagnostic is non-null, otherwise the context's message // consumer will be used. +// +// Validate for SPIR-V spec rules for the SPIR-V version named in the +// binary's header (at word offset 1). Additionally, if the context target +// environment is a client API (such as Vulkan 1.1), then validate for that +// client API version, to the extent that it is verifiable from data in the +// binary itself. SPIRV_TOOLS_EXPORT spv_result_t spvValidate(const spv_const_context context, const spv_const_binary binary, spv_diagnostic* diagnostic); @@ -681,6 +735,12 @@ SPIRV_TOOLS_EXPORT spv_result_t spvValidate(const spv_const_context context, // Validates a SPIR-V binary for correctness. Uses the provided Validator // options. Any errors will be written into *diagnostic if diagnostic is // non-null, otherwise the context's message consumer will be used. +// +// Validate for SPIR-V spec rules for the SPIR-V version named in the +// binary's header (at word offset 1). Additionally, if the context target +// environment is a client API (such as Vulkan 1.1), then validate for that +// client API version, to the extent that it is verifiable from data in the +// binary itself, or in the validator options. SPIRV_TOOLS_EXPORT spv_result_t spvValidateWithOptions( const spv_const_context context, const spv_const_validator_options options, const spv_const_binary binary, spv_diagnostic* diagnostic); diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp index ceadef8fb..5e1819ee6 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp @@ -36,6 +36,9 @@ class Context { public: // Constructs a context targeting the given environment |env|. // + // See specific API calls for how the target environment is interpeted + // (particularly assembly and validation). + // // The constructed instance will have an empty message consumer, which just // ignores all messages from the library. Use SetMessageConsumer() to supply // one if messages are of concern. @@ -279,16 +282,20 @@ class SpirvTools { // Assembles the given assembly |text| and writes the result to |binary|. // Returns true on successful assembling. |binary| will be kept untouched if // assembling is unsuccessful. + // The SPIR-V binary version is set to the highest version of SPIR-V supported + // by the target environment with which this SpirvTools object was created. bool Assemble(const std::string& text, std::vector* binary, uint32_t options = kDefaultAssembleOption) const; // |text_size| specifies the number of bytes in |text|. A terminating null // character is not required to present in |text| as long as |text| is valid. + // The SPIR-V binary version is set to the highest version of SPIR-V supported + // by the target environment with which this SpirvTools object was created. bool Assemble(const char* text, size_t text_size, std::vector* binary, uint32_t options = kDefaultAssembleOption) const; // Disassembles the given SPIR-V |binary| with the given |options| and writes - // the assembly to |text|. Returns ture on successful disassembling. |text| + // the assembly to |text|. Returns true on successful disassembling. |text| // will be kept untouched if diassembling is unsuccessful. bool Disassemble(const std::vector& binary, std::string* text, uint32_t options = kDefaultDisassembleOption) const; @@ -300,10 +307,26 @@ class SpirvTools { // Validates the given SPIR-V |binary|. Returns true if no issues are found. // Otherwise, returns false and communicates issues via the message consumer // registered. + // Validates for SPIR-V spec rules for the SPIR-V version named in the + // binary's header (at word offset 1). Additionally, if the target + // environment is a client API (such as Vulkan 1.1), then validate for that + // client API version, to the extent that it is verifiable from data in the + // binary itself. bool Validate(const std::vector& binary) const; + // Like the previous overload, but provides the binary as a pointer and size: // |binary_size| specifies the number of words in |binary|. + // Validates for SPIR-V spec rules for the SPIR-V version named in the + // binary's header (at word offset 1). Additionally, if the target + // environment is a client API (such as Vulkan 1.1), then validate for that + // client API version, to the extent that it is verifiable from data in the + // binary itself. bool Validate(const uint32_t* binary, size_t binary_size) const; // Like the previous overload, but takes an options object. + // Validates for SPIR-V spec rules for the SPIR-V version named in the + // binary's header (at word offset 1). Additionally, if the target + // environment is a client API (such as Vulkan 1.1), then validate for that + // client API version, to the extent that it is verifiable from data in the + // binary itself, or in the validator options. bool Validate(const uint32_t* binary, size_t binary_size, spv_validator_options options) const; diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index 2998ec1ec..c31ccef8c 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -65,9 +65,9 @@ class Optimizer { // Constructs an instance with the given target |env|, which is used to decode // the binaries to be optimized later. // - // The constructed instance will have an empty message consumer, which just - // ignores all messages from the library. Use SetMessageConsumer() to supply - // one if messages are of concern. + // The instance will have an empty message consumer, which ignores all + // messages from the library. Use SetMessageConsumer() to supply a consumer + // if messages are of concern. // // For collections of passes that are meant to transform the input into // another execution environment, then the source environment should be @@ -164,17 +164,26 @@ class Optimizer { bool FlagHasValidForm(const std::string& flag) const; // Allows changing, after creation time, the target environment to be - // optimized for. Should be called before calling Run(). + // optimized for and validated. Should be called before calling Run(). void SetTargetEnv(const spv_target_env env); // Optimizes the given SPIR-V module |original_binary| and writes the - // optimized binary into |optimized_binary|. + // optimized binary into |optimized_binary|. The optimized binary uses + // the same SPIR-V version as the original binary. + // // Returns true on successful optimization, whether or not the module is // modified. Returns false if |original_binary| fails to validate or if errors // occur when processing |original_binary| using any of the registered passes. // In that case, no further passes are executed and the contents in // |optimized_binary| may be invalid. // + // By default, the binary is validated before any transforms are performed, + // and optionally after each transform. Validation uses SPIR-V spec rules + // for the SPIR-V version named in the binary's header (at word offset 1). + // Additionally, if the target environment is a client API (such as + // Vulkan 1.1), then validate for that client API version, to the extent + // that it is verifiable from data in the binary itself. + // // It's allowed to alias |original_binary| to the start of |optimized_binary|. bool Run(const uint32_t* original_binary, size_t original_binary_size, std::vector* optimized_binary) const; @@ -190,6 +199,14 @@ class Optimizer { // Same as above, except it takes an options object. See the documentation // for |OptimizerOptions| to see which options can be set. + // + // By default, the binary is validated before any transforms are performed, + // and optionally after each transform. Validation uses SPIR-V spec rules + // for the SPIR-V version named in the binary's header (at word offset 1). + // Additionally, if the target environment is a client API (such as + // Vulkan 1.1), then validate for that client API version, to the extent + // that it is verifiable from data in the binary itself, or from the + // validator options set on the optimizer options. bool Run(const uint32_t* original_binary, const size_t original_binary_size, std::vector* optimized_binary, const spv_optimizer_options opt_options) const; @@ -685,9 +702,9 @@ Optimizer::PassToken CreateSSARewritePass(); // any resulting half precision values back to float32 as needed. No variables // are changed. No image operations are changed. // -// Best if run late since it will generate better code with unneeded function -// scope loads and stores and composite inserts and extracts removed. Also best -// if followed by instruction simplification, redundancy elimination and DCE. +// Best if run after function scope store/load and composite operation +// eliminations are run. Also best if followed by instruction simplification, +// redundancy elimination and DCE. Optimizer::PassToken CreateConvertRelaxedToHalfPass(); // Create relax float ops pass. @@ -748,7 +765,7 @@ Optimizer::PassToken CreateCombineAccessChainsPass(); // |version| specifies the buffer record format. Optimizer::PassToken CreateInstBindlessCheckPass( uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false, - bool input_init_enable = false, uint32_t version = 1); + bool input_init_enable = false, uint32_t version = 2); // Create a pass to instrument physical buffer address checking // This pass instruments all physical buffer address references to check that diff --git a/3rdparty/spirv-tools/source/CMakeLists.txt b/3rdparty/spirv-tools/source/CMakeLists.txt index f3b5942bc..4e7e10cb6 100644 --- a/3rdparty/spirv-tools/source/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/CMakeLists.txt @@ -20,6 +20,7 @@ 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") # 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 @@ -33,9 +34,13 @@ macro(spvtools_core_tables CONFIG_VERSION) COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} --spirv-core-grammar=${GRAMMAR_JSON_FILE} --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} + --extinst-cldebuginfo100-grammar=${CLDEBUGINFO100_GRAMMAR_JSON_FILE} --core-insts-output=${GRAMMAR_INSTS_INC_FILE} --operand-kinds-output=${GRAMMAR_KINDS_INC_FILE} - DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} + ${GRAMMAR_JSON_FILE} + ${DEBUGINFO_GRAMMAR_JSON_FILE} + ${CLDEBUGINFO100_GRAMMAR_JSON_FILE} COMMENT "Generate info tables for SPIR-V v${CONFIG_VERSION} core instructions and operands.") list(APPEND OPCODE_CPP_DEPENDS ${GRAMMAR_INSTS_INC_FILE}) list(APPEND OPERAND_CPP_DEPENDS ${GRAMMAR_KINDS_INC_FILE}) @@ -50,9 +55,13 @@ macro(spvtools_enum_string_mapping CONFIG_VERSION) COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} --spirv-core-grammar=${GRAMMAR_JSON_FILE} --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} + --extinst-cldebuginfo100-grammar=${CLDEBUGINFO100_GRAMMAR_JSON_FILE} --extension-enum-output=${GRAMMAR_EXTENSION_ENUM_INC_FILE} --enum-string-mapping-output=${GRAMMAR_ENUM_STRING_MAPPING_INC_FILE} - DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} + ${GRAMMAR_JSON_FILE} + ${DEBUGINFO_GRAMMAR_JSON_FILE} + ${CLDEBUGINFO100_GRAMMAR_JSON_FILE} COMMENT "Generate enum-string mapping for SPIR-V v${CONFIG_VERSION}.") list(APPEND EXTENSION_H_DEPENDS ${GRAMMAR_EXTENSION_ENUM_INC_FILE}) list(APPEND ENUM_STRING_MAPPING_CPP_DEPENDS ${GRAMMAR_ENUM_STRING_MAPPING_INC_FILE}) @@ -101,13 +110,14 @@ macro(spvtools_opencl_tables CONFIG_VERSION) list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE}) endmacro(spvtools_opencl_tables) -macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME) +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") add_custom_command(OUTPUT ${INSTS_FILE} COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} --extinst-vendor-grammar=${GRAMMAR_FILE} --vendor-insts-output=${INSTS_FILE} + --vendor-operand-kind-prefix=${OPERAND_KIND_PREFIX} DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE} COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.") add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE}) @@ -134,12 +144,14 @@ spvtools_core_tables("unified1") spvtools_enum_string_mapping("unified1") spvtools_opencl_tables("unified1") spvtools_glsl_tables("unified1") -spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp") -spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm") -spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs") -spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb") -spvtools_vendor_tables("debuginfo" "debuginfo") +spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp" "") +spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm" "") +spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs" "") +spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb" "") +spvtools_vendor_tables("debuginfo" "debuginfo" "") +spvtools_vendor_tables("opencl.debuginfo.100" "cldi100" "CLDEBUG100_") spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE}) +spvtools_extinst_lang_headers("OpenCLDebugInfo100" ${CLDEBUGINFO100_GRAMMAR_JSON_FILE}) spvtools_vimsyntax("unified1" "1.0") add_custom_target(spirv-tools-vimsyntax DEPENDS ${VIMSYNTAX_FILE}) diff --git a/3rdparty/spirv-tools/source/binary.cpp b/3rdparty/spirv-tools/source/binary.cpp index 1d3128373..046306185 100644 --- a/3rdparty/spirv-tools/source/binary.cpp +++ b/3rdparty/spirv-tools/source/binary.cpp @@ -477,9 +477,22 @@ spv_result_t Parser::parseOperand(size_t inst_offset, assert(SpvOpExtInst == opcode); assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE); spv_ext_inst_desc ext_inst; - if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst)) - return diagnostic() << "Invalid extended instruction number: " << word; - spvPushOperandTypes(ext_inst->operandTypes, expected_operands); + if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst) == + SPV_SUCCESS) { + // if we know about this ext inst, push the expected operands + spvPushOperandTypes(ext_inst->operandTypes, expected_operands); + } else { + // if we don't know this extended instruction and the set isn't + // non-semantic, we cannot process further + if (!spvExtInstIsNonSemantic(inst->ext_inst_type)) { + return diagnostic() + << "Invalid extended instruction number: " << word; + } else { + // for non-semantic instruction sets, we know the form of all such + // extended instructions contains a series of IDs as parameters + expected_operands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID); + } + } } break; case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { @@ -623,7 +636,12 @@ spv_result_t Parser::parseOperand(size_t inst_offset, case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: - case SPV_OPERAND_TYPE_DEBUG_OPERATION: { + case SPV_OPERAND_TYPE_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY: { // A single word that is a plain enum value. // Map an optional operand type to its corresponding concrete type. @@ -647,6 +665,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset, case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: case SPV_OPERAND_TYPE_SELECTION_CONTROL: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: { // This operand is a mask. diff --git a/3rdparty/spirv-tools/source/disassemble.cpp b/3rdparty/spirv-tools/source/disassemble.cpp index c116f5072..4b3972b51 100644 --- a/3rdparty/spirv-tools/source/disassemble.cpp +++ b/3rdparty/spirv-tools/source/disassemble.cpp @@ -217,10 +217,18 @@ void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst, break; case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { spv_ext_inst_desc ext_inst; - if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst)) - assert(false && "should have caught this earlier"); SetRed(); - stream_ << ext_inst->name; + if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) == + SPV_SUCCESS) { + stream_ << ext_inst->name; + } else { + if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) { + assert(false && "should have caught this earlier"); + } else { + // for non-semantic instruction sets we can just print the number + stream_ << word; + } + } } break; case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { spv_opcode_desc opcode_desc; @@ -272,7 +280,12 @@ void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst, case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: - case SPV_OPERAND_TYPE_DEBUG_OPERATION: { + case SPV_OPERAND_TYPE_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY: { spv_operand_desc entry; if (grammar_.lookupOperand(operand.type, word, &entry)) assert(false && "should have caught this earlier"); @@ -285,6 +298,7 @@ void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst, case SPV_OPERAND_TYPE_MEMORY_ACCESS: case SPV_OPERAND_TYPE_SELECTION_CONTROL: case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: EmitMaskOperand(operand.type, word); break; default: diff --git a/3rdparty/spirv-tools/source/ext_inst.cpp b/3rdparty/spirv-tools/source/ext_inst.cpp index 0499e2304..e69c3c9b7 100644 --- a/3rdparty/spirv-tools/source/ext_inst.cpp +++ b/3rdparty/spirv-tools/source/ext_inst.cpp @@ -28,6 +28,7 @@ #include "debuginfo.insts.inc" #include "glsl.std.450.insts.inc" +#include "opencl.debuginfo.100.insts.inc" #include "opencl.std.insts.inc" #include "spirv-tools/libspirv.h" @@ -51,6 +52,8 @@ static const spv_ext_inst_group_t kGroups_1_0[] = { ARRAY_SIZE(spv_amd_shader_ballot_entries), spv_amd_shader_ballot_entries}, {SPV_EXT_INST_TYPE_DEBUGINFO, ARRAY_SIZE(debuginfo_entries), debuginfo_entries}, + {SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, + ARRAY_SIZE(opencl_debuginfo_100_entries), opencl_debuginfo_100_entries}, }; static const spv_ext_inst_table_t kTable_1_0 = {ARRAY_SIZE(kGroups_1_0), @@ -85,6 +88,7 @@ spv_result_t spvExtInstTableGet(spv_ext_inst_table* pExtInstTable, case SPV_ENV_WEBGPU_0: case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: *pExtInstTable = &kTable_1_0; return SPV_SUCCESS; default: @@ -116,9 +120,32 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { if (!strcmp("DebugInfo", name)) { return SPV_EXT_INST_TYPE_DEBUGINFO; } + if (!strcmp("OpenCL.DebugInfo.100", name)) { + return SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100; + } + // ensure to add any known non-semantic extended instruction sets + // above this point, and update spvExtInstIsNonSemantic() + if (!strncmp("NonSemantic.", name, 12)) { + return SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN; + } return SPV_EXT_INST_TYPE_NONE; } +bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) { + if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN) { + return true; + } + return false; +} + +bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type) { + if (type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || + type == SPV_EXT_INST_TYPE_DEBUGINFO) { + return true; + } + return false; +} + spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table, const spv_ext_inst_type_t type, const char* name, diff --git a/3rdparty/spirv-tools/source/ext_inst.h b/3rdparty/spirv-tools/source/ext_inst.h index a821cc2bc..aff6e308c 100644 --- a/3rdparty/spirv-tools/source/ext_inst.h +++ b/3rdparty/spirv-tools/source/ext_inst.h @@ -21,6 +21,12 @@ // Gets the type of the extended instruction set with the specified name. spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name); +// Returns true if the extended instruction set is non-semantic +bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type); + +// Returns true if the extended instruction set is debug info +bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type); + // Finds the named extented instruction of the given type in the given extended // instruction table. On success, returns SPV_SUCCESS and writes a handle of // the instruction entry into *entry. diff --git a/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json b/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json new file mode 100644 index 000000000..9a6fe2738 --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.opencl.debuginfo.100.grammar.json @@ -0,0 +1,619 @@ +{ + "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" : "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/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index 97f89763c..c816f8757 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -36,6 +36,8 @@ if(SPIRV_BUILD_FUZZER) fuzzer.h fuzzer_context.h fuzzer_pass.h + fuzzer_pass_add_composite_types.h + fuzzer_pass_add_dead_blocks.h fuzzer_pass_add_dead_breaks.h fuzzer_pass_add_dead_continues.h fuzzer_pass_add_no_contraction_decorations.h @@ -47,12 +49,16 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_apply_id_synonyms.h fuzzer_pass_construct_composites.h fuzzer_pass_copy_objects.h + fuzzer_pass_donate_modules.h + fuzzer_pass_merge_blocks.h fuzzer_pass_obfuscate_constants.h + fuzzer_pass_outline_functions.h fuzzer_pass_permute_blocks.h fuzzer_pass_split_blocks.h fuzzer_util.h id_use_descriptor.h instruction_descriptor.h + instruction_message.h protobufs/spirvfuzz_protobufs.h pseudo_random_generator.h random_generator.h @@ -60,18 +66,30 @@ if(SPIRV_BUILD_FUZZER) shrinker.h transformation.h transformation_add_constant_boolean.h + transformation_add_constant_composite.h transformation_add_constant_scalar.h + transformation_add_dead_block.h transformation_add_dead_break.h transformation_add_dead_continue.h + transformation_add_function.h + transformation_add_global_undef.h + transformation_add_global_variable.h transformation_add_no_contraction_decoration.h + transformation_add_type_array.h transformation_add_type_boolean.h transformation_add_type_float.h + transformation_add_type_function.h transformation_add_type_int.h + transformation_add_type_matrix.h transformation_add_type_pointer.h + transformation_add_type_struct.h + transformation_add_type_vector.h transformation_composite_construct.h transformation_composite_extract.h transformation_copy_object.h + transformation_merge_blocks.h transformation_move_block_down.h + transformation_outline_function.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h transformation_replace_id_with_synonym.h @@ -90,6 +108,8 @@ if(SPIRV_BUILD_FUZZER) fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp + fuzzer_pass_add_composite_types.cpp + fuzzer_pass_add_dead_blocks.cpp fuzzer_pass_add_dead_breaks.cpp fuzzer_pass_add_dead_continues.cpp fuzzer_pass_add_no_contraction_decorations.cpp @@ -101,30 +121,46 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_apply_id_synonyms.cpp fuzzer_pass_construct_composites.cpp fuzzer_pass_copy_objects.cpp + fuzzer_pass_donate_modules.cpp + fuzzer_pass_merge_blocks.cpp fuzzer_pass_obfuscate_constants.cpp + fuzzer_pass_outline_functions.cpp fuzzer_pass_permute_blocks.cpp fuzzer_pass_split_blocks.cpp fuzzer_util.cpp id_use_descriptor.cpp instruction_descriptor.cpp + instruction_message.cpp pseudo_random_generator.cpp random_generator.cpp replayer.cpp shrinker.cpp transformation.cpp transformation_add_constant_boolean.cpp + transformation_add_constant_composite.cpp transformation_add_constant_scalar.cpp + transformation_add_dead_block.cpp transformation_add_dead_break.cpp transformation_add_dead_continue.cpp + transformation_add_function.cpp + transformation_add_global_undef.cpp + transformation_add_global_variable.cpp transformation_add_no_contraction_decoration.cpp + transformation_add_type_array.cpp transformation_add_type_boolean.cpp transformation_add_type_float.cpp + transformation_add_type_function.cpp transformation_add_type_int.cpp + transformation_add_type_matrix.cpp transformation_add_type_pointer.cpp + transformation_add_type_struct.cpp + transformation_add_type_vector.cpp transformation_composite_construct.cpp transformation_composite_extract.cpp transformation_copy_object.cpp + transformation_merge_blocks.cpp transformation_move_block_down.cpp + transformation_outline_function.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp transformation_replace_id_with_synonym.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp index ac3ea30fc..a7b431120 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp @@ -800,9 +800,102 @@ bool FactManager::DataSynonymFacts::IsSynonymous( // 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 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 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 +//============================== + +//============================== +// Arbitrarily-valued variable facts + +// The purpose of this class is to group the fields and data used to represent +// facts about livesafe functions. +class FactManager::ArbitrarilyValuedVaribleFacts { + public: + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactValueOfVariableIsArbitrary& fact); + + // See method in FactManager which delegates to this method. + bool VariableValueIsArbitrary(uint32_t variable_id) const; + + private: + std::set arbitrary_valued_varible_ids_; +}; + +void FactManager::ArbitrarilyValuedVaribleFacts::AddFact( + const protobufs::FactValueOfVariableIsArbitrary& fact) { + arbitrary_valued_varible_ids_.insert(fact.variable_id()); +} + +bool FactManager::ArbitrarilyValuedVaribleFacts::VariableValueIsArbitrary( + uint32_t variable_id) const { + return arbitrary_valued_varible_ids_.count(variable_id) != 0; +} + +// End of arbitrarily-valued variable facts +//============================== + FactManager::FactManager() : uniform_constant_facts_(MakeUnique()), - data_synonym_facts_(MakeUnique()) {} + data_synonym_facts_(MakeUnique()), + dead_block_facts_(MakeUnique()), + livesafe_function_facts_(MakeUnique()), + arbitrarily_valued_variable_facts_( + MakeUnique()) {} FactManager::~FactManager() = default; @@ -827,6 +920,12 @@ bool FactManager::AddFact(const fuzz::protobufs::Fact& fact, case protobufs::Fact::kDataSynonymFact: data_synonym_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; @@ -898,5 +997,36 @@ bool FactManager::IsSynonymous( context); } +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::VariableValueIsArbitrary(uint32_t variable_id) const { + return arbitrarily_valued_variable_facts_->VariableValueIsArbitrary( + variable_id); +} + +void FactManager::AddFactValueOfVariableIsArbitrary(uint32_t variable_id) { + protobufs::FactValueOfVariableIsArbitrary fact; + fact.set_variable_id(variable_id); + arbitrarily_valued_variable_facts_->AddFact(fact); +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.h b/3rdparty/spirv-tools/source/fuzz/fact_manager.h index 62d9dac9b..117ed1c61 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.h +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.h @@ -58,6 +58,16 @@ class FactManager { const protobufs::DataDescriptor& data2, opt::IRContext* context); + // Records the fact that |block_id| is dead. + void AddFactBlockIsDead(uint32_t block_id); + + // Records the fact that |function_id| is livesafe. + void AddFactFunctionIsLivesafe(uint32_t function_id); + + // Records the fact that |variable_id| has an arbitrary value and can thus be + // stored to without affecting the module's behaviour. + void AddFactValueOfVariableIsArbitrary(uint32_t variable_id); + // The fact manager is responsible for managing a few distinct categories of // facts. In principle there could be different fact managers for each kind // of fact, but in practice providing one 'go to' place for facts is @@ -130,6 +140,35 @@ class FactManager { // End of id synonym facts //============================== + //============================== + // Querying facts about dead blocks + + // Returns true if and ony if |block_id| is the id of a block known to be + // dynamically unreachable. + bool BlockIsDead(uint32_t block_id) const; + + // End of dead block facts + //============================== + + //============================== + // Querying facts about livesafe function + + // Returns true if and ony if |function_id| is the id of a function known + // to be livesafe. + bool FunctionIsLivesafe(uint32_t function_id) const; + + // End of dead livesafe function facts + //============================== + + //============================== + // Querying facts about arbitrarily-valued variables + + // Returns true if and ony if |variable_id| is arbitrarily-valued. + bool VariableValueIsArbitrary(uint32_t variable_id) const; + + // End of arbitrarily-valued variable facts + //============================== + private: // For each distinct kind of fact to be managed, we use a separate opaque // class type. @@ -142,6 +181,20 @@ class FactManager { class DataSynonymFacts; // Opaque class for management of data synonym facts. std::unique_ptr data_synonym_facts_; // Unique pointer to internal data. + + class DeadBlockFacts; // Opaque class for management of dead block facts. + std::unique_ptr + dead_block_facts_; // Unique pointer to internal data. + + class LivesafeFunctionFacts; // Opaque class for management of livesafe + // function facts. + std::unique_ptr + livesafe_function_facts_; // Unique pointer to internal data. + + class ArbitrarilyValuedVaribleFacts; // Opaque class for management of + // facts about variables whose values should be expected to be arbitrary. + std::unique_ptr + arbitrarily_valued_variable_facts_; // Unique pointer to internal data. }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index 20e714d7c..27b697c94 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -21,6 +21,8 @@ #include "fuzzer_pass_adjust_memory_operands_masks.h" #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" +#include "source/fuzz/fuzzer_pass_add_composite_types.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_no_contraction_decorations.h" @@ -31,7 +33,10 @@ #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_merge_blocks.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_split_blocks.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" @@ -50,15 +55,21 @@ const uint32_t kTransformationLimit = 500; const uint32_t kChanceOfApplyingAnotherPass = 85; -template +// A convenience method to add a fuzzer pass to |passes| with probability 0.5. +// All fuzzer passes take |ir_context|, |fact_manager|, |fuzzer_context| and +// |transformation_sequence_out| as parameters. Extra arguments can be provided +// via |extra_args|. +template void MaybeAddPass( std::vector>* passes, opt::IRContext* ir_context, FactManager* fact_manager, FuzzerContext* fuzzer_context, - protobufs::TransformationSequence* transformation_sequence_out) { + protobufs::TransformationSequence* transformation_sequence_out, + Args&&... extra_args) { if (fuzzer_context->ChooseEven()) { passes->push_back(MakeUnique(ir_context, fact_manager, fuzzer_context, - transformation_sequence_out)); + transformation_sequence_out, + std::forward(extra_args)...)); } } @@ -112,6 +123,7 @@ bool Fuzzer::Impl::ApplyPassAndCheckValidity( Fuzzer::FuzzerResultStatus Fuzzer::Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, + const std::vector& donor_suppliers, std::vector* binary_out, protobufs::TransformationSequence* transformation_sequence_out) const { // Check compatibility between the library version being linked with and the @@ -167,6 +179,12 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( // Apply some semantics-preserving passes. std::vector> passes; while (passes.empty()) { + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); @@ -182,9 +200,18 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass( + &passes, ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out, donor_suppliers); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); + MaybeAddPass(&passes, ir_context.get(), + &fact_manager, &fuzzer_context, + transformation_sequence_out); MaybeAddPass(&passes, ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out); diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.h b/3rdparty/spirv-tools/source/fuzz/fuzzer.h index c1d2deeef..3ac73a1a6 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.h @@ -18,6 +18,7 @@ #include #include +#include "source/fuzz/fuzzer_util.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "spirv-tools/libspirv.hpp" @@ -57,11 +58,14 @@ class Fuzzer { // 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|. The transformation - // sequence that was applied is returned via |transformation_sequence_out|. + // 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& binary_in, const protobufs::FactSequence& initial_facts, + const std::vector& donor_suppliers, std::vector* binary_out, protobufs::TransformationSequence* transformation_sequence_out) const; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp index 356cb3599..916803a7b 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp @@ -23,10 +23,16 @@ namespace { // Default pairs of probabilities for applying various // transformations. All values are percentages. Keep them in alphabetical order. +const std::pair kChanceOfAddingAnotherStructField = {20, + 90}; +const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; +const std::pair kChanceOfAddingDeadBlock = {20, 90}; const std::pair kChanceOfAddingDeadBreak = {5, 80}; const std::pair kChanceOfAddingDeadContinue = {5, 80}; +const std::pair kChanceOfAddingMatrixType = {20, 70}; const std::pair kChanceOfAddingNoContractionDecoration = { 5, 70}; +const std::pair kChanceOfAddingVectorType = {20, 70}; const std::pair kChanceOfAdjustingFunctionControl = {20, 70}; const std::pair kChanceOfAdjustingLoopControl = {20, 90}; @@ -34,10 +40,16 @@ const std::pair kChanceOfAdjustingMemoryOperandsMask = {20, 90}; const std::pair kChanceOfAdjustingSelectionControl = {20, 90}; -const std::pair kChanceOfCopyingObject = {20, 50}; +const std::pair kChanceOfChoosingStructTypeVsArrayType = { + 20, 80}; const std::pair kChanceOfConstructingComposite = {20, 50}; +const std::pair kChanceOfCopyingObject = {20, 50}; +const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair kChanceOfMakingDonorLivesafe = {40, 60}; +const std::pair kChanceOfMergingBlocks = {20, 95}; const std::pair kChanceOfMovingBlockDown = {20, 50}; const std::pair kChanceOfObfuscatingConstant = {10, 90}; +const std::pair kChanceOfOutliningFunction = {10, 90}; const std::pair kChanceOfReplacingIdWithSynonym = {10, 90}; const std::pair kChanceOfSplittingBlock = {40, 95}; @@ -45,6 +57,8 @@ const std::pair kChanceOfSplittingBlock = {40, 95}; // Keep them in alphabetical order. const uint32_t kDefaultMaxLoopControlPartialCount = 100; const uint32_t kDefaultMaxLoopControlPeelCount = 100; +const uint32_t kDefaultMaxLoopLimit = 20; +const uint32_t kDefaultMaxNewArraySizeLimit = 100; // Default functions for controlling how deep to go during recursive // generation/transformation. Keep them in alphabetical order. @@ -64,12 +78,22 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, next_fresh_id_(min_fresh_id), go_deeper_in_constant_obfuscation_( kDefaultGoDeeperInConstantObfuscation) { + chance_of_adding_another_struct_field_ = + ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); + chance_of_adding_array_or_struct_type_ = + ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType); + chance_of_adding_dead_block_ = + ChooseBetweenMinAndMax(kChanceOfAddingDeadBlock); chance_of_adding_dead_break_ = ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak); chance_of_adding_dead_continue_ = ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue); + chance_of_adding_matrix_type_ = + ChooseBetweenMinAndMax(kChanceOfAddingMatrixType); chance_of_adding_no_contraction_decoration_ = ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration); + chance_of_adding_vector_type_ = + ChooseBetweenMinAndMax(kChanceOfAddingVectorType); chance_of_adjusting_function_control_ = ChooseBetweenMinAndMax(kChanceOfAdjustingFunctionControl); chance_of_adjusting_loop_control_ = @@ -78,18 +102,29 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, ChooseBetweenMinAndMax(kChanceOfAdjustingMemoryOperandsMask); chance_of_adjusting_selection_control_ = ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl); + chance_of_choosing_struct_type_vs_array_type_ = + ChooseBetweenMinAndMax(kChanceOfChoosingStructTypeVsArrayType); chance_of_constructing_composite_ = ChooseBetweenMinAndMax(kChanceOfConstructingComposite); chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); + chance_of_donating_additional_module_ = + ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_making_donor_livesafe_ = + ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe); + chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks); chance_of_moving_block_down_ = ChooseBetweenMinAndMax(kChanceOfMovingBlockDown); chance_of_obfuscating_constant_ = ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant); + chance_of_outlining_function_ = + ChooseBetweenMinAndMax(kChanceOfOutliningFunction); chance_of_replacing_id_with_synonym_ = ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym); chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock); max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount; max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount; + max_loop_limit_ = kDefaultMaxLoopLimit; + max_new_array_size_limit_ = kDefaultMaxNewArraySizeLimit; } FuzzerContext::~FuzzerContext() = default; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h index c8242e617..d4d6d58fb 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h @@ -58,13 +58,26 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t GetChanceOfAddingAnotherStructField() { + return chance_of_adding_another_struct_field_; + } + uint32_t GetChanceOfAddingArrayOrStructType() { + return chance_of_adding_array_or_struct_type_; + } + uint32_t GetChanceOfAddingDeadBlock() { return chance_of_adding_dead_block_; } uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; } uint32_t GetChanceOfAddingDeadContinue() { return chance_of_adding_dead_continue_; } + uint32_t GetChanceOfAddingMatrixType() { + return chance_of_adding_matrix_type_; + } uint32_t GetChanceOfAddingNoContractionDecoration() { return chance_of_adding_no_contraction_decoration_; } + uint32_t GetChanceOfAddingVectorType() { + return chance_of_adding_vector_type_; + } uint32_t GetChanceOfAdjustingFunctionControl() { return chance_of_adjusting_function_control_; } @@ -77,14 +90,27 @@ class FuzzerContext { uint32_t GetChanceOfAdjustingSelectionControl() { return chance_of_adjusting_selection_control_; } + uint32_t GetChanceOfChoosingStructTypeVsArrayType() { + return chance_of_choosing_struct_type_vs_array_type_; + } uint32_t GetChanceOfConstructingComposite() { return chance_of_constructing_composite_; } uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; } + uint32_t GetChanceOfDonatingAdditionalModule() { + return chance_of_donating_additional_module_; + } + uint32_t ChanceOfMakingDonorLivesafe() { + return chance_of_making_donor_livesafe_; + } + uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; } uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; } uint32_t GetChanceOfObfuscatingConstant() { return chance_of_obfuscating_constant_; } + uint32_t GetChanceOfOutliningFunction() { + return chance_of_outlining_function_; + } uint32_t GetChanceOfReplacingIdWithSynonym() { return chance_of_replacing_id_with_synonym_; } @@ -95,6 +121,13 @@ class FuzzerContext { uint32_t GetRandomLoopControlPartialCount() { return random_generator_->RandomUint32(max_loop_control_partial_count_); } + uint32_t GetRandomLoopLimit() { + return random_generator_->RandomUint32(max_loop_limit_); + } + uint32_t GetRandomSizeForNewArray() { + // Ensure that the array size is non-zero. + return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1; + } // Functions to control how deeply to recurse. // Keep them in alphabetical order. @@ -110,17 +143,27 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. + uint32_t chance_of_adding_another_struct_field_; + uint32_t chance_of_adding_array_or_struct_type_; + uint32_t chance_of_adding_dead_block_; uint32_t chance_of_adding_dead_break_; uint32_t chance_of_adding_dead_continue_; + uint32_t chance_of_adding_matrix_type_; uint32_t chance_of_adding_no_contraction_decoration_; + uint32_t chance_of_adding_vector_type_; uint32_t chance_of_adjusting_function_control_; uint32_t chance_of_adjusting_loop_control_; uint32_t chance_of_adjusting_memory_operands_mask_; uint32_t chance_of_adjusting_selection_control_; + uint32_t chance_of_choosing_struct_type_vs_array_type_; uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; + uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_making_donor_livesafe_; + uint32_t chance_of_merging_blocks_; uint32_t chance_of_moving_block_down_; uint32_t chance_of_obfuscating_constant_; + uint32_t chance_of_outlining_function_; uint32_t chance_of_replacing_id_with_synonym_; uint32_t chance_of_splitting_block_; @@ -129,6 +172,8 @@ class FuzzerContext { // Keep them in alphabetical order. uint32_t max_loop_control_partial_count_; uint32_t max_loop_control_peel_count_; + uint32_t max_loop_limit_; + uint32_t max_new_array_size_limit_; // Functions to determine with what probability to go deeper when generating // or mutating constructs recursively. diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp index 1da53f426..9964a6c3f 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp @@ -15,6 +15,14 @@ #include "source/fuzz/fuzzer_pass.h" #include "source/fuzz/instruction_descriptor.h" +#include "source/fuzz/transformation_add_constant_scalar.h" +#include "source/fuzz/transformation_add_global_undef.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_matrix.h" +#include "source/fuzz/transformation_add_type_pointer.h" +#include "source/fuzz/transformation_add_type_vector.h" namespace spvtools { namespace fuzz { @@ -128,5 +136,123 @@ void FuzzerPass::MaybeAddTransformationBeforeEachInstruction( } } +uint32_t FuzzerPass::FindOrCreateBoolType() { + opt::analysis::Bool bool_type; + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&bool_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeBoolean(result)); + return result; +} + +uint32_t FuzzerPass::FindOrCreate32BitIntegerType(bool is_signed) { + opt::analysis::Integer int_type(32, is_signed); + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&int_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeInt(result, 32, is_signed)); + return result; +} + +uint32_t FuzzerPass::FindOrCreate32BitFloatType() { + opt::analysis::Float float_type(32); + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&float_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeFloat(result, 32)); + return result; +} + +uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id, + uint32_t component_count) { + assert(component_count >= 2 && component_count <= 4 && + "Precondition: component count must be in range [2, 4]."); + opt::analysis::Type* component_type = + GetIRContext()->get_type_mgr()->GetType(component_type_id); + assert(component_type && "Precondition: the component type must exist."); + opt::analysis::Vector vector_type(component_type, component_count); + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&vector_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddTypeVector(result, component_type_id, component_count)); + return result; +} + +uint32_t FuzzerPass::FindOrCreateMatrixType(uint32_t column_count, + uint32_t row_count) { + assert(column_count >= 2 && column_count <= 4 && + "Precondition: column count must be in range [2, 4]."); + assert(row_count >= 2 && row_count <= 4 && + "Precondition: row count must be in range [2, 4]."); + uint32_t column_type_id = + FindOrCreateVectorType(FindOrCreate32BitFloatType(), row_count); + opt::analysis::Type* column_type = + GetIRContext()->get_type_mgr()->GetType(column_type_id); + opt::analysis::Matrix matrix_type(column_type, column_count); + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&matrix_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddTypeMatrix(result, column_type_id, column_count)); + return result; +} + +uint32_t FuzzerPass::FindOrCreatePointerTo32BitIntegerType( + bool is_signed, SpvStorageClass storage_class) { + auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); + opt::analysis::Pointer pointer_type( + GetIRContext()->get_type_mgr()->GetType(uint32_type_id), storage_class); + auto existing_id = GetIRContext()->get_type_mgr()->GetId(&pointer_type); + if (existing_id) { + return existing_id; + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddTypePointer(result, storage_class, uint32_type_id)); + return result; +} + +uint32_t FuzzerPass::FindOrCreate32BitIntegerConstant(uint32_t word, + bool is_signed) { + auto uint32_type_id = FindOrCreate32BitIntegerType(is_signed); + opt::analysis::IntConstant int_constant( + GetIRContext()->get_type_mgr()->GetType(uint32_type_id)->AsInteger(), + {word}); + auto existing_constant = + GetIRContext()->get_constant_mgr()->FindConstant(&int_constant); + if (existing_constant) { + return GetIRContext() + ->get_constant_mgr() + ->GetDefiningInstruction(existing_constant) + ->result_id(); + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddConstantScalar(result, uint32_type_id, {word})); + return result; +} + +uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) { + for (auto& inst : GetIRContext()->types_values()) { + if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) { + return inst.result_id(); + } + } + auto result = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddGlobalUndef(result, type_id)); + return result; +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h index cf56e2407..e1e8aec25 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass.h @@ -89,6 +89,60 @@ class FuzzerPass { const protobufs::InstructionDescriptor& instruction_descriptor)> maybe_apply_transformation); + // A generic helper for applying a transformation that should be applicable + // by construction, and adding it to the sequence of applied transformations. + template + void ApplyTransformation(const TransformationType& transformation) { + assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) && + "Transformation should be applicable by construction."); + transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + + // Returns the id of an OpTypeBool instruction. If such an instruction does + // not exist, a transformation is applied to add it. + uint32_t FindOrCreateBoolType(); + + // Returns the id of an OpTypeInt instruction, with width 32 and signedness + // specified by |is_signed|. If such an instruction does not exist, a + // transformation is applied to add it. + uint32_t FindOrCreate32BitIntegerType(bool is_signed); + + // Returns the id of an OpTypeFloat instruction, with width 32. If such an + // instruction does not exist, a transformation is applied to add it. + uint32_t FindOrCreate32BitFloatType(); + + // Returns the id of an OpTypeVector instruction, with |component_type_id| + // (which must already exist) as its base type, and |component_count| + // elements (which must be in the range [2, 4]). If such an instruction does + // not exist, a transformation is applied to add it. + uint32_t FindOrCreateVectorType(uint32_t component_type_id, + uint32_t component_count); + + // Returns the id of an OpTypeMatrix instruction, with |column_count| columns + // and |row_count| rows (each of which must be in the range [2, 4]). If the + // float and vector types required to build this matrix type or the matrix + // type itself do not exist, transformations are applied to add them. + uint32_t FindOrCreateMatrixType(uint32_t column_count, uint32_t row_count); + + // Returns the id of an OpTypePointer instruction, with a 32-bit integer base + // type of signedness specified by |is_signed|. If the pointer type or + // required integer base type do not exist, transformations are applied to add + // them. + uint32_t FindOrCreatePointerTo32BitIntegerType(bool is_signed, + SpvStorageClass storage_class); + + // Returns the id of an OpConstant instruction, with 32-bit integer type of + // signedness specified by |is_signed|, with |word| as its value. If either + // the required integer type or the constant do not exist, transformations are + // applied to add them. + uint32_t FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed); + + // Returns the result id of an instruction of the form: + // %id = OpUndef %|type_id| + // If no such instruction exists, a transformation is applied to add it. + uint32_t FindOrCreateGlobalUndef(uint32_t type_id); + private: opt::IRContext* ir_context_; FactManager* fact_manager_; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.cpp new file mode 100644 index 000000000..32c720e16 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.cpp @@ -0,0 +1,138 @@ +// 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_types.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_type_array.h" +#include "source/fuzz/transformation_add_type_struct.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddCompositeTypes::FuzzerPassAddCompositeTypes( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddCompositeTypes::~FuzzerPassAddCompositeTypes() = default; + +void FuzzerPassAddCompositeTypes::Apply() { + MaybeAddMissingVectorTypes(); + MaybeAddMissingMatrixTypes(); + + // Randomly interleave between adding struct and array composite types + while (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingArrayOrStructType())) { + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfChoosingStructTypeVsArrayType())) { + AddNewStructType(); + } else { + AddNewArrayType(); + } + } +} + +void FuzzerPassAddCompositeTypes::MaybeAddMissingVectorTypes() { + // Functions to lazily supply scalar base types on demand if we decide to + // create vectors with the relevant base types. + std::function bool_type_supplier = [this]() -> uint32_t { + return FindOrCreateBoolType(); + }; + std::function float_type_supplier = [this]() -> uint32_t { + return FindOrCreate32BitFloatType(); + }; + std::function int_type_supplier = [this]() -> uint32_t { + return FindOrCreate32BitIntegerType(true); + }; + std::function uint_type_supplier = [this]() -> uint32_t { + return FindOrCreate32BitIntegerType(false); + }; + + // Consider each of the base types with which we can make vectors. + for (auto& base_type_supplier : {bool_type_supplier, float_type_supplier, + int_type_supplier, uint_type_supplier}) { + // Consider each valid vector size. + for (uint32_t size = 2; size <= 4; size++) { + // Randomly decide whether to create (if it does not already exist) a + // vector with this size and base type. + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingVectorType())) { + FindOrCreateVectorType(base_type_supplier(), size); + } + } + } +} + +void FuzzerPassAddCompositeTypes::MaybeAddMissingMatrixTypes() { + // Consider every valid matrix dimension. + for (uint32_t columns = 2; columns <= 4; columns++) { + for (uint32_t rows = 2; rows <= 4; rows++) { + // Randomly decide whether to create (if it does not already exist) a + // matrix with these dimensions. As matrices can only have floating-point + // base type, we do not need to consider multiple base types as in the + // case for vectors. + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingMatrixType())) { + FindOrCreateMatrixType(columns, rows); + } + } + } +} + +void FuzzerPassAddCompositeTypes::AddNewArrayType() { + ApplyTransformation(TransformationAddTypeArray( + GetFuzzerContext()->GetFreshId(), ChooseScalarOrCompositeType(), + FindOrCreate32BitIntegerConstant( + GetFuzzerContext()->GetRandomSizeForNewArray(), false))); +} + +void FuzzerPassAddCompositeTypes::AddNewStructType() { + std::vector field_type_ids; + do { + field_type_ids.push_back(ChooseScalarOrCompositeType()); + } while (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingAnotherStructField())); + ApplyTransformation(TransformationAddTypeStruct( + GetFuzzerContext()->GetFreshId(), field_type_ids)); +} + +uint32_t FuzzerPassAddCompositeTypes::ChooseScalarOrCompositeType() { + // Gather up all the possibly-relevant types. + std::vector candidates; + for (auto& inst : GetIRContext()->types_values()) { + switch (inst.opcode()) { + case SpvOpTypeArray: + case SpvOpTypeBool: + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeMatrix: + case SpvOpTypeStruct: + case SpvOpTypeVector: + candidates.push_back(inst.result_id()); + break; + default: + break; + } + } + assert(!candidates.empty() && + "This function should only be called if there is at least one scalar " + "or composite type available."); + // Return one of these types at random. + return candidates[GetFuzzerContext()->RandomIndex(candidates)]; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.h new file mode 100644 index 000000000..29d4bb896 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_composite_types.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_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass that randomly adds missing vector and matrix types, and new +// array and struct types, to the module. +class FuzzerPassAddCompositeTypes : public FuzzerPass { + public: + FuzzerPassAddCompositeTypes( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddCompositeTypes(); + + void Apply() override; + + private: + // Creates an array of a random size with a random existing base type and adds + // it to the module. + void AddNewArrayType(); + + // Creates a struct with fields of random existing types and adds it to the + // module. + void AddNewStructType(); + + // For each vector type not already present in the module, randomly decides + // whether to add it to the module. + void MaybeAddMissingVectorTypes(); + + // For each matrix type not already present in the module, randomly decides + // whether to add it to the module. + void MaybeAddMissingMatrixTypes(); + + // Returns the id of a scalar or composite type declared in the module, + // chosen randomly. + uint32_t ChooseScalarOrCompositeType(); +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_TYPES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.cpp new file mode 100644 index 000000000..c9bc9c485 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.cpp @@ -0,0 +1,64 @@ +// 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/fuzzer_pass_add_dead_blocks.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_add_dead_block.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddDeadBlocks::FuzzerPassAddDeadBlocks( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddDeadBlocks::~FuzzerPassAddDeadBlocks() = default; + +void FuzzerPassAddDeadBlocks::Apply() { + // We iterate over all blocks in the module collecting up those at which we + // might add a branch to a new dead block. We then loop over all such + // candidates and actually apply transformations. This separation is to + // avoid modifying the module as we traverse it. + std::vector candidate_transformations; + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingDeadBlock())) { + continue; + } + // We speculatively create a transformation, and then apply it (below) if + // it turns out to be applicable. This avoids duplicating the logic for + // applicability checking. + // + // It means that fresh ids for transformations that turn out not to be + // applicable end up being unused. + candidate_transformations.emplace_back(TransformationAddDeadBlock( + GetFuzzerContext()->GetFreshId(), block.id(), + GetFuzzerContext()->ChooseEven())); + } + } + // Apply all those transformations that are in fact applicable. + for (auto& transformation : candidate_transformations) { + if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.h new file mode 100644 index 000000000..01e3843db --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_blocks.h @@ -0,0 +1,39 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// Fuzzer pass to add dynamically unreachable blocks to the module. Future +// passes can then manipulate such blocks. +class FuzzerPassAddDeadBlocks : public FuzzerPass { + public: + FuzzerPassAddDeadBlocks(opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddDeadBlocks(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_BLOCKS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp index 9eb56316c..ff0adabcc 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_construct_composites.cpp @@ -148,7 +148,6 @@ void FuzzerPassConstructComposites::Apply() { transformation.Apply(GetIRContext(), GetFactManager()); *GetTransformations()->add_transformation() = transformation.ToMessage(); - // Indicate that one instruction was added. }); } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp new file mode 100644 index 000000000..75530b10e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.cpp @@ -0,0 +1,748 @@ +// 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/fuzzer_pass_donate_modules.h" + +#include +#include +#include + +#include "source/fuzz/instruction_message.h" +#include "source/fuzz/transformation_add_constant_boolean.h" +#include "source/fuzz/transformation_add_constant_composite.h" +#include "source/fuzz/transformation_add_constant_scalar.h" +#include "source/fuzz/transformation_add_function.h" +#include "source/fuzz/transformation_add_global_undef.h" +#include "source/fuzz/transformation_add_global_variable.h" +#include "source/fuzz/transformation_add_type_array.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" +#include "source/fuzz/transformation_add_type_int.h" +#include "source/fuzz/transformation_add_type_matrix.h" +#include "source/fuzz/transformation_add_type_pointer.h" +#include "source/fuzz/transformation_add_type_struct.h" +#include "source/fuzz/transformation_add_type_vector.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassDonateModules::FuzzerPassDonateModules( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations, + const std::vector& donor_suppliers) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations), + donor_suppliers_(donor_suppliers) {} + +FuzzerPassDonateModules::~FuzzerPassDonateModules() = default; + +void FuzzerPassDonateModules::Apply() { + // If there are no donor suppliers, this fuzzer pass is a no-op. + if (donor_suppliers_.empty()) { + return; + } + + // Donate at least one module, and probabilistically decide when to stop + // donating modules. + do { + // Choose a donor supplier at random, and get the module that it provides. + std::unique_ptr donor_ir_context = donor_suppliers_.at( + GetFuzzerContext()->RandomIndex(donor_suppliers_))(); + assert(donor_ir_context != nullptr && "Supplying of donor failed"); + // Donate the supplied module. + // + // Randomly decide whether to make the module livesafe (see + // FactFunctionIsLivesafe); doing so allows it to be used for live code + // injection but restricts its behaviour to allow this, and means that its + // functions cannot be transformed as if they were arbitrary dead code. + bool make_livesafe = GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->ChanceOfMakingDonorLivesafe()); + DonateSingleModule(donor_ir_context.get(), make_livesafe); + } while (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfDonatingAdditionalModule())); +} + +void FuzzerPassDonateModules::DonateSingleModule( + opt::IRContext* donor_ir_context, bool make_livesafe) { + // 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, + // and it is not always legal to re-declare equivalent instructions. For + // example, OpTypeVoid cannot be declared twice. + // + // To handle this, we maintain a mapping from an id used in the donor module + // to the corresponding id that will be used by the donated code when it + // appears in the recipient module. + // + // This mapping is populated in two ways: + // (1) by mapping a donor instruction's result id to the id of some equivalent + // existing instruction in the recipient (e.g. this has to be done for + // OpTypeVoid) + // (2) by mapping a donor instruction's result id to a freshly chosen id that + // is guaranteed to be different from any id already used by the recipient + // (or from any id already chosen to handle a previous donor id) + std::map original_id_to_donated_id; + + HandleExternalInstructionImports(donor_ir_context, + &original_id_to_donated_id); + HandleTypesAndValues(donor_ir_context, &original_id_to_donated_id); + HandleFunctions(donor_ir_context, &original_id_to_donated_id, make_livesafe); + + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3115) Handle some + // kinds of decoration. +} + +SpvStorageClass FuzzerPassDonateModules::AdaptStorageClass( + SpvStorageClass donor_storage_class) { + switch (donor_storage_class) { + case SpvStorageClassFunction: + case SpvStorageClassPrivate: + // We leave these alone + return donor_storage_class; + case SpvStorageClassInput: + case SpvStorageClassOutput: + case SpvStorageClassUniform: + case SpvStorageClassUniformConstant: + case SpvStorageClassPushConstant: + // We change these to Private + return SpvStorageClassPrivate; + default: + // Handle other cases on demand. + assert(false && "Currently unsupported storage class."); + return SpvStorageClassMax; + } +} + +void FuzzerPassDonateModules::HandleExternalInstructionImports( + opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id) { + // Consider every external instruction set import in the donor module. + for (auto& donor_import : donor_ir_context->module()->ext_inst_imports()) { + const auto& donor_import_name_words = donor_import.GetInOperand(0).words; + // Look for an identical import in the recipient module. + for (auto& existing_import : GetIRContext()->module()->ext_inst_imports()) { + const auto& existing_import_name_words = + existing_import.GetInOperand(0).words; + if (donor_import_name_words == existing_import_name_words) { + // A matching import has found. Map the result id for the donor import + // to the id of the existing import, so that when donor instructions + // rely on the import they will be rewritten to use the existing import. + original_id_to_donated_id->insert( + {donor_import.result_id(), existing_import.result_id()}); + break; + } + } + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3116): At present + // we do not handle donation of instruction imports, i.e. we do not allow + // the donor to import instruction sets that the recipient did not already + // import. It might be a good idea to allow this, but it requires some + // thought. + assert(original_id_to_donated_id->count(donor_import.result_id()) && + "Donation of imports is not yet supported."); + } +} + +void FuzzerPassDonateModules::HandleTypesAndValues( + opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id) { + // Consider every type/global/constant/undef in the module. + for (auto& type_or_value : donor_ir_context->module()->types_values()) { + // Each such instruction generates a result id, and as part of donation we + // need to associate the donor's result id with a new result id. That new + // result id will either be the id of some existing instruction, or a fresh + // id. This variable captures it. + uint32_t new_result_id; + + // Decide how to handle each kind of instruction on a case-by-case basis. + // + // Because the donor module is required to be valid, when we encounter a + // type comprised of component types (e.g. an aggregate or pointer), we know + // that its component types will have been considered previously, and that + // |original_id_to_donated_id| will already contain an entry for them. + switch (type_or_value.opcode()) { + case SpvOpTypeVoid: { + // Void has to exist already in order for us to have an entry point. + // Get the existing id of void. + opt::analysis::Void void_type; + new_result_id = GetIRContext()->get_type_mgr()->GetId(&void_type); + assert(new_result_id && + "The module being transformed will always have 'void' type " + "declared."); + } break; + case SpvOpTypeBool: { + // Bool cannot be declared multiple times, so use its existing id if + // present, or add a declaration of Bool with a fresh id if not. + opt::analysis::Bool bool_type; + auto bool_type_id = GetIRContext()->get_type_mgr()->GetId(&bool_type); + if (bool_type_id) { + new_result_id = bool_type_id; + } else { + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeBoolean(new_result_id)); + } + } break; + case SpvOpTypeInt: { + // Int cannot be declared multiple times with the same width and + // signedness, so check whether an existing identical Int type is + // present and use its id if so. Otherwise add a declaration of the + // Int type used by the donor, with a fresh id. + const uint32_t width = type_or_value.GetSingleWordInOperand(0); + const bool is_signed = + static_cast(type_or_value.GetSingleWordInOperand(1)); + opt::analysis::Integer int_type(width, is_signed); + auto int_type_id = GetIRContext()->get_type_mgr()->GetId(&int_type); + if (int_type_id) { + new_result_id = int_type_id; + } else { + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation( + TransformationAddTypeInt(new_result_id, width, is_signed)); + } + } break; + case SpvOpTypeFloat: { + // Similar to SpvOpTypeInt. + const uint32_t width = type_or_value.GetSingleWordInOperand(0); + opt::analysis::Float float_type(width); + auto float_type_id = GetIRContext()->get_type_mgr()->GetId(&float_type); + if (float_type_id) { + new_result_id = float_type_id; + } else { + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeFloat(new_result_id, width)); + } + } break; + case SpvOpTypeVector: { + // It is not legal to have two Vector type declarations with identical + // element types and element counts, so check whether an existing + // identical Vector type is present and use its id if so. Otherwise add + // a declaration of the Vector type used by the donor, with a fresh id. + + // When considering the vector's component type id, we look up the id + // use in the donor to find the id to which this has been remapped. + uint32_t component_type_id = original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(0)); + auto component_type = + GetIRContext()->get_type_mgr()->GetType(component_type_id); + assert(component_type && "The base type should be registered."); + auto component_count = type_or_value.GetSingleWordInOperand(1); + opt::analysis::Vector vector_type(component_type, component_count); + auto vector_type_id = + GetIRContext()->get_type_mgr()->GetId(&vector_type); + if (vector_type_id) { + new_result_id = vector_type_id; + } else { + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeVector( + new_result_id, component_type_id, component_count)); + } + } break; + case SpvOpTypeMatrix: { + // Similar to SpvOpTypeVector. + uint32_t column_type_id = original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(0)); + auto column_type = + GetIRContext()->get_type_mgr()->GetType(column_type_id); + assert(column_type && column_type->AsVector() && + "The column type should be a registered vector type."); + auto column_count = type_or_value.GetSingleWordInOperand(1); + opt::analysis::Matrix matrix_type(column_type, column_count); + auto matrix_type_id = + GetIRContext()->get_type_mgr()->GetId(&matrix_type); + if (matrix_type_id) { + new_result_id = matrix_type_id; + } else { + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeMatrix( + new_result_id, column_type_id, column_count)); + } + + } break; + case SpvOpTypeArray: { + // It is OK to have multiple structurally identical array types, so + // we go ahead and add a remapped version of the type declared by the + // donor. + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypeArray( + new_result_id, + original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(0)), + original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(1)))); + } break; + case SpvOpTypeStruct: { + // Similar to SpvOpTypeArray. + new_result_id = GetFuzzerContext()->GetFreshId(); + std::vector member_type_ids; + type_or_value.ForEachInId( + [&member_type_ids, + &original_id_to_donated_id](const uint32_t* component_type_id) { + member_type_ids.push_back( + original_id_to_donated_id->at(*component_type_id)); + }); + ApplyTransformation( + TransformationAddTypeStruct(new_result_id, member_type_ids)); + } break; + case SpvOpTypePointer: { + // Similar to SpvOpTypeArray. + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddTypePointer( + new_result_id, + AdaptStorageClass(static_cast( + type_or_value.GetSingleWordInOperand(0))), + original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(1)))); + } break; + case SpvOpTypeFunction: { + // It is not OK to have multiple function types that use identical ids + // for their return and parameter types. We thus go through all + // existing function types to look for a match. We do not use the + // type manager here because we want to regard two function types that + // are structurally identical but that differ with respect to the + // actual ids used for pointer types as different. + // + // Example: + // + // %1 = OpTypeVoid + // %2 = OpTypeInt 32 0 + // %3 = OpTypePointer Function %2 + // %4 = OpTypePointer Function %2 + // %5 = OpTypeFunction %1 %3 + // %6 = OpTypeFunction %1 %4 + // + // We regard %5 and %6 as distinct function types here, even though + // they both have the form "uint32* -> void" + + std::vector return_and_parameter_types; + for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) { + return_and_parameter_types.push_back(original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(i))); + } + uint32_t existing_function_id = fuzzerutil::FindFunctionType( + GetIRContext(), return_and_parameter_types); + if (existing_function_id) { + new_result_id = existing_function_id; + } else { + // No match was found, so add a remapped version of the function type + // to the module, with a fresh id. + new_result_id = GetFuzzerContext()->GetFreshId(); + std::vector argument_type_ids; + for (uint32_t i = 1; i < type_or_value.NumInOperands(); i++) { + argument_type_ids.push_back(original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(i))); + } + ApplyTransformation(TransformationAddTypeFunction( + new_result_id, + original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(0)), + argument_type_ids)); + } + } break; + case SpvOpConstantTrue: + case SpvOpConstantFalse: { + // It is OK to have duplicate definitions of True and False, so add + // these to the module, using a remapped Bool type. + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddConstantBoolean( + new_result_id, type_or_value.opcode() == SpvOpConstantTrue)); + } break; + case SpvOpConstant: { + // It is OK to have duplicate constant definitions, so add this to the + // module using a remapped result type. + new_result_id = GetFuzzerContext()->GetFreshId(); + std::vector data_words; + type_or_value.ForEachInOperand( + [&data_words](const uint32_t* in_operand) { + data_words.push_back(*in_operand); + }); + ApplyTransformation(TransformationAddConstantScalar( + new_result_id, + original_id_to_donated_id->at(type_or_value.type_id()), + data_words)); + } break; + case SpvOpConstantComposite: { + // It is OK to have duplicate constant composite definitions, so add + // this to the module using remapped versions of all consituent ids and + // the result type. + new_result_id = GetFuzzerContext()->GetFreshId(); + std::vector constituent_ids; + type_or_value.ForEachInId( + [&constituent_ids, + &original_id_to_donated_id](const uint32_t* constituent_id) { + constituent_ids.push_back( + original_id_to_donated_id->at(*constituent_id)); + }); + ApplyTransformation(TransformationAddConstantComposite( + new_result_id, + original_id_to_donated_id->at(type_or_value.type_id()), + constituent_ids)); + } break; + case SpvOpVariable: { + // This is a global variable that could have one of various storage + // classes. However, we change all global variable pointer storage + // classes (such as Uniform, Input and Output) to private when donating + // pointer types. Thus this variable's pointer type is guaranteed to + // have storage class private. As a result, we simply add a Private + // storage class global variable, using remapped versions of the result + // type and initializer ids for the global variable in the donor. + // + // We regard the added variable as having an arbitrary value. This + // means that future passes can add stores to the variable in any + // way they wish, and pass them as pointer parameters to functions + // without worrying about whether their data might get modified. + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddGlobalVariable( + new_result_id, + original_id_to_donated_id->at(type_or_value.type_id()), + type_or_value.NumInOperands() == 1 + ? 0 + : original_id_to_donated_id->at( + type_or_value.GetSingleWordInOperand(1)), + true)); + } break; + case SpvOpUndef: { + // It is fine to have multiple Undef instructions of the same type, so + // we just add this to the recipient module. + new_result_id = GetFuzzerContext()->GetFreshId(); + ApplyTransformation(TransformationAddGlobalUndef( + new_result_id, + original_id_to_donated_id->at(type_or_value.type_id()))); + } break; + default: { + assert(0 && "Unknown type/value."); + new_result_id = 0; + } break; + } + // Update the id mapping to associate the instruction's result id with its + // corresponding id in the recipient. + original_id_to_donated_id->insert( + {type_or_value.result_id(), new_result_id}); + } +} + +void FuzzerPassDonateModules::HandleFunctions( + opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id, + bool make_livesafe) { + // 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); + + // Donate the functions in reverse topological order. This ensures that a + // function gets donated before any function that depends on it. This allows + // donation of the functions to be separated into a number of transformations, + // each adding one function, such that every prefix of transformations leaves + // the module valid. + for (auto function_id = topological_order.rbegin(); + function_id != topological_order.rend(); ++function_id) { + // Find the function to be donated. + opt::Function* function_to_donate = nullptr; + for (auto& function : *donor_ir_context->module()) { + if (function.result_id() == *function_id) { + function_to_donate = &function; + break; + } + } + assert(function_to_donate && "Function to be donated was not found."); + + // We will collect up protobuf messages representing the donor function's + // instructions here, and use them to create an AddFunction transformation. + std::vector donated_instructions; + + // Scan through the function, remapping each result id that it generates to + // a fresh id. This is necessary because functions include forward + // references, e.g. to labels. + function_to_donate->ForEachInst([this, &original_id_to_donated_id]( + const opt::Instruction* instruction) { + if (instruction->result_id()) { + original_id_to_donated_id->insert( + {instruction->result_id(), GetFuzzerContext()->GetFreshId()}); + } + }); + + // Consider every instruction of the donor function. + function_to_donate->ForEachInst( + [&donated_instructions, + &original_id_to_donated_id](const opt::Instruction* instruction) { + // Get the instruction's input operands into donation-ready form, + // remapping any id uses in the process. + opt::Instruction::OperandList input_operands; + + // Consider each input operand in turn. + for (uint32_t in_operand_index = 0; + in_operand_index < instruction->NumInOperands(); + in_operand_index++) { + std::vector operand_data; + const opt::Operand& in_operand = + instruction->GetInOperand(in_operand_index); + switch (in_operand.type) { + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + // This is an id operand - it consists of a single word of data, + // which needs to be remapped so that it is replaced with the + // donated form of the id. + operand_data.push_back( + original_id_to_donated_id->at(in_operand.words[0])); + break; + default: + // For non-id operands, we just add each of the data words. + for (auto word : in_operand.words) { + operand_data.push_back(word); + } + break; + } + input_operands.push_back({in_operand.type, operand_data}); + } + // Remap the result type and result id (if present) of the + // instruction, and turn it into a protobuf message. + donated_instructions.push_back(MakeInstructionMessage( + instruction->opcode(), + instruction->type_id() + ? original_id_to_donated_id->at(instruction->type_id()) + : 0, + instruction->result_id() + ? original_id_to_donated_id->at(instruction->result_id()) + : 0, + input_operands)); + }); + + if (make_livesafe) { + // Various types and constants must be in place for a function to be made + // live-safe. Add them if not already present. + FindOrCreateBoolType(); // Needed for comparisons + FindOrCreatePointerTo32BitIntegerType( + false, SpvStorageClassFunction); // Needed for adding loop limiters + FindOrCreate32BitIntegerConstant( + 0, false); // Needed for initializing loop limiters + FindOrCreate32BitIntegerConstant( + 1, false); // Needed for incrementing loop limiters + + // Get a fresh id for the variable that will be used as a loop limiter. + const uint32_t loop_limiter_variable_id = + GetFuzzerContext()->GetFreshId(); + // Choose a random loop limit, and add the required constant to the + // module if not already there. + const uint32_t loop_limit = FindOrCreate32BitIntegerConstant( + GetFuzzerContext()->GetRandomLoopLimit(), false); + + // Consider every loop header in the function to donate, and create a + // structure capturing the ids to be used for manipulating the loop + // limiter each time the loop is iterated. + std::vector loop_limiters; + 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); + } + } + + // Consider every access chain in the function to donate, and create a + // structure containing the ids necessary to clamp the access chain + // indices to be in-bounds. + std::vector + access_chain_clamping_info; + for (auto& block : *function_to_donate) { + for (auto& inst : block) { + switch (inst.opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: { + protobufs::AccessChainClampingInfo clamping_info; + clamping_info.set_access_chain_id( + original_id_to_donated_id->at(inst.result_id())); + + auto base_object = donor_ir_context->get_def_use_mgr()->GetDef( + inst.GetSingleWordInOperand(0)); + assert(base_object && "The base object must exist."); + auto pointer_type = donor_ir_context->get_def_use_mgr()->GetDef( + base_object->type_id()); + assert(pointer_type && + pointer_type->opcode() == SpvOpTypePointer && + "The base object must have pointer type."); + + auto should_be_composite_type = + donor_ir_context->get_def_use_mgr()->GetDef( + pointer_type->GetSingleWordInOperand(1)); + + // Walk the access chain, creating fresh ids to facilitate + // clamping each index. For simplicity we do this for every + // index, even though constant indices will not end up being + // clamped. + for (uint32_t index = 1; index < inst.NumInOperands(); index++) { + auto compare_and_select_ids = + clamping_info.add_compare_and_select_ids(); + compare_and_select_ids->set_first( + GetFuzzerContext()->GetFreshId()); + compare_and_select_ids->set_second( + GetFuzzerContext()->GetFreshId()); + + // Get the bound for the component being indexed into. + uint32_t bound = + TransformationAddFunction::GetBoundForCompositeIndex( + donor_ir_context, *should_be_composite_type); + const uint32_t index_id = inst.GetSingleWordInOperand(index); + auto index_inst = + donor_ir_context->get_def_use_mgr()->GetDef(index_id); + auto index_type_inst = + donor_ir_context->get_def_use_mgr()->GetDef( + index_inst->type_id()); + assert(index_type_inst->opcode() == SpvOpTypeInt); + assert(index_type_inst->GetSingleWordInOperand(0) == 32); + opt::analysis::Integer* index_int_type = + donor_ir_context->get_type_mgr() + ->GetType(index_type_inst->result_id()) + ->AsInteger(); + if (index_inst->opcode() != SpvOpConstant) { + // We will have to clamp this index, so we need a constant + // whose value is one less than the bound, to compare + // against and to use as the clamped value. + FindOrCreate32BitIntegerConstant(bound - 1, + index_int_type->IsSigned()); + } + should_be_composite_type = + TransformationAddFunction::FollowCompositeIndex( + donor_ir_context, *should_be_composite_type, index_id); + } + access_chain_clamping_info.push_back(clamping_info); + break; + } + default: + break; + } + } + } + + // 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; + 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 OpUndef. + kill_unreachable_return_value_id = + FindOrCreateGlobalUndef(function_return_type_inst->type_id()); + } + // 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)); + } else { + // Add the function in a non-livesafe manner. + ApplyTransformation(TransformationAddFunction(donated_instructions)); + } + } +} + +std::vector +FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder( + opt::IRContext* context) { + // This is an implementation of Kahn’s algorithm for topological sorting. + + // For each function id, stores the number of distinct functions that call + // the function. + std::map function_in_degree; + + // We first build a call graph for the module, and compute the in-degree for + // each function in the process. + // TODO(afd): If there is functionality elsewhere in the SPIR-V tools + // framework to construct call graphs it could be nice to re-use it here. + std::map> call_graph_edges; + + // Initialize function in-degree and call graph edges to 0 and empty. + for (auto& function : *context->module()) { + function_in_degree[function.result_id()] = 0; + call_graph_edges[function.result_id()] = std::set(); + } + + // Consider every function. + for (auto& function : *context->module()) { + // Avoid considering the same callee of this function multiple times by + // recording known callees. + std::set known_callees; + // Consider every function call instruction in every block. + for (auto& block : function) { + for (auto& instruction : block) { + if (instruction.opcode() != SpvOpFunctionCall) { + continue; + } + // Get the id of the function being called. + uint32_t callee = instruction.GetSingleWordInOperand(0); + if (known_callees.count(callee)) { + // We have already considered a call to this function - ignore it. + continue; + } + // Increase the callee's in-degree and add an edge to the call graph. + function_in_degree[callee]++; + call_graph_edges[function.result_id()].insert(callee); + // Mark the callee as 'known'. + known_callees.insert(callee); + } + } + } + + // This is the sorted order of function ids that we will eventually return. + std::vector result; + + // Populate a queue with all those function ids with in-degree zero. + std::queue 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_edges.at(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; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h new file mode 100644 index 000000000..ef529db70 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_donate_modules.h @@ -0,0 +1,93 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_ + +#include + +#include "source/fuzz/fuzzer_pass.h" +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass that randomly adds code from other SPIR-V modules to the module +// being transformed. +class FuzzerPassDonateModules : public FuzzerPass { + public: + FuzzerPassDonateModules( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations, + const std::vector& donor_suppliers); + + ~FuzzerPassDonateModules(); + + void Apply() override; + + // Donates the global declarations and functions of |donor_ir_context| into + // the fuzzer pass's IR context. |make_livesafe| dictates whether the + // functions of the donated module will be made livesafe (see + // FactFunctionIsLivesafe). + void DonateSingleModule(opt::IRContext* donor_ir_context, bool make_livesafe); + + private: + // Adapts a storage class coming from a donor module so that it will work + // in a recipient module, e.g. by changing Uniform to Private. + static SpvStorageClass AdaptStorageClass(SpvStorageClass donor_storage_class); + + // Identifies all external instruction set imports in |donor_ir_context| and + // populates |original_id_to_donated_id| with a mapping from the donor's id + // for such an import to a corresponding import in the recipient. Aborts if + // no such corresponding import is available. + void HandleExternalInstructionImports( + opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id); + + // Considers all types, globals, constants and undefs in |donor_ir_context|. + // For each instruction, uses |original_to_donated_id| to map its result id to + // either (1) the id of an existing identical instruction in the recipient, or + // (2) to a fresh id, in which case the instruction is also added to the + // recipient (with any operand ids that it uses being remapped via + // |original_id_to_donated_id|). + void HandleTypesAndValues( + opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id); + + // Assumes that |donor_ir_context| does not exhibit recursion. Considers the + // functions in |donor_ir_context|'s call graph in a reverse-topologically- + // sorted order (leaves-to-root), adding each function to the recipient + // module, rewritten to use fresh ids and using |original_id_to_donated_id| to + // remap ids. The |make_livesafe| argument captures whether the functions in + // the module are required to be made livesafe before being added to the + // recipient. + void HandleFunctions(opt::IRContext* donor_ir_context, + std::map* original_id_to_donated_id, + bool make_livesafe); + + // 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 GetFunctionsInCallGraphTopologicalOrder( + opt::IRContext* context); + + // Functions that supply SPIR-V modules + std::vector donor_suppliers_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_DONATE_MODULES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.cpp new file mode 100644 index 000000000..ca1bfb33f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/fuzzer_pass_merge_blocks.h" + +#include + +#include "source/fuzz/transformation_merge_blocks.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassMergeBlocks::FuzzerPassMergeBlocks( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassMergeBlocks::~FuzzerPassMergeBlocks() = default; + +void FuzzerPassMergeBlocks::Apply() { + // First we populate a sequence of transformations that we might consider + // applying. + std::vector potential_transformations; + // We do this by considering every block of every function. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // We probabilistically decide to ignore some blocks. + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfMergingBlocks())) { + continue; + } + // For other blocks, we add a transformation to merge the block into its + // predecessor if that transformation would be applicable. + TransformationMergeBlocks transformation(block.id()); + if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + potential_transformations.push_back(transformation); + } + } + } + + while (!potential_transformations.empty()) { + uint32_t index = GetFuzzerContext()->RandomIndex(potential_transformations); + auto transformation = potential_transformations.at(index); + potential_transformations.erase(potential_transformations.begin() + index); + if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.h new file mode 100644 index 000000000..457e59137 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_merge_blocks.h @@ -0,0 +1,38 @@ +// 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_FUZZER_PASS_MERGE_BLOCKS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_MERGE_BLOCKS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass for merging blocks in the module. +class FuzzerPassMergeBlocks : public FuzzerPass { + public: + FuzzerPassMergeBlocks(opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassMergeBlocks(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_MERGE_BLOCKS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp new file mode 100644 index 000000000..d59c195d7 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.cpp @@ -0,0 +1,99 @@ +// 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/fuzzer_pass_outline_functions.h" + +#include + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/transformation_outline_function.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassOutlineFunctions::FuzzerPassOutlineFunctions( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassOutlineFunctions::~FuzzerPassOutlineFunctions() = default; + +void FuzzerPassOutlineFunctions::Apply() { + std::vector original_functions; + for (auto& function : *GetIRContext()->module()) { + original_functions.push_back(&function); + } + for (auto& function : original_functions) { + if (!GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfOutliningFunction())) { + continue; + } + std::vector blocks; + for (auto& block : *function) { + blocks.push_back(&block); + } + auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)]; + auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function); + auto postdominator_analysis = + GetIRContext()->GetPostDominatorAnalysis(function); + std::vector candidate_exit_blocks; + for (auto postdominates_entry_block = entry_block; + postdominates_entry_block != nullptr; + postdominates_entry_block = postdominator_analysis->ImmediateDominator( + postdominates_entry_block)) { + if (dominator_analysis->Dominates(entry_block, + postdominates_entry_block)) { + 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 region_blocks = TransformationOutlineFunction::GetRegionBlocks( + GetIRContext(), entry_block, exit_block); + std::map input_id_to_fresh_id; + for (auto id : TransformationOutlineFunction::GetRegionInputIds( + GetIRContext(), region_blocks, exit_block)) { + input_id_to_fresh_id[id] = GetFuzzerContext()->GetFreshId(); + } + std::map output_id_to_fresh_id; + for (auto id : TransformationOutlineFunction::GetRegionOutputIds( + GetIRContext(), region_blocks, exit_block)) { + output_id_to_fresh_id[id] = GetFuzzerContext()->GetFreshId(); + } + TransformationOutlineFunction transformation( + entry_block->id(), exit_block->id(), + /*new_function_struct_return_type_id*/ + GetFuzzerContext()->GetFreshId(), + /*new_function_type_id*/ GetFuzzerContext()->GetFreshId(), + /*new_function_id*/ GetFuzzerContext()->GetFreshId(), + /*new_function_region_entry_block*/ + GetFuzzerContext()->GetFreshId(), + /*new_caller_result_id*/ GetFuzzerContext()->GetFreshId(), + /*new_callee_result_id*/ GetFuzzerContext()->GetFreshId(), + /*input_id_to_fresh_id*/ std::move(input_id_to_fresh_id), + /*output_id_to_fresh_id*/ std::move(output_id_to_fresh_id)); + if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = transformation.ToMessage(); + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h new file mode 100644 index 000000000..5448e7df7 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_outline_functions.h @@ -0,0 +1,40 @@ +// 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_FUZZER_PASS_OUTLINE_FUNCTIONS_H_ +#define SOURCE_FUZZ_FUZZER_PASS_OUTLINE_FUNCTIONS_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass for outlining single-entry single-exit regions of a control +// flow graph into their own functions. +class FuzzerPassOutlineFunctions : public FuzzerPass { + public: + FuzzerPassOutlineFunctions( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassOutlineFunctions(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_FUZZER_PASS_OUTLINE_FUNCTIONS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp index 192f89657..f9f9969cd 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp @@ -103,10 +103,23 @@ bool PhiIdsOkForNewEdge( } phi_index++; } - // Return false if not all of the ids for extending OpPhi instructions are - // needed. This might turn out to be stricter than necessary; perhaps it would - // be OK just to not use the ids in this case. - return phi_index == static_cast(phi_ids.size()); + // We allow some of the ids provided for extending OpPhi instructions to be + // unused. Their presence does no harm, and requiring a perfect match may + // make transformations less likely to cleanly apply. + return true; +} + +uint32_t MaybeGetBoolConstantId(opt::IRContext* context, bool value) { + opt::analysis::Bool bool_type; + auto registered_bool_type = + context->get_type_mgr()->GetRegisteredType(&bool_type); + if (!registered_bool_type) { + return 0; + } + opt::analysis::BoolConstant bool_constant(registered_bool_type->AsBool(), + value); + return context->get_constant_mgr()->FindDeclaredConstant( + &bool_constant, context->get_type_mgr()->GetId(&bool_type)); } void AddUnreachableEdgeAndUpdateOpPhis( @@ -119,12 +132,10 @@ void AddUnreachableEdgeAndUpdateOpPhis( "Precondition on terminator of bb_from is not satisfied"); // Get the id of the boolean constant to be used as the condition. - opt::analysis::Bool bool_type; - opt::analysis::BoolConstant bool_constant( - context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(), - condition_value); - uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant( - &bool_constant, context->get_type_mgr()->GetId(&bool_type)); + uint32_t bool_id = MaybeGetBoolConstantId(context, condition_value); + assert( + bool_id && + "Precondition that condition value must be available is not satisfied"); const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to); auto successor = bb_from->terminator()->GetSingleWordInOperand(0); @@ -147,13 +158,11 @@ void AddUnreachableEdgeAndUpdateOpPhis( break; } assert(phi_index < static_cast(phi_ids.size()) && - "There should be exactly one phi id per OpPhi instruction."); + "There should be at least one phi id per OpPhi instruction."); inst.AddOperand({SPV_OPERAND_TYPE_ID, {phi_ids[phi_index]}}); inst.AddOperand({SPV_OPERAND_TYPE_ID, {bb_from->id()}}); phi_index++; } - assert(phi_index == static_cast(phi_ids.size()) && - "There should be exactly one phi id per OpPhi instruction."); } } @@ -217,6 +226,20 @@ bool CanMakeSynonymOf(opt::IRContext* ir_context, opt::Instruction* inst) { // We can only make a synonym of an instruction that has a type. return false; } + auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id()); + if (type_inst->opcode() == SpvOpTypePointer) { + switch (inst->opcode()) { + case SpvOpConstantNull: + case SpvOpUndef: + // We disallow making synonyms of null or undefined pointers. This is + // to provide the property that if the original shader exhibited no bad + // pointer accesses, the transformed shader will not either. + return false; + default: + break; + } + } + // We do not make synonyms of objects that have decorations: if the synonym is // not decorated analogously, using the original object vs. its synonymous // form may not be equivalent. @@ -247,33 +270,36 @@ uint32_t WalkCompositeTypeIndices( auto should_be_composite_type = context->get_def_use_mgr()->GetDef(sub_object_type_id); assert(should_be_composite_type && "The type should exist."); - if (SpvOpTypeArray == should_be_composite_type->opcode()) { - auto array_length = GetArraySize(*should_be_composite_type, context); - if (array_length == 0 || index >= array_length) { - return 0; + switch (should_be_composite_type->opcode()) { + case SpvOpTypeArray: { + auto array_length = GetArraySize(*should_be_composite_type, context); + if (array_length == 0 || index >= array_length) { + return 0; + } + sub_object_type_id = + should_be_composite_type->GetSingleWordInOperand(0); + break; } - sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0); - } else if (SpvOpTypeMatrix == should_be_composite_type->opcode()) { - auto matrix_column_count = - should_be_composite_type->GetSingleWordInOperand(1); - if (index >= matrix_column_count) { - return 0; + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + auto count = should_be_composite_type->GetSingleWordInOperand(1); + if (index >= count) { + return 0; + } + sub_object_type_id = + should_be_composite_type->GetSingleWordInOperand(0); + break; } - sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0); - } else if (SpvOpTypeStruct == should_be_composite_type->opcode()) { - if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { - return 0; + case SpvOpTypeStruct: { + if (index >= GetNumberOfStructMembers(*should_be_composite_type)) { + return 0; + } + sub_object_type_id = + should_be_composite_type->GetSingleWordInOperand(index); + break; } - sub_object_type_id = - should_be_composite_type->GetSingleWordInOperand(index); - } else if (SpvOpTypeVector == should_be_composite_type->opcode()) { - auto vector_length = should_be_composite_type->GetSingleWordInOperand(1); - if (index >= vector_length) { + default: return 0; - } - sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0); - } else { - return 0; } } return sub_object_type_id; @@ -302,7 +328,8 @@ uint32_t GetArraySize(const opt::Instruction& array_type_instruction, bool IsValid(opt::IRContext* context) { std::vector binary; context->module()->ToBinary(&binary, false); - return SpirvTools(context->grammar().target_env()).Validate(binary); + SpirvTools tools(context->grammar().target_env()); + return tools.Validate(binary); } std::unique_ptr CloneIRContext(opt::IRContext* context) { @@ -312,6 +339,58 @@ std::unique_ptr CloneIRContext(opt::IRContext* context) { binary.size()); } +bool IsNonFunctionTypeId(opt::IRContext* ir_context, uint32_t id) { + auto type = ir_context->get_type_mgr()->GetType(id); + return type && !type->AsFunction(); +} + +bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id) { + bool result = false; + ir_context->get_def_use_mgr()->WhileEachUse( + block_id, + [&result](const opt::Instruction* use_instruction, + uint32_t /*unused*/) -> bool { + switch (use_instruction->opcode()) { + case SpvOpLoopMerge: + case SpvOpSelectionMerge: + result = true; + return false; + default: + return true; + } + }); + return result; +} + +uint32_t FindFunctionType(opt::IRContext* ir_context, + const std::vector& type_ids) { + // Look through the existing types for a match. + for (auto& type_or_value : ir_context->types_values()) { + if (type_or_value.opcode() != SpvOpTypeFunction) { + // We are only interested in function types. + continue; + } + if (type_or_value.NumInOperands() != type_ids.size()) { + // Not a match: different numbers of arguments. + continue; + } + // Check whether the return type and argument types match. + bool input_operands_match = true; + for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) { + if (type_ids[i] != type_or_value.GetSingleWordInOperand(i)) { + input_operands_match = false; + break; + } + } + if (input_operands_match) { + // Everything matches. + return type_or_value.result_id(); + } + } + // No match was found. + return 0; +} + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h index c54688812..f0a2953fd 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h @@ -25,9 +25,12 @@ namespace spvtools { namespace fuzz { -// Provides global utility methods for use by the fuzzer +// Provides types and global utility methods for use by the fuzzer namespace fuzzerutil { +// Function type that produces a SPIR-V module. +using ModuleSupplier = std::function()>; + // Returns true if and only if the module does not define the given id. bool IsFreshId(opt::IRContext* context, uint32_t id); @@ -49,8 +52,13 @@ bool PhiIdsOkForNewEdge( opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to, const google::protobuf::RepeatedField& phi_ids); -// Requires that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) holds, -// and that bb_from ends with "OpBranch %some_block". Turns OpBranch into +// Returns the id of a boolean constant with value |value| if it exists in the +// module, or 0 otherwise. +uint32_t MaybeGetBoolConstantId(opt::IRContext* context, bool value); + +// Requires that a boolean constant with value |condition_value| is available, +// that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) holds, and that +// bb_from ends with "OpBranch %some_block". Turns OpBranch into // "OpBranchConditional |condition_value| ...", such that control will branch // to %some_block, with |bb_to| being the unreachable alternative. Updates // OpPhi instructions in |bb_to| using |phi_ids| so that the new edge is valid. @@ -116,6 +124,19 @@ bool IsValid(opt::IRContext* context); // parsing it again. std::unique_ptr CloneIRContext(opt::IRContext* context); +// Returns true if and only if |id| is the id of a type that is not a function +// type. +bool IsNonFunctionTypeId(opt::IRContext* ir_context, uint32_t id); + +// Returns true if and only if |block_id| is a merge block or continue target +bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id); + +// Returns the result id of an instruction of the form: +// %id = OpTypeFunction |type_ids| +// or 0 if no such instruction exists. +uint32_t FindFunctionType(opt::IRContext* ir_context, + const std::vector& type_ids); + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/instruction_message.cpp b/3rdparty/spirv-tools/source/fuzz/instruction_message.cpp new file mode 100644 index 000000000..44777aede --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/instruction_message.cpp @@ -0,0 +1,68 @@ +// 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/instruction_message.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +protobufs::Instruction MakeInstructionMessage( + SpvOp opcode, uint32_t result_type_id, uint32_t result_id, + const opt::Instruction::OperandList& input_operands) { + protobufs::Instruction result; + result.set_opcode(opcode); + result.set_result_type_id(result_type_id); + result.set_result_id(result_id); + for (auto& operand : input_operands) { + auto operand_message = result.add_input_operand(); + operand_message->set_operand_type(static_cast(operand.type)); + for (auto operand_word : operand.words) { + operand_message->add_operand_data(operand_word); + } + } + return result; +} + +std::unique_ptr InstructionFromMessage( + opt::IRContext* ir_context, + const protobufs::Instruction& instruction_message) { + // First, update the module's id bound with respect to the new instruction, + // if it has a result id. + if (instruction_message.result_id()) { + fuzzerutil::UpdateModuleIdBound(ir_context, + instruction_message.result_id()); + } + // Now create a sequence of input operands from the input operand data in the + // protobuf message. + opt::Instruction::OperandList in_operands; + for (auto& operand_message : instruction_message.input_operand()) { + opt::Operand::OperandData operand_data; + for (auto& word : operand_message.operand_data()) { + operand_data.push_back(word); + } + in_operands.push_back( + {static_cast(operand_message.operand_type()), + operand_data}); + } + // Create and return the instruction. + return MakeUnique( + ir_context, static_cast(instruction_message.opcode()), + instruction_message.result_type_id(), instruction_message.result_id(), + in_operands); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/instruction_message.h b/3rdparty/spirv-tools/source/fuzz/instruction_message.h new file mode 100644 index 000000000..c010c2f6a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/instruction_message.h @@ -0,0 +1,43 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_ +#define SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_ + +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/instruction.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +// Creates an Instruction protobuf message from its component parts. +protobufs::Instruction MakeInstructionMessage( + SpvOp opcode, uint32_t result_type_id, uint32_t result_id, + const opt::Instruction::OperandList& input_operands); + +// Creates and returns an opt::Instruction from protobuf message +// |instruction_message|, relative to |ir_context|. In the process, the module +// id bound associated with |ir_context| is updated to be at least as large as +// the result id (if any) associated with the new instruction. +std::unique_ptr InstructionFromMessage( + opt::IRContext* ir_context, + const protobufs::Instruction& instruction_message); + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_INSTRUCTION_MESSAGE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto index b33c2e5dc..52a3a788c 100644 --- a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -21,6 +21,16 @@ syntax = "proto3"; package spvtools.fuzz.protobufs; +message UInt32Pair { + + // A pair of uint32s; useful for defining mappings. + + uint32 first = 1; + + uint32 second = 2; + +} + message InstructionDescriptor { // Describes an instruction in some block of a function with respect to a @@ -116,6 +126,37 @@ message UniformBufferElementDescriptor { } +message InstructionOperand { + + // Represents an operand to a SPIR-V instruction. + + // The type of the operand. + uint32 operand_type = 1; + + // The data associated with the operand. For most operands (e.g. ids, + // storage classes and literals) this will be a single word. + repeated uint32 operand_data = 2; + +} + +message Instruction { + + // Represents a SPIR-V instruction. + + // The instruction's opcode (e.g. OpLabel). + uint32 opcode = 1; + + // The id of the instruction's result type; 0 if there is no result type. + uint32 result_type_id = 2; + + // The id of the instruction's result; 0 if there is no result. + uint32 result_id = 3; + + // Zero or more input operands. + repeated InstructionOperand input_operand = 4; + +} + message FactSequence { repeated Fact fact = 1; } @@ -125,6 +166,9 @@ message Fact { // Order the fact options by numeric id (rather than alphabetically). FactConstantUniform constant_uniform_fact = 1; FactDataSynonym data_synonym_fact = 2; + FactBlockIsDead block_is_dead_fact = 3; + FactFunctionIsLivesafe function_is_livesafe_fact = 4; + FactValueOfVariableIsArbitrary value_of_variable_is_arbitrary = 5; } } @@ -159,6 +203,99 @@ message FactDataSynonym { } +message FactBlockIsDead { + + // Records the fact that a block is guaranteed to be dynamically unreachable. + // This is useful because it informs the fuzzer that rather arbitrary changes + // can be made to this block. + + uint32 block_id = 1; +} + +message FactFunctionIsLivesafe { + + // Records the fact that a function is guaranteed to be "livesafe", meaning + // that it will not make out-of-bounds accesses, does not contain reachable + // OpKill or OpUnreachable instructions, does not contain loops that will + // execute for large numbers of iterations, and only invokes other livesafe + // functions. + + uint32 function_id = 1; +} + +message FactValueOfVariableIsArbitrary { + + // Records the fact that the value stored in the variable or function + // parameter with the given id can be arbitrary: the module's observable + // behaviour does not depend on it. This means that arbitrary stores can be + // made to the variable, and that nothing can be guaranteed about values + // loaded from the variable. + + // The result id of an OpVariable instruction, or an OpFunctionParameter + // instruction with pointer type + uint32 variable_id = 1; +} + +message AccessChainClampingInfo { + + // When making a function livesafe it is necessary to clamp the indices that + // occur as operands to access chain instructions so that they are guaranteed + // to be in bounds. This message type allows an access chain instruction to + // have an associated sequence of ids that are reserved for comparing an + // access chain index with a bound (e.g. an array size), and selecting + // between the access chain index (if it is within bounds) and the bound (if + // it is not). + // + // This allows turning an instruction of the form: + // + // %result = OpAccessChain %type %object ... %index ... + // + // into: + // + // %t1 = OpULessThanEqual %bool %index %bound_minus_one + // %t2 = OpSelect %int_type %t1 %index %bound_minus_one + // %result = OpAccessChain %type %object ... %t2 ... + + // The result id of an OpAccessChain or OpInBoundsAccessChain instruction. + uint32 access_chain_id = 1; + + // A series of pairs of fresh ids, one per access chain index, for the results + // of a compare instruction and a select instruction, serving the roles of %t1 + // and %t2 in the above example. + repeated UInt32Pair compare_and_select_ids = 2; + +} + +message LoopLimiterInfo { + + // Structure capturing the information required to manipulate a loop limiter + // at a loop header. + + // The header for the loop. + uint32 loop_header_id = 1; + + // A fresh id into which the loop limiter's current value can be loaded. + uint32 load_id = 2; + + // A fresh id that can be used to increment the loaded value by 1. + uint32 increment_id = 3; + + // A fresh id that can be used to compare the loaded value with the loop + // limit. + uint32 compare_id = 4; + + // A fresh id that can be used to compute the conjunction or disjunction of + // an original loop exit condition with |compare_id|, if the loop's back edge + // block can conditionally exit the loop. + uint32 logical_op_id = 5; + + // A sequence of ids suitable for extending OpPhi instructions of the loop + // merge block if it did not previously have an incoming edge from the loop + // back edge block. + repeated uint32 phi_id = 6; + +} + message TransformationSequence { repeated Transformation transformation = 1; } @@ -190,6 +327,18 @@ message Transformation { TransformationSetMemoryOperandsMask set_memory_operands_mask = 20; TransformationCompositeExtract composite_extract = 21; TransformationVectorShuffle vector_shuffle = 22; + TransformationOutlineFunction outline_function = 23; + TransformationMergeBlocks merge_blocks = 24; + TransformationAddTypeVector add_type_vector = 25; + TransformationAddTypeArray add_type_array = 26; + TransformationAddTypeMatrix add_type_matrix = 27; + TransformationAddTypeStruct add_type_struct = 28; + TransformationAddTypeFunction add_type_function = 29; + TransformationAddConstantComposite add_constant_composite = 30; + TransformationAddGlobalVariable add_global_variable = 31; + TransformationAddGlobalUndef add_global_undef = 32; + TransformationAddFunction add_function = 33; + TransformationAddDeadBlock add_dead_block = 34; // Add additional option using the next available number. } } @@ -206,9 +355,24 @@ message TransformationAddConstantBoolean { } +message TransformationAddConstantComposite { + + // Adds a constant of the given composite type to the module. + + // Fresh id for the composite + uint32 fresh_id = 1; + + // A composite type id + uint32 type_id = 2; + + // Constituent ids for the composite + repeated uint32 constituent_id = 3; + +} + message TransformationAddConstantScalar { - // Adds a constant of the given scalar type + // Adds a constant of the given scalar type. // Id for the constant uint32 fresh_id = 1; @@ -221,6 +385,25 @@ message TransformationAddConstantScalar { } +message TransformationAddDeadBlock { + + // Adds a new block to the module that is statically reachable from an + // existing block, but dynamically unreachable. + + // Fresh id for the dead block + uint32 fresh_id = 1; + + // Id of an existing block terminated with OpBranch, such that this OpBranch + // can be replaced with an OpBranchConditional to its exiting successor or + // the dead block + uint32 existing_block = 2; + + // Determines whether the condition associated with the OpBranchConditional + // is true or false + bool condition_value = 3; + +} + message TransformationAddDeadBreak { // A transformation that turns a basic block that unconditionally branches to @@ -262,6 +445,77 @@ message TransformationAddDeadContinue { } +message TransformationAddFunction { + + // Adds a SPIR-V function to the module. + + // The series of instructions that comprise the function. + repeated Instruction instruction = 1; + + // True if and only if the given function should be made livesafe (see + // FactFunctionIsLivesafe for definition). + bool is_livesafe = 2; + + // Fresh id for a new variable that will serve as a "loop limiter" for the + // function; only relevant if |is_livesafe| holds. + uint32 loop_limiter_variable_id = 3; + + // Id of an existing unsigned integer constant providing the maximum value + // that the loop limiter can reach before the loop is broken from; only + // relevant if |is_livesafe| holds. + uint32 loop_limit_constant_id = 4; + + // Fresh ids for each loop in the function that allow the loop limiter to be + // manipulated; only relevant if |is_livesafe| holds. + repeated LoopLimiterInfo loop_limiter_info = 5; + + // Id of an existing global value with the same return type as the function + // that can be used to replace OpKill and OpReachable instructions with + // ReturnValue instructions. Ignored if the function has void return type. + uint32 kill_unreachable_return_value_id = 6; + + // A mapping (represented as a sequence) from every access chain result id in + // the function to the ids required to clamp its indices to ensure they are in + // bounds. + repeated AccessChainClampingInfo access_chain_clamping_info = 7; + +} + +message TransformationAddGlobalUndef { + + // Adds an undefined value of a given type to the module at global scope. + + // Fresh id for the undefined value + uint32 fresh_id = 1; + + // The type of the undefined value + uint32 type_id = 2; + +} + +message TransformationAddGlobalVariable { + + // Adds a global variable of the given type to the module, with Private + // storage class and optionally with an initializer. + + // Fresh id for the global variable + uint32 fresh_id = 1; + + // The type of the global variable + uint32 type_id = 2; + + // Optional initializer; 0 if there is no initializer + uint32 initializer_id = 3; + + // True if and only if the value of the variable should be regarded, in + // general, as totally unknown and subject to change (even if, due to an + // initializer, the original value is known). This is the case for variables + // added when a module is donated, for example, and means that stores to such + // variables can be performed in an arbitrary fashion. + bool value_is_arbitrary = 4; + +} + message TransformationAddNoContractionDecoration { // Applies OpDecorate NoContraction to the given result id @@ -271,6 +525,21 @@ message TransformationAddNoContractionDecoration { } +message TransformationAddTypeArray { + + // Adds an array type of the given element type and size to the module + + // Fresh id for the array type + uint32 fresh_id = 1; + + // The array's element type + uint32 element_type_id = 2; + + // The array's size + uint32 size_id = 3; + +} + message TransformationAddTypeBoolean { // Adds OpTypeBool to the module @@ -292,6 +561,21 @@ message TransformationAddTypeFloat { } +message TransformationAddTypeFunction { + + // Adds a function type to the module + + // Fresh id for the function type + uint32 fresh_id = 1; + + // The function's return type + uint32 return_type_id = 2; + + // The function's argument types + repeated uint32 argument_type_id = 3; + +} + message TransformationAddTypeInt { // Adds OpTypeInt to the module with the given width and signedness @@ -307,6 +591,22 @@ message TransformationAddTypeInt { } +message TransformationAddTypeMatrix { + + // Adds a matrix type to the module + + // Fresh id for the matrix type + uint32 fresh_id = 1; + + // The matrix's column type, which must be a floating-point vector (as per + // the "data rules" in the SPIR-V specification). + uint32 column_type_id = 2; + + // The matrix's column count + uint32 column_count = 3; + +} + message TransformationAddTypePointer { // Adds OpTypePointer to the module, with the given storage class and base @@ -323,6 +623,33 @@ message TransformationAddTypePointer { } +message TransformationAddTypeStruct { + + // Adds a struct type to the module + + // Fresh id for the struct type + uint32 fresh_id = 1; + + // The struct's member types + repeated uint32 member_type_id = 3; + +} + +message TransformationAddTypeVector { + + // Adds a vector type to the module + + // Fresh id for the vector type + uint32 fresh_id = 1; + + // The vector's component type + uint32 component_type_id = 2; + + // The vector's component count + uint32 component_count = 3; + +} + message TransformationCompositeConstruct { // A transformation that introduces an OpCompositeConstruct instruction to @@ -380,6 +707,16 @@ message TransformationCopyObject { } +message TransformationMergeBlocks { + + // A transformation that merges a block with its predecessor. + + // The id of the block that is to be merged with its predecessor; the merged + // block will have the *predecessor's* id. + uint32 block_id = 1; + +} + message TransformationMoveBlockDown { // A transformation that moves a basic block to be one position lower in @@ -389,6 +726,53 @@ message TransformationMoveBlockDown { uint32 block_id = 1; } +message TransformationOutlineFunction { + + // A transformation that outlines a single-entry single-exit region of a + // control flow graph into a separate function, and replaces the region with + // a call to that function. + + // Id of the entry block of the single-entry single-exit region to be outlined + uint32 entry_block = 1; + + // Id of the exit block of the single-entry single-exit region to be outlined + uint32 exit_block = 2; + + // Id of a struct that will store the return values of the new function + uint32 new_function_struct_return_type_id = 3; + + // A fresh id for the type of the outlined function + uint32 new_function_type_id = 4; + + // A fresh id for the outlined function itself + uint32 new_function_id = 5; + + // A fresh id to represent the block in the outlined function that represents + // the first block of the outlined region. + uint32 new_function_region_entry_block = 6; + + // A fresh id for the result of the OpFunctionCall instruction that will call + // the outlined function + uint32 new_caller_result_id = 7; + + // A fresh id to capture the return value of the outlined function - the + // argument to OpReturn + uint32 new_callee_result_id = 8; + + // Ids defined outside the region and used inside the region will become + // parameters to the outlined function. This is a mapping from used ids to + // fresh parameter ids. + repeated UInt32Pair input_id_to_fresh_id = 9; + + // Ids defined inside the region and used outside the region will become + // fresh ids defined by the outlined function, which get copied into the + // function's struct return value and then copied into their destination ids + // by the caller. This is a mapping from original ids to corresponding fresh + // ids. + repeated UInt32Pair output_id_to_fresh_id = 10; + +} + message TransformationReplaceBooleanConstantWithConstantBinary { // A transformation to capture replacing a use of a boolean constant with diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.cpp b/3rdparty/spirv-tools/source/fuzz/transformation.cpp index d8fc92fa4..8037af15e 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation.cpp @@ -16,19 +16,32 @@ #include +#include "source/fuzz/fuzzer_util.h" #include "source/fuzz/transformation_add_constant_boolean.h" +#include "source/fuzz/transformation_add_constant_composite.h" #include "source/fuzz/transformation_add_constant_scalar.h" +#include "source/fuzz/transformation_add_dead_block.h" #include "source/fuzz/transformation_add_dead_break.h" #include "source/fuzz/transformation_add_dead_continue.h" +#include "source/fuzz/transformation_add_function.h" +#include "source/fuzz/transformation_add_global_undef.h" +#include "source/fuzz/transformation_add_global_variable.h" #include "source/fuzz/transformation_add_no_contraction_decoration.h" +#include "source/fuzz/transformation_add_type_array.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" #include "source/fuzz/transformation_add_type_int.h" +#include "source/fuzz/transformation_add_type_matrix.h" #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_composite_construct.h" #include "source/fuzz/transformation_composite_extract.h" #include "source/fuzz/transformation_copy_object.h" +#include "source/fuzz/transformation_merge_blocks.h" #include "source/fuzz/transformation_move_block_down.h" +#include "source/fuzz/transformation_outline_function.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_id_with_synonym.h" @@ -51,28 +64,52 @@ std::unique_ptr Transformation::FromMessage( case protobufs::Transformation::TransformationCase::kAddConstantBoolean: return MakeUnique( message.add_constant_boolean()); + case protobufs::Transformation::TransformationCase::kAddConstantComposite: + return MakeUnique( + message.add_constant_composite()); case protobufs::Transformation::TransformationCase::kAddConstantScalar: return MakeUnique( message.add_constant_scalar()); + case protobufs::Transformation::TransformationCase::kAddDeadBlock: + return MakeUnique(message.add_dead_block()); case protobufs::Transformation::TransformationCase::kAddDeadBreak: return MakeUnique(message.add_dead_break()); case protobufs::Transformation::TransformationCase::kAddDeadContinue: return MakeUnique( message.add_dead_continue()); + case protobufs::Transformation::TransformationCase::kAddFunction: + return MakeUnique(message.add_function()); + case protobufs::Transformation::TransformationCase::kAddGlobalUndef: + return MakeUnique( + message.add_global_undef()); + case protobufs::Transformation::TransformationCase::kAddGlobalVariable: + return MakeUnique( + message.add_global_variable()); case protobufs::Transformation::TransformationCase:: kAddNoContractionDecoration: return MakeUnique( message.add_no_contraction_decoration()); + case protobufs::Transformation::TransformationCase::kAddTypeArray: + return MakeUnique(message.add_type_array()); case protobufs::Transformation::TransformationCase::kAddTypeBoolean: return MakeUnique( message.add_type_boolean()); case protobufs::Transformation::TransformationCase::kAddTypeFloat: return MakeUnique(message.add_type_float()); + case protobufs::Transformation::TransformationCase::kAddTypeFunction: + return MakeUnique( + message.add_type_function()); case protobufs::Transformation::TransformationCase::kAddTypeInt: return MakeUnique(message.add_type_int()); + case protobufs::Transformation::TransformationCase::kAddTypeMatrix: + return MakeUnique(message.add_type_matrix()); case protobufs::Transformation::TransformationCase::kAddTypePointer: return MakeUnique( message.add_type_pointer()); + case protobufs::Transformation::TransformationCase::kAddTypeStruct: + return MakeUnique(message.add_type_struct()); + case protobufs::Transformation::TransformationCase::kAddTypeVector: + return MakeUnique(message.add_type_vector()); case protobufs::Transformation::TransformationCase::kCompositeConstruct: return MakeUnique( message.composite_construct()); @@ -81,8 +118,13 @@ std::unique_ptr Transformation::FromMessage( message.composite_extract()); case protobufs::Transformation::TransformationCase::kCopyObject: return MakeUnique(message.copy_object()); + case protobufs::Transformation::TransformationCase::kMergeBlocks: + return MakeUnique(message.merge_blocks()); case protobufs::Transformation::TransformationCase::kMoveBlockDown: return MakeUnique(message.move_block_down()); + case protobufs::Transformation::TransformationCase::kOutlineFunction: + return MakeUnique( + message.outline_function()); case protobufs::Transformation::TransformationCase:: kReplaceBooleanConstantWithConstantBinary: return MakeUnique( @@ -118,5 +160,18 @@ std::unique_ptr Transformation::FromMessage( return nullptr; } +bool Transformation::CheckIdIsFreshAndNotUsedByThisTransformation( + uint32_t id, opt::IRContext* context, + std::set* ids_used_by_this_transformation) { + if (!fuzzerutil::IsFreshId(context, id)) { + return false; + } + if (ids_used_by_this_transformation->count(id) != 0) { + return false; + } + ids_used_by_this_transformation->insert(id); + return true; +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.h b/3rdparty/spirv-tools/source/fuzz/transformation.h index c6b852fd7..dbe803f35 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation.h @@ -83,6 +83,15 @@ class Transformation { // representation of a transformation given by |message|. static std::unique_ptr FromMessage( const protobufs::Transformation& message); + + // Helper that returns true if and only if (a) |id| is a fresh id for the + // module, and (b) |id| is not in |ids_used_by_this_transformation|, a set of + // ids already known to be in use by a transformation. This is useful when + // checking id freshness for a transformation that uses many ids, all of which + // must be distinct. + static bool CheckIdIsFreshAndNotUsedByThisTransformation( + uint32_t id, opt::IRContext* context, + std::set* ids_used_by_this_transformation); }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.cpp new file mode 100644 index 000000000..7ba1ea4d7 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.cpp @@ -0,0 +1,130 @@ +// 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/transformation_add_constant_composite.h" + +#include + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddConstantComposite::TransformationAddConstantComposite( + const spvtools::fuzz::protobufs::TransformationAddConstantComposite& + message) + : message_(message) {} + +TransformationAddConstantComposite::TransformationAddConstantComposite( + uint32_t fresh_id, uint32_t type_id, + const std::vector& constituent_ids) { + message_.set_fresh_id(fresh_id); + message_.set_type_id(type_id); + for (auto constituent_id : constituent_ids) { + message_.add_constituent_id(constituent_id); + } +} + +bool TransformationAddConstantComposite::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // Check that the given id is fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // Check that the composite type id is an instruction id. + auto composite_type_instruction = + context->get_def_use_mgr()->GetDef(message_.type_id()); + if (!composite_type_instruction) { + return false; + } + // Gather up the operands for the composite constant, in the process checking + // whether the given type really defines a composite. + std::vector constituent_type_ids; + switch (composite_type_instruction->opcode()) { + case SpvOpTypeArray: + for (uint32_t index = 0; + index < + fuzzerutil::GetArraySize(*composite_type_instruction, context); + index++) { + constituent_type_ids.push_back( + composite_type_instruction->GetSingleWordInOperand(0)); + } + break; + case SpvOpTypeMatrix: + case SpvOpTypeVector: + for (uint32_t index = 0; + index < composite_type_instruction->GetSingleWordInOperand(1); + index++) { + constituent_type_ids.push_back( + composite_type_instruction->GetSingleWordInOperand(0)); + } + break; + case SpvOpTypeStruct: + composite_type_instruction->ForEachInOperand( + [&constituent_type_ids](const uint32_t* member_type_id) { + constituent_type_ids.push_back(*member_type_id); + }); + break; + default: + // Not a composite type. + return false; + } + + // Check that the number of provided operands matches the number of + // constituents required by the type. + if (constituent_type_ids.size() != + static_cast(message_.constituent_id().size())) { + return false; + } + + // Check that every provided operand refers to an instruction of the + // corresponding constituent type. + for (uint32_t index = 0; index < constituent_type_ids.size(); index++) { + auto constituent_instruction = + context->get_def_use_mgr()->GetDef(message_.constituent_id(index)); + if (!constituent_instruction) { + return false; + } + if (constituent_instruction->type_id() != constituent_type_ids.at(index)) { + return false; + } + } + return true; +} + +void TransformationAddConstantComposite::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + for (auto constituent_id : message_.constituent_id()) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {constituent_id}}); + } + context->module()->AddGlobalValue(MakeUnique( + context, SpvOpConstantComposite, message_.type_id(), message_.fresh_id(), + in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddConstantComposite::ToMessage() + const { + protobufs::Transformation result; + *result.mutable_add_constant_composite() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.h new file mode 100644 index 000000000..9a824a047 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_constant_composite.h @@ -0,0 +1,58 @@ +// 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_TRANSFORMATION_ADD_CONSTANT_COMPOSITE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_COMPOSITE_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddConstantComposite : public Transformation { + public: + explicit TransformationAddConstantComposite( + const protobufs::TransformationAddConstantComposite& message); + + TransformationAddConstantComposite( + uint32_t fresh_id, uint32_t type_id, + const std::vector& constituent_ids); + + // - |message_.fresh_id| must be a fresh id + // - |message_.type_id| must be the id of a composite type + // - |message_.constituent_id| must refer to ids that match the constituent + // types of this composite type + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpConstantComposite instruction defining a constant of type + // |message_.type_id|, using |message_.constituent_id| as constituents, with + // result id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddConstantComposite message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_CONSTANT_COMPOSITE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.cpp new file mode 100644 index 000000000..b58f75e52 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.cpp @@ -0,0 +1,169 @@ +// 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/transformation_add_dead_block.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddDeadBlock::TransformationAddDeadBlock( + const spvtools::fuzz::protobufs::TransformationAddDeadBlock& message) + : message_(message) {} + +TransformationAddDeadBlock::TransformationAddDeadBlock(uint32_t fresh_id, + uint32_t existing_block, + bool condition_value) { + message_.set_fresh_id(fresh_id); + message_.set_existing_block(existing_block); + message_.set_condition_value(condition_value); +} + +bool TransformationAddDeadBlock::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The new block's id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + + // First, we check that a constant with the same value as + // |message_.condition_value| is present. + if (!fuzzerutil::MaybeGetBoolConstantId(context, + message_.condition_value())) { + // The required constant is not present, so the transformation cannot be + // applied. + return false; + } + + // The existing block must indeed exist. + auto existing_block = + fuzzerutil::MaybeFindBlock(context, message_.existing_block()); + if (!existing_block) { + return false; + } + + // It must not head a loop. + if (existing_block->IsLoopHeader()) { + return false; + } + + // It must end with OpBranch. + if (existing_block->terminator()->opcode() != SpvOpBranch) { + return false; + } + + // Its successor must not be a merge block nor continue target. + auto successor_block_id = + existing_block->terminator()->GetSingleWordInOperand(0); + if (fuzzerutil::IsMergeOrContinue(context, successor_block_id)) { + return false; + } + + // The successor must not be a loop header (i.e., |message_.existing_block| + // must not be a back-edge block. + if (context->cfg()->block(successor_block_id)->IsLoopHeader()) { + return false; + } + + return true; +} + +void TransformationAddDeadBlock::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + // Update the module id bound so that it is at least the id of the new block. + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + + // Get the existing block and its successor. + auto existing_block = context->cfg()->block(message_.existing_block()); + auto successor_block_id = + existing_block->terminator()->GetSingleWordInOperand(0); + + // Get the id of the boolean value that will be used as the branch condition. + auto bool_id = + fuzzerutil::MaybeGetBoolConstantId(context, message_.condition_value()); + + // Make a new block that unconditionally branches to the original successor + // block. + auto enclosing_function = existing_block->GetParent(); + std::unique_ptr new_block = MakeUnique( + MakeUnique(context, SpvOpLabel, 0, message_.fresh_id(), + opt::Instruction::OperandList())); + new_block->AddInstruction(MakeUnique( + context, SpvOpBranch, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {successor_block_id}}}))); + + // Turn the original block into a selection merge, with its original successor + // as the merge block. + existing_block->terminator()->InsertBefore(MakeUnique( + context, SpvOpSelectionMerge, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {successor_block_id}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}}))); + + // Change the original block's terminator to be a conditional branch on the + // given boolean, with the original successor and the new successor as branch + // targets, and such that at runtime control will always transfer to the + // original successor. + existing_block->terminator()->SetOpcode(SpvOpBranchConditional); + existing_block->terminator()->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {bool_id}}, + {SPV_OPERAND_TYPE_ID, + {message_.condition_value() ? successor_block_id + : message_.fresh_id()}}, + {SPV_OPERAND_TYPE_ID, + {message_.condition_value() ? message_.fresh_id() + : successor_block_id}}}); + + // Add the new block to the enclosing function. + new_block->SetParent(enclosing_function); + enclosing_function->InsertBasicBlockAfter(std::move(new_block), + existing_block); + + // Record the fact that the new block is dead. + fact_manager->AddFactBlockIsDead(message_.fresh_id()); + + // Fix up OpPhi instructions in the successor block, so that the values they + // yield when control has transferred from the new block are the same as if + // control had transferred from |message_.existing_block|. This is guaranteed + // to be valid since |message_.existing_block| dominates the new block by + // construction. Other transformations can change these phi operands to more + // interesting values. + context->cfg() + ->block(successor_block_id) + ->ForEachPhiInst([this](opt::Instruction* phi_inst) { + // Copy the operand that provides the phi value for the first of any + // existing predecessors. + opt::Operand copy_of_existing_operand = phi_inst->GetInOperand(0); + // Use this as the value associated with the new predecessor. + phi_inst->AddOperand(std::move(copy_of_existing_operand)); + phi_inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}); + }); + + // Do not rely on any existing analysis results since the control flow graph + // of the module has changed. + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationAddDeadBlock::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_dead_block() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.h new file mode 100644 index 000000000..059daca9b --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_block.h @@ -0,0 +1,63 @@ +// 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_TRANSFORMATION_ADD_DEAD_BLOCK_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BLOCK_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddDeadBlock : public Transformation { + public: + explicit TransformationAddDeadBlock( + const protobufs::TransformationAddDeadBlock& message); + + TransformationAddDeadBlock(uint32_t fresh_id, uint32_t existing_block, + bool condition_value); + + // - |message_.fresh_id| must be a fresh id + // - A constant with the same value as |message_.condition_value| must be + // available + // - |message_.existing_block| must be a block that is not a loop header, + // and that ends with OpBranch to a block that is not a merge block nor + // continue target - this is because the successor will become the merge + // block of a selection construct headed at |message_.existing_block| + // - |message_.existing_block| must not be a back-edge block, since in this + // case the newly-added block would lead to another back-edge to the + // associated loop header + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Changes the OpBranch from |message_.existing_block| to its successor 's' + // to an OpBranchConditional to either 's' or a new block, + // |message_.fresh_id|, which itself unconditionally branches to 's'. The + // conditional branch uses |message.condition_value| as its condition, and is + // arranged so that control will pass to 's' at runtime. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddDeadBlock message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_BLOCK_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp index a37100b04..43847fada 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp @@ -111,15 +111,8 @@ bool TransformationAddDeadBreak::IsApplicable( opt::IRContext* context, const FactManager& /*unused*/) const { // First, we check that a constant with the same value as // |message_.break_condition_value| is present. - opt::analysis::Bool bool_type; - auto registered_bool_type = - context->get_type_mgr()->GetRegisteredType(&bool_type); - if (!registered_bool_type) { - return false; - } - opt::analysis::BoolConstant bool_constant(registered_bool_type->AsBool(), - message_.break_condition_value()); - if (!context->get_constant_mgr()->FindConstant(&bool_constant)) { + if (!fuzzerutil::MaybeGetBoolConstantId(context, + message_.break_condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp index 0aacc5bf3..ffa182e93 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp @@ -37,15 +37,8 @@ bool TransformationAddDeadContinue::IsApplicable( opt::IRContext* context, const FactManager& /*unused*/) const { // First, we check that a constant with the same value as // |message_.continue_condition_value| is present. - opt::analysis::Bool bool_type; - auto registered_bool_type = - context->get_type_mgr()->GetRegisteredType(&bool_type); - if (!registered_bool_type) { - return false; - } - opt::analysis::BoolConstant bool_constant( - registered_bool_type->AsBool(), message_.continue_condition_value()); - if (!context->get_constant_mgr()->FindConstant(&bool_constant)) { + if (!fuzzerutil::MaybeGetBoolConstantId( + context, message_.continue_condition_value())) { // The required constant is not present, so the transformation cannot be // applied. return false; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp new file mode 100644 index 000000000..b013d9441 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.cpp @@ -0,0 +1,921 @@ +// 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/transformation_add_function.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/fuzz/instruction_message.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddFunction::TransformationAddFunction( + const spvtools::fuzz::protobufs::TransformationAddFunction& message) + : message_(message) {} + +TransformationAddFunction::TransformationAddFunction( + const std::vector& instructions) { + for (auto& instruction : instructions) { + *message_.add_instruction() = instruction; + } + message_.set_is_livesafe(false); +} + +TransformationAddFunction::TransformationAddFunction( + const std::vector& instructions, + uint32_t loop_limiter_variable_id, uint32_t loop_limit_constant_id, + const std::vector& loop_limiters, + uint32_t kill_unreachable_return_value_id, + const std::vector& + access_chain_clampers) { + for (auto& instruction : instructions) { + *message_.add_instruction() = instruction; + } + message_.set_is_livesafe(true); + message_.set_loop_limiter_variable_id(loop_limiter_variable_id); + message_.set_loop_limit_constant_id(loop_limit_constant_id); + for (auto& loop_limiter : loop_limiters) { + *message_.add_loop_limiter_info() = loop_limiter; + } + message_.set_kill_unreachable_return_value_id( + kill_unreachable_return_value_id); + for (auto& access_clamper : access_chain_clampers) { + *message_.add_access_chain_clamping_info() = access_clamper; + } +} + +bool TransformationAddFunction::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& fact_manager) const { + // This transformation may use a lot of ids, all of which need to be fresh + // and distinct. This set tracks them. + std::set ids_used_by_this_transformation; + + // Ensure that all result ids in the new function are fresh and distinct. + for (auto& instruction : message_.instruction()) { + if (instruction.result_id()) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + instruction.result_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + } + } + + if (message_.is_livesafe()) { + // Ensure that all ids provided for making the function livesafe are fresh + // and distinct. + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.loop_limiter_variable_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + for (auto& loop_limiter_info : message_.loop_limiter_info()) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + loop_limiter_info.load_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + loop_limiter_info.increment_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + loop_limiter_info.compare_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + loop_limiter_info.logical_op_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + } + for (auto& access_chain_clamping_info : + message_.access_chain_clamping_info()) { + for (auto& pair : access_chain_clamping_info.compare_and_select_ids()) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.first(), context, &ids_used_by_this_transformation)) { + return false; + } + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second(), context, &ids_used_by_this_transformation)) { + return false; + } + } + } + } + + // Because checking all the conditions for a function to be valid is a big + // job that the SPIR-V validator can already do, a "try it and see" approach + // is taken here. + + // We first clone the current module, so that we can try adding the new + // function without risking wrecking |context|. + auto cloned_module = fuzzerutil::CloneIRContext(context); + + // We try to add a function to the cloned module, which may fail if + // |message_.instruction| is not sufficiently well-formed. + if (!TryToAddFunction(cloned_module.get())) { + return false; + } + + if (message_.is_livesafe()) { + // We make the cloned module livesafe. + if (!TryToMakeFunctionLivesafe(cloned_module.get(), fact_manager)) { + return false; + } + } + + // Having managed to add the new function to the cloned module, and + // potentially also made it livesafe, we ascertain whether the cloned module + // is still valid. If it is, the transformation is applicable. + return fuzzerutil::IsValid(cloned_module.get()); +} + +void TransformationAddFunction::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + // Add the function to the module. As the transformation is applicable, this + // should succeed. + bool success = TryToAddFunction(context); + assert(success && "The function should be successfully added."); + (void)(success); // Keep release builds happy (otherwise they may complain + // that |success| is not used). + + // Record the fact that all pointer parameters and variables declared in the + // function should be regarded as having arbitrary values. This allows other + // passes to store arbitrarily to such variables, and to pass them freely as + // parameters to other functions knowing that it is OK if they get + // over-written. + for (auto& instruction : message_.instruction()) { + switch (instruction.opcode()) { + case SpvOpFunctionParameter: + if (context->get_def_use_mgr() + ->GetDef(instruction.result_type_id()) + ->opcode() == SpvOpTypePointer) { + fact_manager->AddFactValueOfVariableIsArbitrary( + instruction.result_id()); + } + break; + case SpvOpVariable: + fact_manager->AddFactValueOfVariableIsArbitrary( + instruction.result_id()); + break; + default: + break; + } + } + + if (message_.is_livesafe()) { + // Make the function livesafe, which also should succeed. + success = TryToMakeFunctionLivesafe(context, *fact_manager); + 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."); + fact_manager->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) { + fact_manager->AddFactBlockIsDead(inst.result_id()); + } + } + } + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); +} + +protobufs::Transformation TransformationAddFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_function() = message_; + return result; +} + +bool TransformationAddFunction::TryToAddFunction( + opt::IRContext* context) const { + // This function returns false if |message_.instruction| was not well-formed + // enough to actually create a function and add it to |context|. + + // A function must have at least some instructions. + if (message_.instruction().empty()) { + return false; + } + + // A function must start with OpFunction. + auto function_begin = message_.instruction(0); + if (function_begin.opcode() != SpvOpFunction) { + return false; + } + + // Make a function, headed by the OpFunction instruction. + std::unique_ptr new_function = MakeUnique( + InstructionFromMessage(context, function_begin)); + + // Keeps track of which instruction protobuf message we are currently + // considering. + uint32_t instruction_index = 1; + const auto num_instructions = + static_cast(message_.instruction().size()); + + // Iterate through all function parameter instructions, adding parameters to + // the new function. + while (instruction_index < num_instructions && + message_.instruction(instruction_index).opcode() == + SpvOpFunctionParameter) { + new_function->AddParameter(InstructionFromMessage( + context, message_.instruction(instruction_index))); + instruction_index++; + } + + // After the parameters, there needs to be a label. + if (instruction_index == num_instructions || + message_.instruction(instruction_index).opcode() != SpvOpLabel) { + return false; + } + + // Iterate through the instructions block by block until the end of the + // function is reached. + while (instruction_index < num_instructions && + message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) { + // Invariant: we should always be at a label instruction at this point. + assert(message_.instruction(instruction_index).opcode() == SpvOpLabel); + + // Make a basic block using the label instruction, with the new function + // as its parent. + std::unique_ptr block = + MakeUnique(InstructionFromMessage( + context, message_.instruction(instruction_index))); + block->SetParent(new_function.get()); + + // Consider successive instructions until we hit another label or the end + // of the function, adding each such instruction to the block. + instruction_index++; + while (instruction_index < num_instructions && + message_.instruction(instruction_index).opcode() != + SpvOpFunctionEnd && + message_.instruction(instruction_index).opcode() != SpvOpLabel) { + block->AddInstruction(InstructionFromMessage( + context, message_.instruction(instruction_index))); + instruction_index++; + } + // Add the block to the new function. + new_function->AddBasicBlock(std::move(block)); + } + // Having considered all the blocks, we should be at the last instruction and + // it needs to be OpFunctionEnd. + if (instruction_index != num_instructions - 1 || + message_.instruction(instruction_index).opcode() != SpvOpFunctionEnd) { + return false; + } + // Set the function's final instruction, add the function to the module and + // report success. + new_function->SetFunctionEnd( + InstructionFromMessage(context, message_.instruction(instruction_index))); + context->AddFunction(std::move(new_function)); + + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + + return true; +} + +bool TransformationAddFunction::TryToMakeFunctionLivesafe( + opt::IRContext* context, const FactManager& fact_manager) const { + assert(message_.is_livesafe() && "Precondition: is_livesafe must hold."); + + // Get a pointer to the added function. + opt::Function* added_function = nullptr; + for (auto& function : *context->module()) { + if (function.result_id() == message_.instruction(0).result_id()) { + added_function = &function; + break; + } + } + assert(added_function && "The added function should have been found."); + + if (!TryToAddLoopLimiters(context, added_function)) { + // Adding loop limiters did not work; bail out. + return false; + } + + // Consider all the instructions in the function, and: + // - attempt to replace OpKill and OpUnreachable with return instructions + // - attempt to clamp access chains to be within bounds + // - check that OpFunctionCall instructions are only to livesafe functions + for (auto& block : *added_function) { + for (auto& inst : block) { + switch (inst.opcode()) { + case SpvOpKill: + case SpvOpUnreachable: + if (!TryToTurnKillOrUnreachableIntoReturn(context, added_function, + &inst)) { + return false; + } + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + if (!TryToClampAccessChainIndices(context, &inst)) { + return false; + } + break; + case SpvOpFunctionCall: + // A livesafe function my only call other livesafe functions. + if (!fact_manager.FunctionIsLivesafe( + inst.GetSingleWordInOperand(0))) { + return false; + } + default: + break; + } + } + } + return true; +} + +bool TransformationAddFunction::TryToAddLoopLimiters( + opt::IRContext* context, opt::Function* added_function) const { + // Collect up all the loop headers so that we can subsequently add loop + // limiting logic. + std::vector loop_headers; + for (auto& block : *added_function) { + if (block.IsLoopHeader()) { + loop_headers.push_back(&block); + } + } + + if (loop_headers.empty()) { + // There are no loops, so no need to add any loop limiters. + return true; + } + + // Check that the module contains appropriate ingredients for declaring and + // manipulating a loop limiter. + + auto loop_limit_constant_id_instr = + context->get_def_use_mgr()->GetDef(message_.loop_limit_constant_id()); + if (!loop_limit_constant_id_instr || + loop_limit_constant_id_instr->opcode() != SpvOpConstant) { + // The loop limit constant id instruction must exist and have an + // appropriate opcode. + return false; + } + + auto loop_limit_type = context->get_def_use_mgr()->GetDef( + loop_limit_constant_id_instr->type_id()); + if (loop_limit_type->opcode() != SpvOpTypeInt || + loop_limit_type->GetSingleWordInOperand(0) != 32) { + // The type of the loop limit constant must be 32-bit integer. It + // doesn't actually matter whether the integer is signed or not. + return false; + } + + // Find the id of the "unsigned int" type. + opt::analysis::Integer unsigned_int_type(32, false); + uint32_t unsigned_int_type_id = + context->get_type_mgr()->GetId(&unsigned_int_type); + if (!unsigned_int_type_id) { + // Unsigned int is not available; we need this type in order to add loop + // limiters. + return false; + } + auto registered_unsigned_int_type = + context->get_type_mgr()->GetRegisteredType(&unsigned_int_type); + + // Look for 0 of type unsigned int. + opt::analysis::IntConstant zero(registered_unsigned_int_type->AsInteger(), + {0}); + auto registered_zero = context->get_constant_mgr()->FindConstant(&zero); + if (!registered_zero) { + // We need 0 in order to be able to initialize loop limiters. + return false; + } + uint32_t zero_id = context->get_constant_mgr() + ->GetDefiningInstruction(registered_zero) + ->result_id(); + + // Look for 1 of type unsigned int. + opt::analysis::IntConstant one(registered_unsigned_int_type->AsInteger(), + {1}); + auto registered_one = context->get_constant_mgr()->FindConstant(&one); + if (!registered_one) { + // We need 1 in order to be able to increment loop limiters. + return false; + } + uint32_t one_id = context->get_constant_mgr() + ->GetDefiningInstruction(registered_one) + ->result_id(); + + // Look for pointer-to-unsigned int type. + opt::analysis::Pointer pointer_to_unsigned_int_type( + registered_unsigned_int_type, SpvStorageClassFunction); + uint32_t pointer_to_unsigned_int_type_id = + context->get_type_mgr()->GetId(&pointer_to_unsigned_int_type); + if (!pointer_to_unsigned_int_type_id) { + // We need pointer-to-unsigned int in order to declare the loop limiter + // variable. + return false; + } + + // Look for bool type. + opt::analysis::Bool bool_type; + uint32_t bool_type_id = context->get_type_mgr()->GetId(&bool_type); + if (!bool_type_id) { + // We need bool in order to compare the loop limiter's value with the loop + // limit constant. + return false; + } + + // Declare the loop limiter variable at the start of the function's entry + // block, via an instruction of the form: + // %loop_limiter_var = SpvOpVariable %ptr_to_uint Function %zero + added_function->begin()->begin()->InsertBefore(MakeUnique( + context, SpvOpVariable, pointer_to_unsigned_int_type_id, + message_.loop_limiter_variable_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}, + {SPV_OPERAND_TYPE_ID, {zero_id}}}))); + // Update the module's id bound since we have added the loop limiter + // variable id. + fuzzerutil::UpdateModuleIdBound(context, message_.loop_limiter_variable_id()); + + // Consider each loop in turn. + 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 : context->cfg()->preds(loop_header->id())) { + if (context->GetDominatorAnalysis(added_function) + ->Dominates(loop_header->id(), pred)) { + back_edge_block_id = pred; + break; + } + } + 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 = context->cfg()->block(back_edge_block_id); + + // Go through the sequence of loop limiter infos and find the one + // corresponding to this loop. + bool found = false; + protobufs::LoopLimiterInfo loop_limiter_info; + for (auto& info : message_.loop_limiter_info()) { + if (info.loop_header_id() == loop_header->id()) { + loop_limiter_info = info; + found = true; + break; + } + } + if (!found) { + // We don't have loop limiter info for this loop header. + return false; + } + + // The back-edge block either has the form: + // + // (1) + // + // %l = OpLabel + // ... instructions ... + // OpBranch %loop_header + // + // (2) + // + // %l = OpLabel + // ... instructions ... + // OpBranchConditional %c %loop_header %loop_merge + // + // (3) + // + // %l = OpLabel + // ... instructions ... + // OpBranchConditional %c %loop_merge %loop_header + // + // We turn these into the following: + // + // (1) + // + // %l = OpLabel + // ... instructions ... + // %t1 = OpLoad %uint32 %loop_limiter + // %t2 = OpIAdd %uint32 %t1 %one + // OpStore %loop_limiter %t2 + // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit + // OpBranchConditional %t3 %loop_merge %loop_header + // + // (2) + // + // %l = OpLabel + // ... instructions ... + // %t1 = OpLoad %uint32 %loop_limiter + // %t2 = OpIAdd %uint32 %t1 %one + // OpStore %loop_limiter %t2 + // %t3 = OpULessThan %bool %t1 %loop_limit + // %t4 = OpLogicalAnd %bool %c %t3 + // OpBranchConditional %t4 %loop_header %loop_merge + // + // (3) + // + // %l = OpLabel + // ... instructions ... + // %t1 = OpLoad %uint32 %loop_limiter + // %t2 = OpIAdd %uint32 %t1 %one + // OpStore %loop_limiter %t2 + // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit + // %t4 = OpLogicalOr %bool %c %t3 + // OpBranchConditional %t4 %loop_merge %loop_header + + auto back_edge_block_terminator = back_edge_block->terminator(); + bool compare_using_greater_than_equal; + if (back_edge_block_terminator->opcode() == SpvOpBranch) { + compare_using_greater_than_equal = true; + } else { + assert(back_edge_block_terminator->opcode() == SpvOpBranchConditional); + assert(((back_edge_block_terminator->GetSingleWordInOperand(1) == + loop_header->id() && + back_edge_block_terminator->GetSingleWordInOperand(2) == + loop_header->MergeBlockId()) || + (back_edge_block_terminator->GetSingleWordInOperand(2) == + loop_header->id() && + back_edge_block_terminator->GetSingleWordInOperand(1) == + loop_header->MergeBlockId())) && + "A back edge edge block must branch to" + " either the loop header or merge"); + compare_using_greater_than_equal = + back_edge_block_terminator->GetSingleWordInOperand(1) == + loop_header->MergeBlockId(); + } + + std::vector> new_instructions; + + // Add a load from the loop limiter variable, of the form: + // %t1 = OpLoad %uint32 %loop_limiter + new_instructions.push_back(MakeUnique( + context, SpvOpLoad, unsigned_int_type_id, loop_limiter_info.load_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}}}))); + + // Increment the loaded value: + // %t2 = OpIAdd %uint32 %t1 %one + new_instructions.push_back(MakeUnique( + context, SpvOpIAdd, unsigned_int_type_id, + loop_limiter_info.increment_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}}, + {SPV_OPERAND_TYPE_ID, {one_id}}}))); + + // Store the incremented value back to the loop limiter variable: + // OpStore %loop_limiter %t2 + new_instructions.push_back(MakeUnique( + context, SpvOpStore, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.loop_limiter_variable_id()}}, + {SPV_OPERAND_TYPE_ID, {loop_limiter_info.increment_id()}}}))); + + // Compare the loaded value with the loop limit; either: + // %t3 = OpUGreaterThanEqual %bool %t1 %loop_limit + // or + // %t3 = OpULessThan %bool %t1 %loop_limit + new_instructions.push_back(MakeUnique( + context, + compare_using_greater_than_equal ? SpvOpUGreaterThanEqual + : SpvOpULessThan, + bool_type_id, loop_limiter_info.compare_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.load_id()}}, + {SPV_OPERAND_TYPE_ID, {message_.loop_limit_constant_id()}}}))); + + if (back_edge_block_terminator->opcode() == SpvOpBranchConditional) { + new_instructions.push_back(MakeUnique( + context, + compare_using_greater_than_equal ? SpvOpLogicalOr : SpvOpLogicalAnd, + bool_type_id, loop_limiter_info.logical_op_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, + {back_edge_block_terminator->GetSingleWordInOperand(0)}}, + {SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}}}))); + } + + // Add the new instructions at the end of the back edge block, before the + // terminator and any loop merge instruction (as the back edge block can + // be the loop header). + if (back_edge_block->GetLoopMergeInst()) { + back_edge_block->GetLoopMergeInst()->InsertBefore( + std::move(new_instructions)); + } else { + back_edge_block_terminator->InsertBefore(std::move(new_instructions)); + } + + if (back_edge_block_terminator->opcode() == SpvOpBranchConditional) { + back_edge_block_terminator->SetInOperand( + 0, {loop_limiter_info.logical_op_id()}); + } else { + assert(back_edge_block_terminator->opcode() == SpvOpBranch && + "Back-edge terminator must be OpBranch or OpBranchConditional"); + + // Check that, if the merge block starts with OpPhi instructions, suitable + // ids have been provided to give these instructions a value corresponding + // to the new incoming edge from the back edge block. + auto merge_block = context->cfg()->block(loop_header->MergeBlockId()); + if (!fuzzerutil::PhiIdsOkForNewEdge(context, back_edge_block, merge_block, + loop_limiter_info.phi_id())) { + return false; + } + + // Augment OpPhi instructions at the loop merge with the given ids. + uint32_t phi_index = 0; + for (auto& inst : *merge_block) { + if (inst.opcode() != SpvOpPhi) { + break; + } + assert(phi_index < + static_cast(loop_limiter_info.phi_id().size()) && + "There should be at least one phi id per OpPhi instruction."); + inst.AddOperand( + {SPV_OPERAND_TYPE_ID, {loop_limiter_info.phi_id(phi_index)}}); + inst.AddOperand({SPV_OPERAND_TYPE_ID, {back_edge_block_id}}); + phi_index++; + } + + // 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->id()}}})); + } + + // Update the module's id bound with respect to the various ids that + // have been used for loop limiter manipulation. + fuzzerutil::UpdateModuleIdBound(context, loop_limiter_info.load_id()); + fuzzerutil::UpdateModuleIdBound(context, loop_limiter_info.increment_id()); + fuzzerutil::UpdateModuleIdBound(context, loop_limiter_info.compare_id()); + fuzzerutil::UpdateModuleIdBound(context, loop_limiter_info.logical_op_id()); + } + return true; +} + +bool TransformationAddFunction::TryToTurnKillOrUnreachableIntoReturn( + opt::IRContext* context, opt::Function* added_function, + opt::Instruction* kill_or_unreachable_inst) const { + assert((kill_or_unreachable_inst->opcode() == SpvOpKill || + kill_or_unreachable_inst->opcode() == SpvOpUnreachable) && + "Precondition: instruction must be OpKill or OpUnreachable."); + + // Get the function's return type. + auto function_return_type_inst = + context->get_def_use_mgr()->GetDef(added_function->type_id()); + + if (function_return_type_inst->opcode() == SpvOpTypeVoid) { + // The function has void return type, so change this instruction to + // OpReturn. + kill_or_unreachable_inst->SetOpcode(SpvOpReturn); + } else { + // The function has non-void return type, so change this instruction + // to OpReturnValue, using the value id provided with the + // transformation. + + // We first check that the id, %id, provided with the transformation + // specifically to turn OpKill and OpUnreachable instructions into + // OpReturnValue %id has the same type as the function's return type. + if (context->get_def_use_mgr() + ->GetDef(message_.kill_unreachable_return_value_id()) + ->type_id() != function_return_type_inst->result_id()) { + return false; + } + kill_or_unreachable_inst->SetOpcode(SpvOpReturnValue); + kill_or_unreachable_inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {message_.kill_unreachable_return_value_id()}}}); + } + return true; +} + +bool TransformationAddFunction::TryToClampAccessChainIndices( + opt::IRContext* context, opt::Instruction* access_chain_inst) const { + assert((access_chain_inst->opcode() == SpvOpAccessChain || + access_chain_inst->opcode() == SpvOpInBoundsAccessChain) && + "Precondition: instruction must be OpAccessChain or " + "OpInBoundsAccessChain."); + + // Find the AccessChainClampingInfo associated with this access chain. + const protobufs::AccessChainClampingInfo* access_chain_clamping_info = + nullptr; + for (auto& clamping_info : message_.access_chain_clamping_info()) { + if (clamping_info.access_chain_id() == access_chain_inst->result_id()) { + access_chain_clamping_info = &clamping_info; + break; + } + } + if (!access_chain_clamping_info) { + // No access chain clamping information was found; the function cannot be + // made livesafe. + return false; + } + + // Check that there is a (compare_id, select_id) pair for every + // index associated with the instruction. + if (static_cast( + access_chain_clamping_info->compare_and_select_ids().size()) != + access_chain_inst->NumInOperands() - 1) { + return false; + } + + // Walk the access chain, clamping each index to be within bounds if it is + // not a constant. + auto base_object = context->get_def_use_mgr()->GetDef( + access_chain_inst->GetSingleWordInOperand(0)); + assert(base_object && "The base object must exist."); + auto pointer_type = + context->get_def_use_mgr()->GetDef(base_object->type_id()); + assert(pointer_type && pointer_type->opcode() == SpvOpTypePointer && + "The base object must have pointer type."); + auto should_be_composite_type = context->get_def_use_mgr()->GetDef( + pointer_type->GetSingleWordInOperand(1)); + + // Consider each index input operand in turn (operand 0 is the base object). + for (uint32_t index = 1; index < access_chain_inst->NumInOperands(); + index++) { + // We are going to turn: + // + // %result = OpAccessChain %type %object ... %index ... + // + // into: + // + // %t1 = OpULessThanEqual %bool %index %bound_minus_one + // %t2 = OpSelect %int_type %t1 %index %bound_minus_one + // %result = OpAccessChain %type %object ... %t2 ... + // + // ... unless %index is already a constant. + + // 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(context, *should_be_composite_type); + + // Get the instruction associated with the index and figure out its integer + // type. + const uint32_t index_id = access_chain_inst->GetSingleWordInOperand(index); + auto index_inst = context->get_def_use_mgr()->GetDef(index_id); + auto index_type_inst = + context->get_def_use_mgr()->GetDef(index_inst->type_id()); + assert(index_type_inst->opcode() == SpvOpTypeInt); + assert(index_type_inst->GetSingleWordInOperand(0) == 32); + opt::analysis::Integer* index_int_type = + context->get_type_mgr() + ->GetType(index_type_inst->result_id()) + ->AsInteger(); + + if (index_inst->opcode() != SpvOpConstant) { + // The index is non-constant so we need to clamp it. + assert(should_be_composite_type->opcode() != SpvOpTypeStruct && + "Access chain indices into structures are required to be " + "constants."); + opt::analysis::IntConstant bound_minus_one(index_int_type, {bound - 1}); + if (!context->get_constant_mgr()->FindConstant(&bound_minus_one)) { + // We do not have an integer constant whose value is |bound| -1. + return false; + } + + opt::analysis::Bool bool_type; + uint32_t bool_type_id = context->get_type_mgr()->GetId(&bool_type); + if (!bool_type_id) { + // Bool type is not declared; we cannot do a comparison. + return false; + } + + uint32_t bound_minus_one_id = + context->get_constant_mgr() + ->GetDefiningInstruction(&bound_minus_one) + ->result_id(); + + uint32_t compare_id = + access_chain_clamping_info->compare_and_select_ids(index - 1).first(); + uint32_t select_id = + access_chain_clamping_info->compare_and_select_ids(index - 1) + .second(); + std::vector> new_instructions; + + // Compare the index with the bound via an instruction of the form: + // %t1 = OpULessThanEqual %bool %index %bound_minus_one + new_instructions.push_back(MakeUnique( + context, SpvOpULessThanEqual, bool_type_id, compare_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {index_inst->result_id()}}, + {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}))); + + // Select the index if in-bounds, otherwise one less than the bound: + // %t2 = OpSelect %int_type %t1 %index %bound_minus_one + new_instructions.push_back(MakeUnique( + context, SpvOpSelect, index_type_inst->result_id(), select_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {compare_id}}, + {SPV_OPERAND_TYPE_ID, {index_inst->result_id()}}, + {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}))); + + // Add the new instructions before the access chain + access_chain_inst->InsertBefore(std::move(new_instructions)); + + // Replace %index with %t2. + access_chain_inst->SetInOperand(index, {select_id}); + fuzzerutil::UpdateModuleIdBound(context, compare_id); + fuzzerutil::UpdateModuleIdBound(context, select_id); + } else { + // TODO(afd): At present the SPIR-V spec is not clear on whether + // statically out-of-bounds indices mean that a module is invalid (so + // that it should be rejected by the validator), or that such accesses + // yield undefined results. Via the following assertion, we assume that + // functions added to the module do not feature statically out-of-bounds + // accesses. + // Assert that the index is smaller (unsigned) than this value. + // Return false if it is not (to keep compilers happy). + if (index_inst->GetSingleWordInOperand(0) >= bound) { + assert(false && + "The function has a statically out-of-bounds access; " + "this should not occur."); + return false; + } + } + should_be_composite_type = + FollowCompositeIndex(context, *should_be_composite_type, index_id); + } + return true; +} + +uint32_t TransformationAddFunction::GetBoundForCompositeIndex( + opt::IRContext* context, const opt::Instruction& composite_type_inst) { + switch (composite_type_inst.opcode()) { + case SpvOpTypeArray: + return fuzzerutil::GetArraySize(composite_type_inst, context); + case SpvOpTypeMatrix: + case SpvOpTypeVector: + return composite_type_inst.GetSingleWordInOperand(1); + case SpvOpTypeStruct: { + return fuzzerutil::GetNumberOfStructMembers(composite_type_inst); + } + default: + assert(false && "Unknown composite type."); + return 0; + } +} + +opt::Instruction* TransformationAddFunction::FollowCompositeIndex( + opt::IRContext* context, const opt::Instruction& composite_type_inst, + uint32_t index_id) { + uint32_t sub_object_type_id; + switch (composite_type_inst.opcode()) { + case SpvOpTypeArray: + sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0); + break; + case SpvOpTypeMatrix: + case SpvOpTypeVector: + sub_object_type_id = composite_type_inst.GetSingleWordInOperand(0); + break; + case SpvOpTypeStruct: { + auto index_inst = context->get_def_use_mgr()->GetDef(index_id); + assert(index_inst->opcode() == SpvOpConstant); + assert( + context->get_def_use_mgr()->GetDef(index_inst->type_id())->opcode() == + SpvOpTypeInt); + assert(context->get_def_use_mgr() + ->GetDef(index_inst->type_id()) + ->GetSingleWordInOperand(0) == 32); + uint32_t index_value = index_inst->GetSingleWordInOperand(0); + sub_object_type_id = + composite_type_inst.GetSingleWordInOperand(index_value); + break; + } + default: + assert(false && "Unknown composite type."); + sub_object_type_id = 0; + break; + } + assert(sub_object_type_id && "No sub-object found."); + return context->get_def_use_mgr()->GetDef(sub_object_type_id); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h new file mode 100644 index 000000000..848b799fc --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_function.h @@ -0,0 +1,124 @@ +// 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_TRANSFORMATION_ADD_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddFunction : public Transformation { + public: + explicit TransformationAddFunction( + const protobufs::TransformationAddFunction& message); + + // Creates a transformation to add a non live-safe function. + explicit TransformationAddFunction( + const std::vector& instructions); + + // Creates a transformation to add a live-safe function. + TransformationAddFunction( + const std::vector& instructions, + uint32_t loop_limiter_variable_id, uint32_t loop_limit_constant_id, + const std::vector& loop_limiters, + uint32_t kill_unreachable_return_value_id, + const std::vector& + access_chain_clampers); + + // - |message_.instruction| must correspond to a sufficiently well-formed + // sequence of instructions that a function can be created from them + // - If |message_.is_livesafe| holds then |message_| must contain suitable + // ingredients to make the function livesafe, and the function must only + // invoke other livesafe functions + // - Adding the created function to the module must lead to a valid module. + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds the function defined by |message_.instruction| to the module, making + // it livesafe if |message_.is_livesafe| holds. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + 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* 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. + static opt::Instruction* FollowCompositeIndex( + opt::IRContext* context, const opt::Instruction& composite_type_inst, + uint32_t index_id); + + private: + // Attempts to create a function from the series of instructions in + // |message_.instruction| and add it to |context|. + // + // Returns false if adding the function is not possible due to the messages + // not respecting the basic structure of a function, e.g. if there is no + // OpFunction instruction or no blocks; in this case |context| is left in an + // indeterminate state. + // + // Otherwise returns true. Whether |context| is valid after addition of the + // function depends on the contents of |message_.instruction|. + // + // Intended usage: + // - Perform a dry run of this method on a clone of a module, and use + // the validator to check whether the resulting module is valid. Working + // on a clone means it does not matter if the function fails to be cleanly + // added, or leads to an invalid module. + // - If the dry run succeeds, run the method on the real module of interest, + // to add the function. + bool TryToAddFunction(opt::IRContext* context) const; + + // Should only be called if |message_.is_livesafe| holds. Attempts to make + // the function livesafe (see FactFunctionIsLivesafe for a definition). + // Returns false if this is not possible, due to |message_| or |context| not + // containing sufficient ingredients (such as types and fresh ids) to add + // the instrumentation necessary to make the function livesafe. + bool TryToMakeFunctionLivesafe(opt::IRContext* context, + const FactManager& fact_manager) const; + + // A helper for TryToMakeFunctionLivesafe that tries to add loop-limiting + // logic. + bool TryToAddLoopLimiters(opt::IRContext* context, + opt::Function* added_function) const; + + // A helper for TryToMakeFunctionLivesafe that tries to replace OpKill and + // OpUnreachable instructions into return instructions. + bool TryToTurnKillOrUnreachableIntoReturn( + opt::IRContext* context, opt::Function* added_function, + opt::Instruction* kill_or_unreachable_inst) const; + + // A helper for TryToMakeFunctionLivesafe that tries to clamp access chain + // indices so that they are guaranteed to be in-bounds. + bool TryToClampAccessChainIndices(opt::IRContext* context, + opt::Instruction* access_chain_inst) const; + + protobufs::TransformationAddFunction message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_FUNCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.cpp new file mode 100644 index 000000000..f9585b3bc --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/fuzz/transformation_add_global_undef.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddGlobalUndef::TransformationAddGlobalUndef( + const spvtools::fuzz::protobufs::TransformationAddGlobalUndef& message) + : message_(message) {} + +TransformationAddGlobalUndef::TransformationAddGlobalUndef(uint32_t fresh_id, + uint32_t type_id) { + message_.set_fresh_id(fresh_id); + message_.set_type_id(type_id); +} + +bool TransformationAddGlobalUndef::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // A fresh id is required. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + auto type = context->get_type_mgr()->GetType(message_.type_id()); + // The type must exist, and must not be a function type. + return type && !type->AsFunction(); +} + +void TransformationAddGlobalUndef::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + context->module()->AddGlobalValue(MakeUnique( + context, SpvOpUndef, message_.type_id(), message_.fresh_id(), + opt::Instruction::OperandList())); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddGlobalUndef::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_global_undef() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.h new file mode 100644 index 000000000..550d9f68f --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_undef.h @@ -0,0 +1,51 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_UNDEF_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_UNDEF_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddGlobalUndef : public Transformation { + public: + explicit TransformationAddGlobalUndef( + const protobufs::TransformationAddGlobalUndef& message); + + TransformationAddGlobalUndef(uint32_t fresh_id, uint32_t type_id); + + // - |message_.fresh_id| must be fresh + // - |message_.type_id| must be the id of a non-function type + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpUndef instruction to the module, with |message_.type_id| as its + // type. The instruction has result id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddGlobalUndef message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_UNDEF_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp new file mode 100644 index 000000000..c08517f9a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.cpp @@ -0,0 +1,137 @@ +// 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/transformation_add_global_variable.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddGlobalVariable::TransformationAddGlobalVariable( + const spvtools::fuzz::protobufs::TransformationAddGlobalVariable& message) + : message_(message) {} + +TransformationAddGlobalVariable::TransformationAddGlobalVariable( + uint32_t fresh_id, uint32_t type_id, uint32_t initializer_id, + bool value_is_arbitrary) { + message_.set_fresh_id(fresh_id); + message_.set_type_id(type_id); + message_.set_initializer_id(initializer_id); + message_.set_value_is_arbitrary(value_is_arbitrary); +} + +bool TransformationAddGlobalVariable::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The type id must correspond to a type. + auto type = context->get_type_mgr()->GetType(message_.type_id()); + if (!type) { + return false; + } + // That type must be a pointer type ... + auto pointer_type = type->AsPointer(); + if (!pointer_type) { + return false; + } + // ... with Private storage class. + if (pointer_type->storage_class() != SpvStorageClassPrivate) { + return false; + } + if (message_.initializer_id()) { + // The initializer id must be the id of a constant. Check this with the + // constant manager. + auto constant_id = context->get_constant_mgr()->GetConstantsFromIds( + {message_.initializer_id()}); + if (constant_id.empty()) { + return false; + } + assert(constant_id.size() == 1 && + "We asked for the constant associated with a single id; we should " + "get a single constant."); + // The type of the constant must match the pointee type of the pointer. + if (pointer_type->pointee_type() != constant_id[0]->type()) { + return false; + } + } + return true; +} + +void TransformationAddGlobalVariable::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + opt::Instruction::OperandList input_operands; + input_operands.push_back( + {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassPrivate}}); + if (message_.initializer_id()) { + input_operands.push_back( + {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}); + } + context->module()->AddGlobalValue( + MakeUnique(context, SpvOpVariable, message_.type_id(), + message_.fresh_id(), input_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + + if (PrivateGlobalsMustBeDeclaredInEntryPointInterfaces(context)) { + // Conservatively add this global to the interface of every entry point in + // the module. This means that the global is available for other + // transformations to use. + // + // A downside of this is that the global will be in the interface even if it + // ends up never being used. + // + // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3111) revisit + // this if a more thorough approach to entry point interfaces is taken. + for (auto& entry_point : context->module()->entry_points()) { + entry_point.AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}); + } + } + + if (message_.value_is_arbitrary()) { + fact_manager->AddFactValueOfVariableIsArbitrary(message_.fresh_id()); + } + + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddGlobalVariable::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_global_variable() = message_; + return result; +} + +bool TransformationAddGlobalVariable:: + PrivateGlobalsMustBeDeclaredInEntryPointInterfaces( + opt::IRContext* context) { + // TODO(afd): We capture the universal environments for which this requirement + // holds. The check should be refined on demand for other target + // environments. + switch (context->grammar().target_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: + return false; + default: + return true; + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h new file mode 100644 index 000000000..406c91571 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_global_variable.h @@ -0,0 +1,61 @@ +// 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_TRANSFORMATION_ADD_GLOBAL_VARIABLE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_VARIABLE_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddGlobalVariable : public Transformation { + public: + explicit TransformationAddGlobalVariable( + const protobufs::TransformationAddGlobalVariable& message); + + TransformationAddGlobalVariable(uint32_t fresh_id, uint32_t type_id, + uint32_t initializer_id, + bool value_is_arbitrary); + + // - |message_.fresh_id| must be fresh + // - |message_.type_id| must be the id of a pointer type with Private storage + // class + // - |message_.initializer_id| must either be 0 or the id of a constant whose + // type is the pointee type of |message_.type_id| + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds a global variable with Private storage class to the module, with type + // |message_.type_id| and either no initializer or |message_.initializer_id| + // as an initializer, depending on whether |message_.initializer_id| is 0. + // The global variable has result id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + static bool PrivateGlobalsMustBeDeclaredInEntryPointInterfaces( + opt::IRContext* context); + + protobufs::TransformationAddGlobalVariable message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_GLOBAL_VARIABLE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.cpp new file mode 100644 index 000000000..2074e98a1 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.cpp @@ -0,0 +1,88 @@ +// 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/transformation_add_type_array.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddTypeArray::TransformationAddTypeArray( + const spvtools::fuzz::protobufs::TransformationAddTypeArray& message) + : message_(message) {} + +TransformationAddTypeArray::TransformationAddTypeArray(uint32_t fresh_id, + uint32_t element_type_id, + uint32_t size_id) { + message_.set_fresh_id(fresh_id); + message_.set_element_type_id(element_type_id); + message_.set_size_id(size_id); +} + +bool TransformationAddTypeArray::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // A fresh id is required. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + auto element_type = + context->get_type_mgr()->GetType(message_.element_type_id()); + if (!element_type || element_type->AsFunction()) { + // The element type id either does not refer to a type, or refers to a + // function type; both are illegal. + return false; + } + auto constant = + context->get_constant_mgr()->GetConstantsFromIds({message_.size_id()}); + if (constant.empty()) { + // The size id does not refer to a constant. + return false; + } + assert(constant.size() == 1 && + "Only one constant id was provided, so only one constant should have " + "been returned"); + + auto int_constant = constant[0]->AsIntConstant(); + if (!int_constant) { + // The size constant is not an integer. + return false; + } + // We require that the size constant be a 32-bit value that is positive when + // interpreted as being signed. + return int_constant->words().size() == 1 && int_constant->GetS32() >= 1; +} + +void TransformationAddTypeArray::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.element_type_id()}}); + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.size_id()}}); + context->module()->AddType(MakeUnique( + context, SpvOpTypeArray, 0, message_.fresh_id(), in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddTypeArray::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_type_array() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.h new file mode 100644 index 000000000..b6e071827 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_array.h @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_ARRAY_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_ARRAY_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddTypeArray : public Transformation { + public: + explicit TransformationAddTypeArray( + const protobufs::TransformationAddTypeArray& message); + + TransformationAddTypeArray(uint32_t fresh_id, uint32_t element_type_id, + uint32_t size_id); + + // - |message_.fresh_id| must be fresh + // - |message_.element_type_id| must be the id of a non-function type + // - |message_.size_id| must be the id of a 32-bit integer constant that is + // positive when interpreted as signed. + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpTypeArray instruction to the module, with element type given by + // |message_.element_type_id| and size given by |message_.size_id|. The + // result id of the instruction is |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddTypeArray message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_ARRAY_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.cpp new file mode 100644 index 000000000..4b6717b86 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.cpp @@ -0,0 +1,113 @@ +// 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/transformation_add_type_function.h" + +#include + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddTypeFunction::TransformationAddTypeFunction( + const spvtools::fuzz::protobufs::TransformationAddTypeFunction& message) + : message_(message) {} + +TransformationAddTypeFunction::TransformationAddTypeFunction( + uint32_t fresh_id, uint32_t return_type_id, + const std::vector& argument_type_ids) { + message_.set_fresh_id(fresh_id); + message_.set_return_type_id(return_type_id); + for (auto id : argument_type_ids) { + message_.add_argument_type_id(id); + } +} + +bool TransformationAddTypeFunction::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The return and argument types must be type ids but not not be function + // type ids. + if (!fuzzerutil::IsNonFunctionTypeId(context, message_.return_type_id())) { + return false; + } + for (auto argument_type_id : message_.argument_type_id()) { + if (!fuzzerutil::IsNonFunctionTypeId(context, argument_type_id)) { + return false; + } + } + // Check whether there is already an OpTypeFunction definition that uses + // exactly the same return and argument type ids. (Note that the type manager + // does not allow us to check this, as it does not distinguish between + // function types with different but isomorphic pointer argument types.) + for (auto& inst : context->module()->types_values()) { + if (inst.opcode() != SpvOpTypeFunction) { + // Consider only OpTypeFunction instructions. + continue; + } + if (inst.GetSingleWordInOperand(0) != message_.return_type_id()) { + // Different return types - cannot be the same. + continue; + } + if (inst.NumInOperands() != + 1 + static_cast(message_.argument_type_id().size())) { + // Different numbers of arguments - cannot be the same. + continue; + } + bool found_argument_mismatch = false; + for (uint32_t index = 1; index < inst.NumInOperands(); index++) { + if (message_.argument_type_id(index - 1) != + inst.GetSingleWordInOperand(index)) { + // Argument mismatch - cannot be the same. + found_argument_mismatch = true; + break; + } + } + if (found_argument_mismatch) { + continue; + } + // Everything matches - the type is already declared. + return false; + } + return true; +} + +void TransformationAddTypeFunction::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.return_type_id()}}); + for (auto argument_type_id : message_.argument_type_id()) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {argument_type_id}}); + } + context->module()->AddType(MakeUnique( + context, SpvOpTypeFunction, 0, message_.fresh_id(), in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddTypeFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_type_function() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h new file mode 100644 index 000000000..2b596613a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_function.h @@ -0,0 +1,59 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FUNCTION_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddTypeFunction : public Transformation { + public: + explicit TransformationAddTypeFunction( + const protobufs::TransformationAddTypeFunction& message); + + TransformationAddTypeFunction(uint32_t fresh_id, uint32_t return_type_id, + const std::vector& argument_type_ids); + + // - |message_.fresh_id| must not be used by the module + // - |message_.return_type_id| and each element of |message_.argument_type_id| + // must be the ids of non-function types + // - The module must not contain an OpTypeFunction instruction defining a + // function type with the signature provided by teh given return and + // argument types + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpTypeFunction instruction to the module, with signature given by + // |message_.return_type_id| and |message_.argument_type_id|. The result id + // for the instruction is |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddTypeFunction message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_FUNCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.cpp new file mode 100644 index 000000000..07ab7054a --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.cpp @@ -0,0 +1,71 @@ +// 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/transformation_add_type_matrix.h" + +#include "fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddTypeMatrix::TransformationAddTypeMatrix( + const spvtools::fuzz::protobufs::TransformationAddTypeMatrix& message) + : message_(message) {} + +TransformationAddTypeMatrix::TransformationAddTypeMatrix( + uint32_t fresh_id, uint32_t column_type_id, uint32_t column_count) { + message_.set_fresh_id(fresh_id); + message_.set_column_type_id(column_type_id); + message_.set_column_count(column_count); +} + +bool TransformationAddTypeMatrix::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // The result id must be fresh. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + // The column type must be a floating-point vector. + auto column_type = + context->get_type_mgr()->GetType(message_.column_type_id()); + if (!column_type) { + return false; + } + return column_type->AsVector() && + column_type->AsVector()->element_type()->AsFloat(); +} + +void TransformationAddTypeMatrix::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.column_type_id()}}); + in_operands.push_back( + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.column_count()}}); + context->module()->AddType(MakeUnique( + context, SpvOpTypeMatrix, 0, message_.fresh_id(), in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddTypeMatrix::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_type_matrix() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.h new file mode 100644 index 000000000..69d638906 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_matrix.h @@ -0,0 +1,53 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_MATRIX_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_MATRIX_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddTypeMatrix : public Transformation { + public: + explicit TransformationAddTypeMatrix( + const protobufs::TransformationAddTypeMatrix& message); + + TransformationAddTypeMatrix(uint32_t fresh_id, uint32_t column_type_id, + uint32_t column_count); + + // - |message_.fresh_id| must be a fresh id + // - |message_.column_type_id| must be the id of a floating-point vector type + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpTypeMatrix instruction to the module, with column type + // |message_.column_type_id| and |message_.column_count| columns, with result + // id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddTypeMatrix message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_MATRIX_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.cpp new file mode 100644 index 000000000..1ae83723d --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.cpp @@ -0,0 +1,73 @@ +// 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/transformation_add_type_struct.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddTypeStruct::TransformationAddTypeStruct( + const spvtools::fuzz::protobufs::TransformationAddTypeStruct& message) + : message_(message) {} + +TransformationAddTypeStruct::TransformationAddTypeStruct( + uint32_t fresh_id, const std::vector& member_type_ids) { + message_.set_fresh_id(fresh_id); + for (auto member_type_id : member_type_ids) { + message_.add_member_type_id(member_type_id); + } +} + +bool TransformationAddTypeStruct::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + // A fresh id is required. + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + for (auto member_type : message_.member_type_id()) { + auto type = context->get_type_mgr()->GetType(member_type); + if (!type || type->AsFunction()) { + // The member type id either does not refer to a type, or refers to a + // function type; both are illegal. + return false; + } + } + return true; +} + +void TransformationAddTypeStruct::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + for (auto member_type : message_.member_type_id()) { + in_operands.push_back({SPV_OPERAND_TYPE_ID, {member_type}}); + } + context->module()->AddType(MakeUnique( + context, SpvOpTypeStruct, 0, message_.fresh_id(), in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddTypeStruct::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_type_struct() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.h new file mode 100644 index 000000000..edf3ec6ee --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_struct.h @@ -0,0 +1,54 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_STRUCT_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_STRUCT_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddTypeStruct : public Transformation { + public: + explicit TransformationAddTypeStruct( + const protobufs::TransformationAddTypeStruct& message); + + TransformationAddTypeStruct(uint32_t fresh_id, + const std::vector& component_type_ids); + + // - |message_.fresh_id| must be a fresh id + // - |message_.member_type_id| must be a sequence of non-function type ids + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpTypeStruct instruction whose field types are given by + // |message_.member_type_id|, with result id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddTypeStruct message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_STRUCT_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.cpp new file mode 100644 index 000000000..3fdf50b1c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.cpp @@ -0,0 +1,69 @@ +// 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/transformation_add_type_vector.h" + +#include "fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddTypeVector::TransformationAddTypeVector( + const spvtools::fuzz::protobufs::TransformationAddTypeVector& message) + : message_(message) {} + +TransformationAddTypeVector::TransformationAddTypeVector( + uint32_t fresh_id, uint32_t component_type_id, uint32_t component_count) { + message_.set_fresh_id(fresh_id); + message_.set_component_type_id(component_type_id); + message_.set_component_count(component_count); +} + +bool TransformationAddTypeVector::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + return false; + } + auto component_type = + context->get_type_mgr()->GetType(message_.component_type_id()); + if (!component_type) { + return false; + } + return component_type->AsBool() || component_type->AsFloat() || + component_type->AsInteger(); +} + +void TransformationAddTypeVector::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + opt::Instruction::OperandList in_operands; + in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.component_type_id()}}); + in_operands.push_back( + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.component_count()}}); + context->module()->AddType(MakeUnique( + context, SpvOpTypeVector, 0, message_.fresh_id(), in_operands)); + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // We have added an instruction to the module, so need to be careful about the + // validity of existing analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddTypeVector::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_type_vector() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.h new file mode 100644 index 000000000..af840f5eb --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_type_vector.h @@ -0,0 +1,53 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_VECTOR_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_VECTOR_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddTypeVector : public Transformation { + public: + explicit TransformationAddTypeVector( + const protobufs::TransformationAddTypeVector& message); + + TransformationAddTypeVector(uint32_t fresh_id, uint32_t component_type_id, + uint32_t component_count); + + // - |message_.fresh_id| must be a fresh id + // - |message_.component_type_id| must be the id of a scalar type + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Adds an OpTypeVector instruction to the module, with component type + // |message_.component_type_id| and |message_.component_count| components, + // with result id |message_.fresh_id|. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddTypeVector message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_VECTOR_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h index 3a75ac9d9..ac5e978df 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h @@ -47,6 +47,8 @@ class TransformationCopyObject : public Transformation { // - It must be legal to insert an OpCopyObject instruction directly // before 'inst'. // - |message_.object| must be available directly before 'inst'. + // - |message_.object| must not be a null pointer or undefined pointer (so as + // to make it legal to load from copied pointers). bool IsApplicable(opt::IRContext* context, const FactManager& fact_manager) const override; diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.cpp new file mode 100644 index 000000000..316e80df3 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.cpp @@ -0,0 +1,81 @@ +// 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/transformation_merge_blocks.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/block_merge_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationMergeBlocks::TransformationMergeBlocks( + const spvtools::fuzz::protobufs::TransformationMergeBlocks& message) + : message_(message) {} + +TransformationMergeBlocks::TransformationMergeBlocks(uint32_t block_id) { + message_.set_block_id(block_id); +} + +bool TransformationMergeBlocks::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + auto second_block = fuzzerutil::MaybeFindBlock(context, message_.block_id()); + // The given block must exist. + if (!second_block) { + return false; + } + // The block must have just one predecessor. + auto predecessors = context->cfg()->preds(second_block->id()); + if (predecessors.size() != 1) { + return false; + } + auto first_block = context->cfg()->block(predecessors.at(0)); + + return opt::blockmergeutil::CanMergeWithSuccessor(context, first_block); +} + +void TransformationMergeBlocks::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* /*unused*/) const { + auto second_block = fuzzerutil::MaybeFindBlock(context, message_.block_id()); + auto first_block = + context->cfg()->block(context->cfg()->preds(second_block->id()).at(0)); + + auto function = first_block->GetParent(); + // We need an iterator pointing to the predecessor, hence the loop. + for (auto bi = function->begin(); bi != function->end(); ++bi) { + if (bi->id() == first_block->id()) { + assert(opt::blockmergeutil::CanMergeWithSuccessor(context, &*bi) && + "Because 'Apply' should only be invoked if 'IsApplicable' holds, " + "it must be possible to merge |bi| with its successor."); + opt::blockmergeutil::MergeWithSuccessor(context, function, bi); + // Invalidate all analyses, since we have changed the module + // significantly. + context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); + return; + } + } + assert(false && + "Control should not reach here - we should always find the desired " + "block"); +} + +protobufs::Transformation TransformationMergeBlocks::ToMessage() const { + protobufs::Transformation result; + *result.mutable_merge_blocks() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.h b/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.h new file mode 100644 index 000000000..86216db3b --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_merge_blocks.h @@ -0,0 +1,54 @@ +// Copyright (c) 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_FUZZ_TRANSFORMATION_MERGE_BLOCKS_H_ +#define SOURCE_FUZZ_TRANSFORMATION_MERGE_BLOCKS_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationMergeBlocks : public Transformation { + public: + explicit TransformationMergeBlocks( + const protobufs::TransformationMergeBlocks& message); + + TransformationMergeBlocks(uint32_t block_id); + + // - |message_.block_id| must be the id of a block, b + // - b must have a single predecessor, a + // - b must be the sole successor of a + // - Replacing a with the merge of a and b (and removing b) must lead to a + // valid module + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // The contents of b are merged into a, and a's terminator is replaced with + // the terminator of b. Block b is removed from the module. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationMergeBlocks message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_MERGE_BLOCKS_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp new file mode 100644 index 000000000..7bbac5483 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp @@ -0,0 +1,943 @@ +// 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/transformation_outline_function.h" + +#include + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +namespace { + +std::map PairSequenceToMap( + const google::protobuf::RepeatedPtrField& + pair_sequence) { + std::map result; + for (auto& pair : pair_sequence) { + result[pair.first()] = pair.second(); + } + return result; +} + +} // namespace + +TransformationOutlineFunction::TransformationOutlineFunction( + const spvtools::fuzz::protobufs::TransformationOutlineFunction& message) + : message_(message) {} + +TransformationOutlineFunction::TransformationOutlineFunction( + uint32_t entry_block, uint32_t exit_block, + uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id, + uint32_t new_function_id, uint32_t new_function_region_entry_block, + uint32_t new_caller_result_id, uint32_t new_callee_result_id, + std::map&& input_id_to_fresh_id, + std::map&& output_id_to_fresh_id) { + message_.set_entry_block(entry_block); + message_.set_exit_block(exit_block); + message_.set_new_function_struct_return_type_id( + new_function_struct_return_type_id); + message_.set_new_function_type_id(new_function_type_id); + message_.set_new_function_id(new_function_id); + 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; + } +} + +bool TransformationOutlineFunction::IsApplicable( + opt::IRContext* context, + const spvtools::fuzz::FactManager& /*unused*/) const { + std::set ids_used_by_this_transformation; + + // The various new ids used by the transformation must be fresh and distinct. + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_function_struct_return_type_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_function_type_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_function_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_function_region_entry_block(), context, + &ids_used_by_this_transformation)) { + return false; + } + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_caller_result_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + message_.new_callee_result_id(), context, + &ids_used_by_this_transformation)) { + return false; + } + + for (auto& pair : message_.input_id_to_fresh_id()) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second(), context, &ids_used_by_this_transformation)) { + return false; + } + } + + for (auto& pair : message_.output_id_to_fresh_id()) { + if (!CheckIdIsFreshAndNotUsedByThisTransformation( + pair.second(), context, &ids_used_by_this_transformation)) { + return false; + } + } + + // The entry and exit block ids must indeed refer to blocks. + for (auto block_id : {message_.entry_block(), message_.exit_block()}) { + auto block_label = context->get_def_use_mgr()->GetDef(block_id); + if (!block_label || block_label->opcode() != SpvOpLabel) { + return false; + } + } + + auto entry_block = context->cfg()->block(message_.entry_block()); + auto exit_block = context->cfg()->block(message_.exit_block()); + + // The entry block cannot start with OpVariable - this would mean that + // outlining would remove a variable from the function containing the region + // being outlined. + if (entry_block->begin()->opcode() == SpvOpVariable) { + return false; + } + + // For simplicity, we do not allow the entry block to be a loop header. + if (entry_block->GetLoopMergeInst()) { + return false; + } + + // For simplicity, we do not allow the exit block to be a merge block or + // continue target. + if (fuzzerutil::IsMergeOrContinue(context, exit_block->id())) { + return false; + } + + // The entry block cannot start with OpPhi. This is to keep the + // transformation logic simple. (Another transformation to split the OpPhis + // from a block could be applied to avoid this scenario.) + if (entry_block->begin()->opcode() == SpvOpPhi) { + return false; + } + + // The 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 = + 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 = + context->GetPostDominatorAnalysis(entry_block->GetParent()); + if (!postdominator_analysis->Dominates(exit_block, entry_block)) { + return false; + } + + // Find all the blocks dominated by |message_.entry_block| and post-dominated + // by |message_.exit_block|. + auto region_set = GetRegionBlocks( + context, entry_block = context->cfg()->block(message_.entry_block()), + exit_block = context->cfg()->block(message_.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 function that contains + // the region. + for (auto& block : *entry_block->GetParent()) { + if (&block == exit_block) { + // It is OK (and typically expected) for the exit block of the region to + // have successors outside the region. It is also OK for the exit block + // to head a structured control flow construct - the block containing the + // call to the outlined function will end up heading this construct if + // outlining takes place. + 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, context, + ®ion_set](uint32_t successor) -> bool { + if (region_set.count(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 had better both be in the region or both be + // outside the region. + auto merge_block = context->cfg()->block(merge->GetSingleWordOperand(0)); + if (region_set.count(&block) != region_set.count(merge_block)) { + return false; + } + } + + if (auto loop_merge = block.GetLoopMergeInst()) { + // Similar to the above, but for the continue target of a loop. + auto continue_target = + context->cfg()->block(loop_merge->GetSingleWordOperand(1)); + if (continue_target != exit_block && + region_set.count(&block) != region_set.count(continue_target)) { + return false; + } + } + } + + // For each region input id, i.e. every id defined outside the region but + // used inside the region, ... + std::map input_id_to_fresh_id_map = + PairSequenceToMap(message_.input_id_to_fresh_id()); + for (auto id : GetRegionInputIds(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) { + return false; + } + // Furthermore, if the input id has pointer type it must be an OpVariable + // or OpFunctionParameter. + auto input_id_inst = context->get_def_use_mgr()->GetDef(id); + if (context->get_def_use_mgr() + ->GetDef(input_id_inst->type_id()) + ->opcode() == SpvOpTypePointer) { + switch (input_id_inst->opcode()) { + case SpvOpFunctionParameter: + case SpvOpVariable: + // These are OK. + break; + default: + // Anything else is not OK. + return false; + } + } + } + + // For each region output id -- i.e. every id defined inside the region but + // used outside the region -- there needs to be a corresponding fresh id that + // can hold the value for this id computed in the outlined function. + std::map output_id_to_fresh_id_map = + PairSequenceToMap(message_.output_id_to_fresh_id()); + for (auto id : GetRegionOutputIds(context, region_set, exit_block)) { + if (output_id_to_fresh_id_map.count(id) == 0) { + return false; + } + } + + return true; +} + +void TransformationOutlineFunction::Apply( + opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const { + // The entry block for the region before outlining. + auto original_region_entry_block = + context->cfg()->block(message_.entry_block()); + + // The exit block for the region before outlining. + auto original_region_exit_block = + context->cfg()->block(message_.exit_block()); + + // The single-entry single-exit region defined by |message_.entry_block| and + // |message_.exit_block|. + std::set region_blocks = GetRegionBlocks( + context, original_region_entry_block, original_region_exit_block); + + // Input and output ids for the region being outlined. + std::vector region_input_ids = + GetRegionInputIds(context, region_blocks, original_region_exit_block); + std::vector region_output_ids = + GetRegionOutputIds(context, region_blocks, original_region_exit_block); + + // Maps from input and output ids to fresh ids. + std::map input_id_to_fresh_id_map = + PairSequenceToMap(message_.input_id_to_fresh_id()); + std::map output_id_to_fresh_id_map = + PairSequenceToMap(message_.output_id_to_fresh_id()); + + UpdateModuleIdBoundForFreshIds(context, input_id_to_fresh_id_map, + output_id_to_fresh_id_map); + + // Construct a map that associates each output id with its type id. + std::map output_id_to_type_id; + for (uint32_t output_id : region_output_ids) { + output_id_to_type_id[output_id] = + context->get_def_use_mgr()->GetDef(output_id)->type_id(); + } + + // The region will be collapsed to a single block that calls a function + // containing the outlined region. This block needs to end with whatever + // the exit block of the region ended with before outlining. We thus clone + // the terminator of the region's exit block, and the merge instruction for + // the block if there is one, so that we can append them to the end of the + // collapsed block later. + std::unique_ptr cloned_exit_block_terminator = + std::unique_ptr( + original_region_exit_block->terminator()->Clone(context)); + std::unique_ptr cloned_exit_block_merge = + original_region_exit_block->GetMergeInst() + ? std::unique_ptr( + original_region_exit_block->GetMergeInst()->Clone(context)) + : nullptr; + + // Make a function prototype for the outlined function, which involves + // figuring out its required type. + std::unique_ptr outlined_function = + PrepareFunctionPrototype(region_input_ids, region_output_ids, + input_id_to_fresh_id_map, context, fact_manager); + + // If the original function was livesafe, the new function should also be + // livesafe. + if (fact_manager->FunctionIsLivesafe( + original_region_entry_block->GetParent()->result_id())) { + fact_manager->AddFactFunctionIsLivesafe(message_.new_function_id()); + } + + // Adapt the region to be outlined so that its input ids are replaced with the + // ids of the outlined function's input parameters, and so that output ids + // are similarly remapped. + RemapInputAndOutputIdsInRegion( + context, *original_region_exit_block, region_blocks, region_input_ids, + region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map); + + // Fill out the body of the outlined function according to the region that is + // being outlined. + PopulateOutlinedFunction(*original_region_entry_block, + *original_region_exit_block, region_blocks, + region_output_ids, output_id_to_fresh_id_map, + context, outlined_function.get(), fact_manager); + + // Collapse the region that has been outlined into a function down to a single + // block that calls said function. + ShrinkOriginalRegion( + context, region_blocks, region_input_ids, region_output_ids, + output_id_to_type_id, outlined_function->type_id(), + std::move(cloned_exit_block_merge), + std::move(cloned_exit_block_terminator), original_region_entry_block); + + // Add the outlined function to the module. + context->module()->AddFunction(std::move(outlined_function)); + + // Major surgery has been conducted on the module, so invalidate all analyses. + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationOutlineFunction::ToMessage() const { + protobufs::Transformation result; + *result.mutable_outline_function() = message_; + return result; +} + +std::vector TransformationOutlineFunction::GetRegionInputIds( + opt::IRContext* context, const std::set& region_set, + opt::BasicBlock* region_exit_block) { + std::vector result; + + auto enclosing_function = region_exit_block->GetParent(); + + // Consider each parameter of the function containing the region. + enclosing_function->ForEachParam([context, ®ion_set, &result]( + opt::Instruction* function_parameter) { + // Consider every use of the parameter. + context->get_def_use_mgr()->WhileEachUse( + function_parameter, [context, function_parameter, ®ion_set, &result]( + opt::Instruction* use, uint32_t /*unused*/) { + // Get the block, if any, in which the parameter is used. + auto use_block = context->get_instr_block(use); + // If the use is in a block that lies within the region, the + // parameter is an input id for the region. + if (use_block && region_set.count(use_block) != 0) { + result.push_back(function_parameter->result_id()); + return false; + } + return true; + }); + }); + + // Consider all definitions in the function that might turn out to be input + // ids. + for (auto& block : *enclosing_function) { + std::vector candidate_input_ids_for_block; + if (region_set.count(&block) == 0) { + // All instructions in blocks outside the region are candidate's for + // generating input ids. + for (auto& inst : block) { + candidate_input_ids_for_block.push_back(&inst); + } + } else { + // Blocks in the region cannot generate input ids. + continue; + } + + // Consider each candidate input id to check whether it is used in the + // region. + for (auto& inst : candidate_input_ids_for_block) { + context->get_def_use_mgr()->WhileEachUse( + inst, + [context, &inst, region_exit_block, ®ion_set, &result]( + opt::Instruction* use, uint32_t /*unused*/) -> bool { + + // Find the block in which this id use occurs, recording the id as + // an input id if the block is outside the region, with some + // exceptions detailed below. + auto use_block = context->get_instr_block(use); + + if (!use_block) { + // There might be no containing block, e.g. if the use is in a + // decoration. + return true; + } + + if (region_set.count(use_block) == 0) { + // The use is not in the region: this does not make it an input + // id. + return true; + } + + if (use_block == region_exit_block && use->IsBlockTerminator()) { + // We do not regard uses in the exit block terminator as input + // ids, as this terminator does not get outlined. + return true; + } + + result.push_back(inst->result_id()); + return false; + }); + } + } + return result; +} + +std::vector TransformationOutlineFunction::GetRegionOutputIds( + opt::IRContext* context, const std::set& region_set, + opt::BasicBlock* region_exit_block) { + std::vector result; + + // Consider each block in the function containing the region. + for (auto& block : *region_exit_block->GetParent()) { + if (region_set.count(&block) == 0) { + // Skip blocks that are not in the region. + continue; + } + // Consider each use of each instruction defined in the block. + for (auto& inst : block) { + context->get_def_use_mgr()->WhileEachUse( + &inst, + [®ion_set, 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. + auto use_block = context->get_instr_block(use); + + if (!use_block) { + // There might be no containing block, e.g. if the use is in a + // decoration. + return true; + } + + if (region_set.count(use_block) != 0) { + // The use is in the region. + if (use_block != region_exit_block || !use->IsBlockTerminator()) { + // Furthermore, the use is not in the terminator of the region's + // exit block. + return true; + } + } + + result.push_back(inst.result_id()); + return false; + }); + } + } + return result; +} + +std::set TransformationOutlineFunction::GetRegionBlocks( + opt::IRContext* context, opt::BasicBlock* entry_block, + opt::BasicBlock* exit_block) { + auto enclosing_function = entry_block->GetParent(); + auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function); + auto postdominator_analysis = + context->GetPostDominatorAnalysis(enclosing_function); + + std::set result; + for (auto& block : *enclosing_function) { + if (dominator_analysis->Dominates(entry_block, &block) && + postdominator_analysis->Dominates(exit_block, &block)) { + result.insert(&block); + } + } + return result; +} + +std::unique_ptr +TransformationOutlineFunction::PrepareFunctionPrototype( + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& input_id_to_fresh_id_map, + opt::IRContext* context, FactManager* fact_manager) const { + uint32_t return_type_id = 0; + uint32_t function_type_id = 0; + + // First, try to find an existing function type that is suitable. This is + // only possible if the region generates no output ids; if it generates output + // ids we are going to make a new struct for those, and since that struct does + // not exist there cannot already be a function type with this struct as its + // return type. + if (region_output_ids.empty()) { + std::vector return_and_parameter_types; + opt::analysis::Void void_type; + return_type_id = context->get_type_mgr()->GetId(&void_type); + return_and_parameter_types.push_back(return_type_id); + for (auto id : region_input_ids) { + return_and_parameter_types.push_back( + context->get_def_use_mgr()->GetDef(id)->type_id()); + } + function_type_id = + fuzzerutil::FindFunctionType(context, return_and_parameter_types); + } + + // If no existing function type was found, we need to create one. + if (function_type_id == 0) { + assert( + ((return_type_id == 0) == !region_output_ids.empty()) && + "We should only have set the return type if there are no output ids."); + // If the region generates output ids, we need to make a struct with one + // field per output id. + if (!region_output_ids.empty()) { + opt::Instruction::OperandList struct_member_types; + for (uint32_t output_id : region_output_ids) { + auto output_id_type = + context->get_def_use_mgr()->GetDef(output_id)->type_id(); + struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}}); + } + // Add a new struct type to the module. + context->module()->AddType(MakeUnique( + context, SpvOpTypeStruct, 0, + message_.new_function_struct_return_type_id(), + std::move(struct_member_types))); + // The return type for the function is the newly-created struct. + return_type_id = message_.new_function_struct_return_type_id(); + } + assert( + return_type_id != 0 && + "We should either have a void return type, or have created a struct."); + + // The region's input ids dictate the parameter types to the function. + opt::Instruction::OperandList function_type_operands; + function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}}); + for (auto id : region_input_ids) { + function_type_operands.push_back( + {SPV_OPERAND_TYPE_ID, + {context->get_def_use_mgr()->GetDef(id)->type_id()}}); + } + // Add a new function type to the module, and record that this is the type + // id for the new function. + context->module()->AddType(MakeUnique( + context, SpvOpTypeFunction, 0, message_.new_function_type_id(), + function_type_operands)); + function_type_id = message_.new_function_type_id(); + } + + // Create a new function with |message_.new_function_id| as the function id, + // and the return type and function type prepared above. + std::unique_ptr outlined_function = + MakeUnique(MakeUnique( + context, SpvOpFunction, return_type_id, message_.new_function_id(), + opt::Instruction::OperandList( + {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {SpvFunctionControlMaskNone}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {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|. + for (auto id : region_input_ids) { + outlined_function->AddParameter(MakeUnique( + context, SpvOpFunctionParameter, + context->get_def_use_mgr()->GetDef(id)->type_id(), + input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList())); + // If the input id is an arbitrary-valued variable, the same should be true + // of the corresponding parameter. + if (fact_manager->VariableValueIsArbitrary(id)) { + fact_manager->AddFactValueOfVariableIsArbitrary( + input_id_to_fresh_id_map.at(id)); + } + } + + return outlined_function; +} + +void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds( + opt::IRContext* context, + const std::map& input_id_to_fresh_id_map, + const std::map& output_id_to_fresh_id_map) const { + // Enlarge the module's id bound as needed to accommodate the various fresh + // ids associated with the transformation. + fuzzerutil::UpdateModuleIdBound( + context, message_.new_function_struct_return_type_id()); + fuzzerutil::UpdateModuleIdBound(context, message_.new_function_type_id()); + fuzzerutil::UpdateModuleIdBound(context, message_.new_function_id()); + fuzzerutil::UpdateModuleIdBound(context, + message_.new_function_region_entry_block()); + fuzzerutil::UpdateModuleIdBound(context, message_.new_caller_result_id()); + fuzzerutil::UpdateModuleIdBound(context, message_.new_callee_result_id()); + + for (auto& entry : input_id_to_fresh_id_map) { + fuzzerutil::UpdateModuleIdBound(context, entry.second); + } + + for (auto& entry : output_id_to_fresh_id_map) { + fuzzerutil::UpdateModuleIdBound(context, entry.second); + } +} + +void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion( + opt::IRContext* context, const opt::BasicBlock& original_region_exit_block, + const std::set& region_blocks, + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& input_id_to_fresh_id_map, + const std::map& output_id_to_fresh_id_map) const { + // Change all uses of input ids inside the region to the corresponding fresh + // ids that will ultimately be parameters of the outlined function. + // This is done by considering each region input id in turn. + for (uint32_t id : region_input_ids) { + // We then consider each use of the input id. + context->get_def_use_mgr()->ForEachUse( + id, [context, id, &input_id_to_fresh_id_map, region_blocks]( + opt::Instruction* use, uint32_t operand_index) { + // Find the block in which this use of the input id occurs. + opt::BasicBlock* use_block = context->get_instr_block(use); + // We want to rewrite the use id if its block occurs in the outlined + // region. + if (region_blocks.count(use_block) != 0) { + // Rewrite this use of the input id. + use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)}); + } + }); + } + + // Change each definition of a region output id to define the corresponding + // fresh ids that will store intermediate value for the output ids. Also + // change all uses of the output id located in the outlined region. + // This is done by considering each region output id in turn. + for (uint32_t id : region_output_ids) { + // First consider each use of the output id and update the relevant uses. + context->get_def_use_mgr()->ForEachUse( + id, + [context, &original_region_exit_block, id, &output_id_to_fresh_id_map, + region_blocks](opt::Instruction* use, uint32_t operand_index) { + // Find the block in which this use of the output id occurs. + auto use_block = context->get_instr_block(use); + // We want to rewrite the use id if its block occurs in the outlined + // region, with one exception: the terminator of the exit block of + // the region is going to remain in the original function, so if the + // use appears in such a terminator instruction we leave it alone. + if ( + // The block is in the region ... + region_blocks.count(use_block) != 0 && + // ... and the use is not in the terminator instruction of the + // region's exit block. + !(use_block == &original_region_exit_block && + use->IsBlockTerminator())) { + // Rewrite this use of the output id. + use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)}); + } + }); + + // Now change the instruction that defines the output id so that it instead + // defines the corresponding fresh id. We do this after changing all the + // uses so that the definition of the original id is still registered when + // we analyse its uses. + context->get_def_use_mgr()->GetDef(id)->SetResultId( + output_id_to_fresh_id_map.at(id)); + } +} + +void TransformationOutlineFunction::PopulateOutlinedFunction( + const opt::BasicBlock& original_region_entry_block, + const opt::BasicBlock& original_region_exit_block, + const std::set& region_blocks, + const std::vector& region_output_ids, + const std::map& output_id_to_fresh_id_map, + opt::IRContext* context, opt::Function* outlined_function, + FactManager* fact_manager) const { + // When we create the exit block for the outlined region, we use this pointer + // to track of it so that we can manipulate it later. + opt::BasicBlock* outlined_region_exit_block = nullptr; + + // The region entry block in the new function is identical to the entry block + // of the region being outlined, except that it has + // |message_.new_function_region_entry_block| as its id. + std::unique_ptr outlined_region_entry_block = + MakeUnique(MakeUnique( + context, SpvOpLabel, 0, message_.new_function_region_entry_block(), + opt::Instruction::OperandList())); + outlined_region_entry_block->SetParent(outlined_function); + + // If the original region's entry block was dead, the outlined region's entry + // block is also dead. + if (fact_manager->BlockIsDead(original_region_entry_block.id())) { + fact_manager->AddFactBlockIsDead(outlined_region_entry_block->id()); + } + + if (&original_region_entry_block == &original_region_exit_block) { + outlined_region_exit_block = outlined_region_entry_block.get(); + } + + for (auto& inst : original_region_entry_block) { + outlined_region_entry_block->AddInstruction( + std::unique_ptr(inst.Clone(context))); + } + outlined_function->AddBasicBlock(std::move(outlined_region_entry_block)); + + // We now go through the single-entry single-exit region defined by the entry + // and exit blocks, adding clones of all blocks to the new function. + + // Consider every block in the enclosing function. + auto enclosing_function = original_region_entry_block.GetParent(); + for (auto block_it = enclosing_function->begin(); + block_it != enclosing_function->end();) { + // Skip the region's entry block - we already dealt with it above. + if (region_blocks.count(&*block_it) == 0 || + &*block_it == &original_region_entry_block) { + ++block_it; + continue; + } + // Clone the block so that it can be added to the new function. + auto cloned_block = + std::unique_ptr(block_it->Clone(context)); + + // If this is the region's exit block, then the cloned block is the outlined + // region's exit block. + if (&*block_it == &original_region_exit_block) { + assert(outlined_region_exit_block == nullptr && + "We should not yet have encountered the exit block."); + outlined_region_exit_block = cloned_block.get(); + } + + cloned_block->SetParent(outlined_function); + + // Redirect any OpPhi operands whose predecessors are the original region + // entry block to become the new function entry block. + cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) { + for (uint32_t predecessor_index = 1; + predecessor_index < phi_inst->NumInOperands(); + predecessor_index += 2) { + if (phi_inst->GetSingleWordInOperand(predecessor_index) == + message_.entry_block()) { + phi_inst->SetInOperand(predecessor_index, + {message_.new_function_region_entry_block()}); + } + } + }); + + outlined_function->AddBasicBlock(std::move(cloned_block)); + block_it = block_it.Erase(); + } + assert(outlined_region_exit_block != nullptr && + "We should have encountered the region's exit block when iterating " + "through the function"); + + // We now need to adapt the exit block for the region - in the new function - + // so that it ends with a return. + + // We first eliminate the merge instruction (if any) and the terminator for + // the cloned exit block. + for (auto inst_it = outlined_region_exit_block->begin(); + inst_it != outlined_region_exit_block->end();) { + if (inst_it->opcode() == SpvOpLoopMerge || + inst_it->opcode() == SpvOpSelectionMerge) { + inst_it = inst_it.Erase(); + } else if (inst_it->IsBlockTerminator()) { + inst_it = inst_it.Erase(); + } else { + ++inst_it; + } + } + + // We now add either OpReturn or OpReturnValue as the cloned exit block's + // terminator. + if (region_output_ids.empty()) { + // The case where there are no region output ids is simple: we just add + // OpReturn. + outlined_region_exit_block->AddInstruction(MakeUnique( + context, SpvOpReturn, 0, 0, opt::Instruction::OperandList())); + } else { + // In the case where there are output ids, we add an OpCompositeConstruct + // instruction to pack all the output values into a struct, and then an + // OpReturnValue instruction to return this struct. + opt::Instruction::OperandList struct_member_operands; + for (uint32_t id : region_output_ids) { + struct_member_operands.push_back( + {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}}); + } + outlined_region_exit_block->AddInstruction(MakeUnique( + context, SpvOpCompositeConstruct, + message_.new_function_struct_return_type_id(), + message_.new_callee_result_id(), struct_member_operands)); + outlined_region_exit_block->AddInstruction(MakeUnique( + context, SpvOpReturnValue, 0, 0, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}}))); + } + + outlined_function->SetFunctionEnd(MakeUnique( + context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList())); +} + +void TransformationOutlineFunction::ShrinkOriginalRegion( + opt::IRContext* context, std::set& region_blocks, + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& output_id_to_type_id, + uint32_t return_type_id, + std::unique_ptr cloned_exit_block_merge, + std::unique_ptr cloned_exit_block_terminator, + opt::BasicBlock* original_region_entry_block) const { + // Erase all blocks from the original function that are in the outlined + // region, except for the region's entry block. + // + // In the process, identify all references to the exit block of the region, + // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them + // to refer to the region entry block (the single block to which we are + // shrinking the region). + auto enclosing_function = original_region_entry_block->GetParent(); + for (auto block_it = enclosing_function->begin(); + block_it != enclosing_function->end();) { + if (&*block_it == original_region_entry_block) { + ++block_it; + } else if (region_blocks.count(&*block_it) == 0) { + // The block is not in the region. Check whether it has the last block + // of the region as an OpPhi predecessor, and if so change the + // predecessor to be the first block of the region (i.e. the block + // containing the call to what was outlined). + assert(block_it->MergeBlockIdIfAny() != message_.exit_block() && + "Outlined region must not end with a merge block"); + assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() && + "Outlined region must not end with a continue target"); + block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) { + for (uint32_t predecessor_index = 1; + predecessor_index < phi_inst->NumInOperands(); + predecessor_index += 2) { + if (phi_inst->GetSingleWordInOperand(predecessor_index) == + message_.exit_block()) { + phi_inst->SetInOperand(predecessor_index, {message_.entry_block()}); + } + } + }); + ++block_it; + } else { + // The block is in the region and is not the region's entry block: kill + // it. + block_it = block_it.Erase(); + } + } + + // Now erase all instructions from the region's entry block, as they have + // been outlined. + for (auto inst_it = original_region_entry_block->begin(); + inst_it != original_region_entry_block->end();) { + inst_it = inst_it.Erase(); + } + + // Now we add a call to the outlined function to the region's entry block. + opt::Instruction::OperandList function_call_operands; + function_call_operands.push_back( + {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}}); + // The function parameters are the region input ids. + for (auto input_id : region_input_ids) { + function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}}); + } + + original_region_entry_block->AddInstruction(MakeUnique( + context, SpvOpFunctionCall, return_type_id, + message_.new_caller_result_id(), function_call_operands)); + + // If there are output ids, the function call will return a struct. For each + // output id, we add an extract operation to pull the appropriate struct + // member out into an output id. + for (uint32_t index = 0; index < region_output_ids.size(); ++index) { + uint32_t output_id = region_output_ids[index]; + original_region_entry_block->AddInstruction(MakeUnique( + context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id), + output_id, + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}}))); + } + + // Finally, we terminate the block with the merge instruction (if any) that + // used to belong to the region's exit block, and the terminator that used + // to belong to the region's exit block. + if (cloned_exit_block_merge != nullptr) { + original_region_entry_block->AddInstruction( + std::move(cloned_exit_block_merge)); + } + original_region_entry_block->AddInstruction( + std::move(cloned_exit_block_terminator)); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.h b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.h new file mode 100644 index 000000000..571179062 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_outline_function.h @@ -0,0 +1,221 @@ +// 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_TRANSFORMATION_OUTLINE_FUNCTION_H_ +#define SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_ + +#include +#include +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationOutlineFunction : public Transformation { + public: + explicit TransformationOutlineFunction( + const protobufs::TransformationOutlineFunction& message); + + TransformationOutlineFunction( + uint32_t entry_block, uint32_t exit_block, + uint32_t new_function_struct_return_type_id, + uint32_t new_function_type_id, uint32_t new_function_id, + uint32_t new_function_region_entry_block, uint32_t new_caller_result_id, + uint32_t new_callee_result_id, + std::map&& input_id_to_fresh_id, + std::map&& output_id_to_fresh_id); + + // - All the fresh ids occurring in the transformation must be distinct and + // fresh + // - |message_.entry_block| and |message_.exit_block| must form a single-entry + // single-exit control flow graph region + // - |message_.entry_block| must not start with OpVariable + // - |message_.entry_block| must not be a loop header + // - |message_.exit_block| must not be a merge block or the continue target + // of a loop + // - A structured control flow construct must lie either completely within the + // region or completely outside it + // - |message.entry_block| must not start with OpPhi; this is to keep the + // transformation simple - another transformation should be used to split + // a desired entry block that starts with OpPhi if needed + // - |message_.input_id_to_fresh_id| must contain an entry for every id + // defined outside the region but used in the region + // - |message_.output_id_to_fresh_id| must contain an entry for every id + // defined in the region but used outside the region + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // - A new function with id |message_.new_function_id| is added to the module. + // - If the region generates output ids, the return type of this function is + // a new struct type with one field per output id, and with type id + // |message_.new_function_struct_return_type|, otherwise the function return + // types is void and |message_.new_function_struct_return_type| is not used. + // - If the region generates input ids, the new function has one parameter per + // input id. Fresh ids for these parameters are provided by + // |message_.input_id_to_fresh_id|. + // - 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 + // |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. + // - The outlined region is replaced with a single block, with the same id + // as |message_.entry_block|, and which calls the new function, passing the + // region's input ids as parameters. The result is stored in + // |message_.new_caller_result_id|, which has type + // |message_.new_function_struct_return_type| (unless there are + // no output ids, in which case the return type is void). The components + // of this returned struct are then copied out into the region's output ids. + // The block ends with the merge instruction (if any) and terminator of + // |message_.exit_block|. + // - The body of the new function is identical to the outlined region, except + // that (a) the region's entry block has id + // |message_.new_function_region_entry_block|, (b) input id uses are + // replaced with parameter accesses, (c) and definitions of output ids are + // replaced with definitions of corresponding fresh ids provided by + // |message_.output_id_to_fresh_id|, and (d) the block of the function + // ends by returning a composite of type + // |message_.new_function_struct_return_type| comprised of all the fresh + // output ids (unless the return type is void, in which case no value is + // returned. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + // Returns the set of blocks dominated by |entry_block| and post-dominated + // by |exit_block|. + static std::set GetRegionBlocks( + opt::IRContext* context, opt::BasicBlock* entry_block, + opt::BasicBlock* exit_block); + + // Yields ids that are used in |region_set| and that are either parameters + // to the function containing |region_set|, or are defined by blocks of this + // function that are outside |region_set|. + // + // Special cases: OpPhi instructions in |region_entry_block| and the + // terminator of |region_exit_block| do not get outlined, therefore + // - id uses in OpPhi instructions in |region_entry_block| are ignored + // - id uses in the terminator instruction of |region_exit_block| are ignored + static std::vector GetRegionInputIds( + opt::IRContext* context, const std::set& region_set, + opt::BasicBlock* region_exit_block); + + // Yields all ids that are defined in |region_set| and used outside + // |region_set|. + // + // Special cases: for similar reasons as for |GetRegionInputIds|, + // - ids defined in the region and used in the terminator of + // |region_exit_block| count as output ids + static std::vector GetRegionOutputIds( + opt::IRContext* context, const std::set& region_set, + opt::BasicBlock* region_exit_block); + + private: + // Ensures that the module's id bound is at least the maximum of any fresh id + // associated with the transformation. + void UpdateModuleIdBoundForFreshIds( + opt::IRContext* context, + const std::map& input_id_to_fresh_id_map, + const std::map& output_id_to_fresh_id_map) const; + + // Uses |input_id_to_fresh_id_map| and |output_id_to_fresh_id_map| to convert, + // in the region to be outlined, all the input ids in |region_input_ids| and + // the output ids in |region_output_ids| to their fresh counterparts. + // Parameters |region_blocks| provides access to the blocks that must be + // modified, and |original_region_exit_block| allows for some special cases + // where ids should not be remapped. + void RemapInputAndOutputIdsInRegion( + opt::IRContext* context, + const opt::BasicBlock& original_region_exit_block, + const std::set& region_blocks, + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& input_id_to_fresh_id_map, + const std::map& output_id_to_fresh_id_map) const; + + // Produce a Function object that has the right function type and parameter + // declarations. The function argument types and parameter ids are dictated + // by |region_input_ids| and |input_id_to_fresh_id_map|. The function return + // type is dictated by |region_output_ids|. + // + // A new struct type to represent the function return type, and a new function + // type for the function, will be added to the module (unless suitable types + // are already present). + // + // Facts about the function containing the outlined region that are relevant + // to the new function are propagated via |fact_manager|. + std::unique_ptr PrepareFunctionPrototype( + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& input_id_to_fresh_id_map, + opt::IRContext* context, FactManager* fact_manager) const; + + // Creates the body of the outlined function by cloning blocks from the + // original region, given by |region_blocks|, adapting the cloned version + // of |original_region_exit_block| so that it returns something appropriate, + // and patching up branches to |original_region_entry_block| to refer to its + // clone. Parameters |region_output_ids| and |output_id_to_fresh_id_map| are + // used to determine what the function should return. + // + // The |fact_manager| argument allow facts about blocks being outlined, e.g. + // whether they are dead blocks, to be asserted about blocks that get created + // during outlining. + void PopulateOutlinedFunction( + const opt::BasicBlock& original_region_entry_block, + const opt::BasicBlock& original_region_exit_block, + const std::set& region_blocks, + const std::vector& region_output_ids, + const std::map& output_id_to_fresh_id_map, + opt::IRContext* context, opt::Function* outlined_function, + FactManager* fact_manager) const; + + // Shrinks the outlined region, given by |region_blocks|, down to the single + // block |original_region_entry_block|. This block is itself shrunk to just + // contain: + // - any OpPhi instructions that were originally present + // - a call to the outlined function, with parameters provided by + // |region_input_ids| + // - instructions to route components of the call's return value into + // |region_output_ids| + // - The merge instruction (if any) and terminator of the original region's + // exit block, given by |cloned_exit_block_merge| and + // |cloned_exit_block_terminator| + // Parameters |output_id_to_type_id| and |return_type_id| provide the + // provide types for the region's output ids, and the return type of the + // outlined function: as the module is in an inconsistent state when this + // function is called, this information cannot be gotten from the def-use + // manager. + void ShrinkOriginalRegion( + opt::IRContext* context, std::set& region_blocks, + const std::vector& region_input_ids, + const std::vector& region_output_ids, + const std::map& output_id_to_type_id, + uint32_t return_type_id, + std::unique_ptr cloned_exit_block_merge, + std::unique_ptr cloned_exit_block_terminator, + opt::BasicBlock* original_region_entry_block) const; + + protobufs::TransformationOutlineFunction message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_OUTLINE_FUNCTION_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp index 9f6da7c38..fc5229edc 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp @@ -80,7 +80,7 @@ bool TransformationSplitBlock::IsApplicable( } void TransformationSplitBlock::Apply(opt::IRContext* context, - FactManager* /*unused*/) const { + FactManager* fact_manager) const { opt::Instruction* instruction_to_split_before = FindInstruction(message_.instruction_to_split_before(), context); opt::BasicBlock* block_to_split = @@ -114,6 +114,13 @@ void TransformationSplitBlock::Apply(opt::IRContext* context, "one predecessor."); phi_inst->SetInOperand(1, {block_to_split->id()}); }); + + // If the block being split was dead, the new block arising from the split is + // also dead. + if (fact_manager->BlockIsDead(block_to_split->id())) { + fact_manager->AddFactBlockIsDead(message_.fresh_id()); + } + // Invalidate all analyses context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); } diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h index 63dc7f52b..a193fc7b8 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h @@ -48,6 +48,7 @@ class TransformationSplitBlock : public Transformation { // - All instructions of 'blk' from 'inst' onwards are moved into the new // block. // - 'blk' is made to jump unconditionally to the new block. + // - If 'blk' was dead, the new block is also dead. void Apply(opt::IRContext* context, FactManager* fact_manager) const override; protobufs::Transformation ToMessage() const override; diff --git a/3rdparty/spirv-tools/source/link/linker.cpp b/3rdparty/spirv-tools/source/link/linker.cpp index d99a1e872..da6f0a78c 100644 --- a/3rdparty/spirv-tools/source/link/linker.cpp +++ b/3rdparty/spirv-tools/source/link/linker.cpp @@ -137,8 +137,6 @@ spv_result_t CheckImportExportCompatibility(const MessageConsumer& consumer, // TODO(pierremoreau): Linkage attributes applied by a group decoration are // currently not handled. (You could have a group being // applied to a single ID.) -// TODO(pierremoreau): Run a pass for removing dead instructions, for example -// OpName for prototypes of imported funcions. spv_result_t RemoveLinkageSpecificInstructions( const MessageConsumer& consumer, const LinkerOptions& options, const LinkageTable& linkings_to_do, DecorationManager* decoration_manager, @@ -326,6 +324,11 @@ spv_result_t MergeModules(const MessageConsumer& consumer, linked_module->AddDebug3Inst( std::unique_ptr(inst.Clone(linked_context))); + for (const auto& module : input_modules) + for (const auto& inst : module->ext_inst_debuginfo()) + linked_module->AddExtInstDebugInfo( + std::unique_ptr(inst.Clone(linked_context))); + // If the generated module uses SPIR-V 1.1 or higher, add an // OpModuleProcessed instruction about the linking step. if (linked_module->version() >= 0x10100) { @@ -531,24 +534,6 @@ spv_result_t RemoveLinkageSpecificInstructions( // TODO(pierremoreau): Remove FuncParamAttr decorations of imported // functions' return type. - // Remove FuncParamAttr decorations of imported functions' parameters. - // From the SPIR-V specification, Sec. 2.13: - // When resolving imported functions, the Function Control and all Function - // Parameter Attributes are taken from the function definition, and not - // from the function declaration. - for (const auto& linking_entry : linkings_to_do) { - for (const auto parameter_id : - linking_entry.imported_symbol.parameter_ids) { - decoration_manager->RemoveDecorationsFrom( - parameter_id, [](const Instruction& inst) { - return (inst.opcode() == SpvOpDecorate || - inst.opcode() == SpvOpMemberDecorate) && - inst.GetSingleWordInOperand(1u) == - SpvDecorationFuncParamAttr; - }); - } - } - // Remove prototypes of imported functions for (const auto& linking_entry : linkings_to_do) { for (auto func_iter = linked_context->module()->begin(); @@ -746,24 +731,34 @@ spv_result_t Link(const Context& context, const uint32_t* const* binaries, opt::Pass::Status pass_res = manager.Run(&linked_context); if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA; - // Phase 7: Rematch import variables/functions to export variables/functions - for (const auto& linking_entry : linkings_to_do) + // Phase 7: Remove all names and decorations of import variables/functions + for (const auto& linking_entry : linkings_to_do) { + linked_context.KillNamesAndDecorates(linking_entry.imported_symbol.id); + for (const auto parameter_id : + linking_entry.imported_symbol.parameter_ids) { + linked_context.KillNamesAndDecorates(parameter_id); + } + } + + // Phase 8: Rematch import variables/functions to export variables/functions + for (const auto& linking_entry : linkings_to_do) { linked_context.ReplaceAllUsesWith(linking_entry.imported_symbol.id, linking_entry.exported_symbol.id); + } - // Phase 8: Remove linkage specific instructions, such as import/export + // Phase 9: Remove linkage specific instructions, such as import/export // attributes, linkage capability, etc. if applicable res = RemoveLinkageSpecificInstructions(consumer, options, linkings_to_do, linked_context.get_decoration_mgr(), &linked_context); if (res != SPV_SUCCESS) return res; - // Phase 9: Compact the IDs used in the module + // Phase 10: Compact the IDs used in the module manager.AddPass(); pass_res = manager.Run(&linked_context); if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA; - // Phase 10: Output the module + // Phase 11: Output the module linked_context.module()->ToBinary(linked_binary, true); return SPV_SUCCESS; diff --git a/3rdparty/spirv-tools/source/operand.cpp b/3rdparty/spirv-tools/source/operand.cpp index cd26a3d1a..304260668 100644 --- a/3rdparty/spirv-tools/source/operand.cpp +++ b/3rdparty/spirv-tools/source/operand.cpp @@ -19,6 +19,8 @@ #include +#include "DebugInfo.h" +#include "OpenCLDebugInfo100.h" #include "source/macro.h" #include "source/spirv_constant.h" #include "source/spirv_target_env.h" @@ -29,6 +31,7 @@ // per-item basis. https://github.com/KhronosGroup/SPIRV-Tools/issues/1195 #include "operand.kinds-unified1.inc" +#include "spirv-tools/libspirv.h" static const spv_operand_table_t kOperandTable = { ARRAY_SIZE(pygen_variable_OperandInfoTable), @@ -228,6 +231,18 @@ const char* spvOperandTypeStr(spv_operand_type_t type) { return "debug type qualifier"; case SPV_OPERAND_TYPE_DEBUG_OPERATION: return "debug operation"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: + return "OpenCL.DebugInfo.100 debug info flags"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + return "OpenCL.DebugInfo.100 debug base type encoding"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE: + return "OpenCL.DebugInfo.100 debug composite type"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER: + return "OpenCL.DebugInfo.100 debug type qualifier"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION: + return "OpenCL.DebugInfo.100 debug operation"; + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY: + return "OpenCL.DebugInfo.100 debug imported entity"; // The next values are for values returned from an instruction, not actually // an operand. So the specific strings don't matter. But let's add them @@ -312,6 +327,11 @@ bool spvOperandIsConcrete(spv_operand_type_t type) { case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: case SPV_OPERAND_TYPE_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY: return true; default: break; @@ -328,6 +348,7 @@ bool spvOperandIsConcreteMask(spv_operand_type_t type) { case SPV_OPERAND_TYPE_FUNCTION_CONTROL: case SPV_OPERAND_TYPE_MEMORY_ACCESS: case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: return true; default: break; @@ -493,3 +514,37 @@ std::function spvOperandCanBeForwardDeclaredFunction( } return out; } + +std::function spvDbgInfoExtOperandCanBeForwardDeclaredFunction( + spv_ext_inst_type_t ext_type, uint32_t key) { + // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/532): Forward + // references for debug info instructions are still in discussion. We must + // update the following lines of code when we conclude the spec. + std::function out; + if (ext_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) { + switch (OpenCLDebugInfo100Instructions(key)) { + case OpenCLDebugInfo100DebugFunction: + out = [](unsigned index) { return index == 13; }; + break; + case OpenCLDebugInfo100DebugTypeComposite: + out = [](unsigned index) { return index >= 13; }; + break; + default: + out = [](unsigned) { return false; }; + break; + } + } else { + switch (DebugInfoInstructions(key)) { + case DebugInfoDebugFunction: + out = [](unsigned index) { return index == 13; }; + break; + case DebugInfoDebugTypeComposite: + out = [](unsigned index) { return index >= 12; }; + break; + default: + out = [](unsigned) { return false; }; + break; + } + } + return out; +} diff --git a/3rdparty/spirv-tools/source/operand.h b/3rdparty/spirv-tools/source/operand.h index 15a182583..7c73c6f56 100644 --- a/3rdparty/spirv-tools/source/operand.h +++ b/3rdparty/spirv-tools/source/operand.h @@ -141,4 +141,11 @@ bool spvIsInIdType(spv_operand_type_t type); std::function spvOperandCanBeForwardDeclaredFunction( SpvOp opcode); +// Takes the instruction key of a debug info extension instruction +// and returns a function object that will return true if the index +// of the operand can be forward declared. This function will +// used in the SSA validation stage of the pipeline +std::function spvDbgInfoExtOperandCanBeForwardDeclaredFunction( + spv_ext_inst_type_t ext_type, uint32_t key); + #endif // SOURCE_OPERAND_H_ diff --git a/3rdparty/spirv-tools/source/opt/amd_ext_to_khr.cpp b/3rdparty/spirv-tools/source/opt/amd_ext_to_khr.cpp index e9b7f8613..ccedc0bc5 100644 --- a/3rdparty/spirv-tools/source/opt/amd_ext_to_khr.cpp +++ b/3rdparty/spirv-tools/source/opt/amd_ext_to_khr.cpp @@ -53,12 +53,6 @@ analysis::Type* GetUIntType(IRContext* ctx) { return ctx->get_type_mgr()->GetRegisteredType(&int_type); } -bool NotImplementedYet(IRContext*, Instruction*, - const std::vector&) { - assert(false && "Not implemented."); - return false; -} - // Returns a folding rule that replaces |op(a,b,c)| by |op(op(a,b),c)|, where // |op| is either min or max. |opcode| is the binary opcode in the GLSLstd450 // extended instruction set that corresponds to the trinary instruction being @@ -686,13 +680,13 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst, return true; } -// A folding rule that will replace the CubeFaceCoordAMD extended +// A folding rule that will replace the CubeFaceIndexAMD extended // instruction in the SPV_AMD_gcn_shader_ballot. Returns true if the folding // is successful. // // The instruction // -// %result = OpExtInst %v2float %1 CubeFaceCoordAMD %input +// %result = OpExtInst %float %1 CubeFaceIndexAMD %input // // with // @@ -705,7 +699,7 @@ bool ReplaceCubeFaceCoord(IRContext* ctx, Instruction* inst, // %is_z_neg = OpFOrdLessThan %bool %z %float_0 // %is_y_neg = OpFOrdLessThan %bool %y %float_0 // %is_x_neg = OpFOrdLessThan %bool %x %float_0 -// %amax_x_y = OpExtInst %float %n_1 FMax %ay %ax +// %amax_x_y = OpExtInst %float %n_1 FMax %ax %ay // %is_z_max = OpFOrdGreaterThanEqual %bool %az %amax_x_y // %y_gt_x = OpFOrdGreaterThanEqual %bool %ay %ax // %case_z = OpSelect %float %is_z_neg %float_5 %float4 @@ -800,6 +794,37 @@ bool ReplaceCubeFaceIndex(IRContext* ctx, Instruction* inst, return true; } +// A folding rule that will replace the TimeAMD extended instruction in the +// SPV_AMD_gcn_shader_ballot. It returns true if the folding is successful. +// It returns False, otherwise. +// +// The instruction +// +// %result = OpExtInst %uint64 %1 TimeAMD +// +// with +// +// %result = OpReadClockKHR %uint64 %uint_3 +// +// NOTE: TimeAMD uses subgroup scope (it is not a real time clock). +bool ReplaceTimeAMD(IRContext* ctx, Instruction* inst, + const std::vector&) { + InstructionBuilder ir_builder( + ctx, inst, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + ctx->AddExtension("SPV_KHR_shader_clock"); + ctx->AddCapability(SpvCapabilityShaderClockKHR); + + inst->SetOpcode(SpvOpReadClockKHR); + Instruction::OperandList args; + uint32_t subgroup_scope_id = ir_builder.GetUintConstantId(SpvScopeSubgroup); + args.push_back({SPV_OPERAND_TYPE_ID, {subgroup_scope_id}}); + inst->SetInOperands(std::move(args)); + ctx->UpdateDefUse(inst); + + return true; +} + class AmdExtFoldingRules : public FoldingRules { public: explicit AmdExtFoldingRules(IRContext* ctx) : FoldingRules(ctx) {} @@ -869,7 +894,7 @@ class AmdExtFoldingRules : public FoldingRules { ReplaceCubeFaceCoord); ext_rules_[{extension_id, CubeFaceIndexAMD}].push_back( ReplaceCubeFaceIndex); - ext_rules_[{extension_id, TimeAMD}].push_back(NotImplementedYet); + ext_rules_[{extension_id, TimeAMD}].push_back(ReplaceTimeAMD); } } }; diff --git a/3rdparty/spirv-tools/source/opt/basic_block.h b/3rdparty/spirv-tools/source/opt/basic_block.h index 0bab33714..6741a50f2 100644 --- a/3rdparty/spirv-tools/source/opt/basic_block.h +++ b/3rdparty/spirv-tools/source/opt/basic_block.h @@ -214,7 +214,7 @@ class BasicBlock { void KillAllInsts(bool killLabel); // Splits this basic block into two. Returns a new basic block with label - // |labelId| containing the instructions from |iter| onwards. Instructions + // |label_id| containing the instructions from |iter| onwards. Instructions // prior to |iter| remain in this basic block. The new block will be added // to the function immediately after the original block. BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id, diff --git a/3rdparty/spirv-tools/source/opt/block_merge_util.cpp b/3rdparty/spirv-tools/source/opt/block_merge_util.cpp index 263a069f9..14b5d3642 100644 --- a/3rdparty/spirv-tools/source/opt/block_merge_util.cpp +++ b/3rdparty/spirv-tools/source/opt/block_merge_util.cpp @@ -171,8 +171,17 @@ void MergeWithSuccessor(IRContext* context, Function* func, // flow declaration. context->KillInst(merge_inst); } else { + // Move OpLine/OpNoLine information to merge_inst. This solves + // the validation error that OpLine is placed between OpLoopMerge + // and OpBranchConditional. + auto terminator = bi->terminator(); + auto& vec = terminator->dbg_line_insts(); + auto& new_vec = merge_inst->dbg_line_insts(); + new_vec.insert(new_vec.end(), vec.begin(), vec.end()); + terminator->clear_dbg_line_insts(); + // Move the merge instruction to just before the terminator. - merge_inst->InsertBefore(bi->terminator()); + merge_inst->InsertBefore(terminator); } } context->ReplaceAllUsesWith(lab_id, bi->id()); diff --git a/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp index 2a2493fd6..d262a7eca 100644 --- a/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp +++ b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp @@ -265,7 +265,10 @@ ConstantFoldingRule FoldFPUnaryOp(UnaryScalarFoldingRule scalar_rule) { return nullptr; } - if (constants[0] == nullptr) { + const analysis::Constant* arg = + (inst->opcode() == SpvOpExtInst) ? constants[1] : constants[0]; + + if (arg == nullptr) { return nullptr; } @@ -273,7 +276,7 @@ ConstantFoldingRule FoldFPUnaryOp(UnaryScalarFoldingRule scalar_rule) { std::vector a_components; std::vector results_components; - a_components = constants[0]->GetVectorComponents(const_mgr); + a_components = arg->GetVectorComponents(const_mgr); // Fold each component of the vector. for (uint32_t i = 0; i < a_components.size(); ++i) { @@ -291,7 +294,7 @@ ConstantFoldingRule FoldFPUnaryOp(UnaryScalarFoldingRule scalar_rule) { } return const_mgr->GetConstant(vector_type, ids); } else { - return scalar_rule(result_type, constants[0], const_mgr); + return scalar_rule(result_type, arg, const_mgr); } }; } @@ -1070,6 +1073,60 @@ const analysis::Constant* FoldClamp3( return nullptr; } +UnaryScalarFoldingRule FoldFTranscendentalUnary(double (*fp)(double)) { + return + [fp](const analysis::Type* result_type, const analysis::Constant* a, + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { + assert(result_type != nullptr && a != nullptr); + const analysis::Float* float_type = a->type()->AsFloat(); + assert(float_type != nullptr); + assert(float_type == result_type->AsFloat()); + if (float_type->width() == 32) { + float fa = a->GetFloat(); + float res = static_cast(fp(fa)); + utils::FloatProxy result(res); + std::vector words = result.GetWords(); + return const_mgr->GetConstant(result_type, words); + } else if (float_type->width() == 64) { + double fa = a->GetDouble(); + double res = fp(fa); + utils::FloatProxy result(res); + std::vector words = result.GetWords(); + return const_mgr->GetConstant(result_type, words); + } + return nullptr; + }; +} + +BinaryScalarFoldingRule FoldFTranscendentalBinary(double (*fp)(double, + double)) { + return + [fp](const analysis::Type* result_type, const analysis::Constant* a, + const analysis::Constant* b, + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { + assert(result_type != nullptr && a != nullptr); + const analysis::Float* float_type = a->type()->AsFloat(); + assert(float_type != nullptr); + assert(float_type == result_type->AsFloat()); + assert(float_type == b->type()->AsFloat()); + if (float_type->width() == 32) { + float fa = a->GetFloat(); + float fb = b->GetFloat(); + float res = static_cast(fp(fa, fb)); + utils::FloatProxy result(res); + std::vector words = result.GetWords(); + return const_mgr->GetConstant(result_type, words); + } else if (float_type->width() == 64) { + double fa = a->GetDouble(); + double fb = b->GetDouble(); + double res = fp(fa, fb); + utils::FloatProxy result(res); + std::vector words = result.GetWords(); + return const_mgr->GetConstant(result_type, words); + } + return nullptr; + }; +} } // namespace void ConstantFoldingRules::AddFoldingRules() { @@ -1175,6 +1232,45 @@ void ConstantFoldingRules::AddFoldingRules() { FoldClamp2); ext_rules_[{ext_inst_glslstd450_id, GLSLstd450FClamp}].push_back( FoldClamp3); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Sin}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::sin))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Cos}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::cos))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Tan}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::tan))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Asin}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::asin))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Acos}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::acos))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Atan}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::atan))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::exp))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::log))); + +#ifdef __ANDROID__ + // Android NDK r15c tageting ABI 15 doesn't have full support for C++11 + // (no std::exp2/log2). ::exp2 is available from C99 but ::log2 isn't + // available up until ABI 18 so we use a shim + auto log2_shim = [](double v) -> double { return log(v) / log(2.0); }; + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp2}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(::exp2))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log2}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(log2_shim))); +#else + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Exp2}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::exp2))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Log2}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::log2))); +#endif + + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Sqrt}].push_back( + FoldFPUnaryOp(FoldFTranscendentalUnary(std::sqrt))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Atan2}].push_back( + FoldFPBinaryOp(FoldFTranscendentalBinary(std::atan2))); + ext_rules_[{ext_inst_glslstd450_id, GLSLstd450Pow}].push_back( + FoldFPBinaryOp(FoldFTranscendentalBinary(std::pow))); } } } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/constants.cpp b/3rdparty/spirv-tools/source/opt/constants.cpp index 0887ec2c6..6057356cb 100644 --- a/3rdparty/spirv-tools/source/opt/constants.cpp +++ b/3rdparty/spirv-tools/source/opt/constants.cpp @@ -156,7 +156,7 @@ Type* ConstantManager::GetType(const Instruction* inst) const { } std::vector ConstantManager::GetOperandConstants( - Instruction* inst) const { + const Instruction* inst) const { std::vector constants; for (uint32_t i = 0; i < inst->NumInOperands(); i++) { const Operand* operand = &inst->GetInOperand(i); diff --git a/3rdparty/spirv-tools/source/opt/constants.h b/3rdparty/spirv-tools/source/opt/constants.h index d65d28d60..680e69eac 100644 --- a/3rdparty/spirv-tools/source/opt/constants.h +++ b/3rdparty/spirv-tools/source/opt/constants.h @@ -597,7 +597,8 @@ class ConstantManager { // Returns a vector of constants representing each in operand. If an operand // is not constant its entry is nullptr. - std::vector GetOperandConstants(Instruction* inst) const; + std::vector GetOperandConstants( + const Instruction* inst) const; // Records a mapping between |inst| and the constant value generated by it. // It returns true if a new Constant was successfully mapped, false if |inst| diff --git a/3rdparty/spirv-tools/source/opt/convert_to_half_pass.cpp b/3rdparty/spirv-tools/source/opt/convert_to_half_pass.cpp index 4c02c73e2..5022e1b68 100644 --- a/3rdparty/spirv-tools/source/opt/convert_to_half_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/convert_to_half_pass.cpp @@ -42,7 +42,7 @@ bool ConvertToHalfPass::IsFloat(Instruction* inst, uint32_t width) { return Pass::IsFloat(ty_id, width); } -bool ConvertToHalfPass::IsRelaxed(Instruction* inst) { +bool ConvertToHalfPass::IsDecoratedRelaxed(Instruction* inst) { uint32_t r_id = inst->result_id(); for (auto r_inst : get_decoration_mgr()->GetDecorationsFor(r_id, false)) if (r_inst->opcode() == SpvOpDecorate && @@ -51,6 +51,12 @@ bool ConvertToHalfPass::IsRelaxed(Instruction* inst) { return false; } +bool ConvertToHalfPass::IsRelaxed(uint32_t id) { + return relaxed_ids_set_.count(id) > 0; +} + +void ConvertToHalfPass::AddRelaxed(uint32_t id) { relaxed_ids_set_.insert(id); } + analysis::Type* ConvertToHalfPass::FloatScalarType(uint32_t width) { analysis::Float float_ty(width); return context()->get_type_mgr()->GetRegisteredType(&float_ty); @@ -87,16 +93,19 @@ uint32_t ConvertToHalfPass::EquivFloatTypeId(uint32_t ty_id, uint32_t width) { } void ConvertToHalfPass::GenConvert(uint32_t* val_idp, uint32_t width, - InstructionBuilder* builder) { + Instruction* inst) { Instruction* val_inst = get_def_use_mgr()->GetDef(*val_idp); uint32_t ty_id = val_inst->type_id(); uint32_t nty_id = EquivFloatTypeId(ty_id, width); if (nty_id == ty_id) return; Instruction* cvt_inst; + InstructionBuilder builder( + context(), inst, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); if (val_inst->opcode() == SpvOpUndef) - cvt_inst = builder->AddNullaryOp(nty_id, SpvOpUndef); + cvt_inst = builder.AddNullaryOp(nty_id, SpvOpUndef); else - cvt_inst = builder->AddUnaryOp(nty_id, SpvOpFConvert, *val_idp); + cvt_inst = builder.AddUnaryOp(nty_id, SpvOpFConvert, *val_idp); *val_idp = cvt_inst->result_id(); } @@ -153,17 +162,15 @@ bool ConvertToHalfPass::GenHalfArith(Instruction* inst) { bool modified = false; // Convert all float32 based operands to float16 equivalent and change // instruction type to float16 equivalent. - InstructionBuilder builder( - context(), inst, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - inst->ForEachInId([&builder, &modified, this](uint32_t* idp) { + inst->ForEachInId([&inst, &modified, this](uint32_t* idp) { Instruction* op_inst = get_def_use_mgr()->GetDef(*idp); if (!IsFloat(op_inst, 32)) return; - GenConvert(idp, 16, &builder); + GenConvert(idp, 16, inst); modified = true; }); if (IsFloat(inst, 32)) { inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16)); + converted_ids_.insert(inst->result_id()); modified = true; } if (modified) get_def_use_mgr()->AnalyzeInstUse(inst); @@ -171,23 +178,10 @@ bool ConvertToHalfPass::GenHalfArith(Instruction* inst) { } bool ConvertToHalfPass::ProcessPhi(Instruction* inst) { - // Skip if not float32 - if (!IsFloat(inst, 32)) return false; - // Skip if no relaxed operands. - bool relaxed_found = false; - uint32_t ocnt = 0; - inst->ForEachInId([&ocnt, &relaxed_found, this](uint32_t* idp) { - if (ocnt % 2 == 0) { - Instruction* val_inst = get_def_use_mgr()->GetDef(*idp); - if (IsRelaxed(val_inst)) relaxed_found = true; - } - ++ocnt; - }); - if (!relaxed_found) return false; // Add float16 converts of any float32 operands and change type // of phi to float16 equivalent. Operand converts need to be added to // preceeding blocks. - ocnt = 0; + uint32_t ocnt = 0; uint32_t* prev_idp; inst->ForEachInId([&ocnt, &prev_idp, this](uint32_t* idp) { if (ocnt % 2 == 0) { @@ -203,65 +197,32 @@ bool ConvertToHalfPass::ProcessPhi(Instruction* inst) { insert_before->opcode() != SpvOpLoopMerge) ++insert_before; } - InstructionBuilder builder(context(), &*insert_before, - IRContext::kAnalysisDefUse | - IRContext::kAnalysisInstrToBlockMapping); - GenConvert(prev_idp, 16, &builder); + GenConvert(prev_idp, 16, &*insert_before); } } ++ocnt; }); inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16)); get_def_use_mgr()->AnalyzeInstUse(inst); + converted_ids_.insert(inst->result_id()); return true; } -bool ConvertToHalfPass::ProcessExtract(Instruction* inst) { - bool modified = false; - uint32_t comp_id = inst->GetSingleWordInOperand(0); - Instruction* comp_inst = get_def_use_mgr()->GetDef(comp_id); - // If extract is relaxed float32 based type and the composite is a relaxed - // float32 based type, convert it to float16 equivalent. This is slightly - // aggressive and pushes any likely conversion to apply to the whole - // composite rather than apply to each extracted component later. This - // can be a win if the platform can convert the entire composite in the same - // time as one component. It risks converting components that may not be - // used, although empirical data on a large set of real-world shaders seems - // to suggest this is not common and the composite convert is the best choice. - if (IsFloat(inst, 32) && IsRelaxed(inst) && IsFloat(comp_inst, 32) && - IsRelaxed(comp_inst)) { - InstructionBuilder builder( - context(), inst, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - GenConvert(&comp_id, 16, &builder); - inst->SetInOperand(0, {comp_id}); - comp_inst = get_def_use_mgr()->GetDef(comp_id); - modified = true; - } - // If the composite is a float16 based type, make sure the type of the - // extract agrees. - if (IsFloat(comp_inst, 16) && !IsFloat(inst, 16)) { - inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16)); - modified = true; - } - if (modified) get_def_use_mgr()->AnalyzeInstUse(inst); - return modified; -} - bool ConvertToHalfPass::ProcessConvert(Instruction* inst) { // If float32 and relaxed, change to float16 convert - if (IsFloat(inst, 32) && IsRelaxed(inst)) { + if (IsFloat(inst, 32) && IsRelaxed(inst->result_id())) { inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16)); get_def_use_mgr()->AnalyzeInstUse(inst); + converted_ids_.insert(inst->result_id()); } - // If operand and result types are the same, replace result with operand - // and change convert to copy to keep validator happy; DCE will clean it up + // If operand and result types are the same, change FConvert to CopyObject to + // keep validator happy; simplification and DCE will clean it up + // One way this can happen is if an FConvert generated during this pass + // (likely by ProcessPhi) is later encountered here and its operand has been + // changed to half. uint32_t val_id = inst->GetSingleWordInOperand(0); Instruction* val_inst = get_def_use_mgr()->GetDef(val_id); - if (inst->type_id() == val_inst->type_id()) { - context()->ReplaceAllUsesWith(inst->result_id(), val_id); - inst->SetOpcode(SpvOpCopyObject); - } + if (inst->type_id() == val_inst->type_id()) inst->SetOpcode(SpvOpCopyObject); return true; // modified } @@ -270,12 +231,8 @@ bool ConvertToHalfPass::ProcessImageRef(Instruction* inst) { // If image reference, only need to convert dref args back to float32 if (dref_image_ops_.count(inst->opcode()) != 0) { uint32_t dref_id = inst->GetSingleWordInOperand(kImageSampleDrefIdInIdx); - Instruction* dref_inst = get_def_use_mgr()->GetDef(dref_id); - if (IsFloat(dref_inst, 16) && IsRelaxed(dref_inst)) { - InstructionBuilder builder( - context(), inst, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - GenConvert(&dref_id, 32, &builder); + if (converted_ids_.count(dref_id) > 0) { + GenConvert(&dref_id, 32, inst); inst->SetInOperand(kImageSampleDrefIdInIdx, {dref_id}); get_def_use_mgr()->AnalyzeInstUse(inst); modified = true; @@ -288,32 +245,24 @@ bool ConvertToHalfPass::ProcessDefault(Instruction* inst) { bool modified = false; // If non-relaxed instruction has changed operands, need to convert // them back to float32 - InstructionBuilder builder( - context(), inst, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - inst->ForEachInId([&builder, &modified, this](uint32_t* idp) { - Instruction* op_inst = get_def_use_mgr()->GetDef(*idp); - if (!IsFloat(op_inst, 16)) return; - if (!IsRelaxed(op_inst)) return; + inst->ForEachInId([&inst, &modified, this](uint32_t* idp) { + if (converted_ids_.count(*idp) == 0) return; uint32_t old_id = *idp; - GenConvert(idp, 32, &builder); + GenConvert(idp, 32, inst); if (*idp != old_id) modified = true; }); if (modified) get_def_use_mgr()->AnalyzeInstUse(inst); return modified; } -bool ConvertToHalfPass::GenHalfCode(Instruction* inst) { +bool ConvertToHalfPass::GenHalfInst(Instruction* inst) { bool modified = false; // Remember id for later deletion of RelaxedPrecision decoration - bool inst_relaxed = IsRelaxed(inst); - if (inst_relaxed) relaxed_ids_.push_back(inst->result_id()); + bool inst_relaxed = IsRelaxed(inst->result_id()); if (IsArithmetic(inst) && inst_relaxed) modified = GenHalfArith(inst); - else if (inst->opcode() == SpvOpPhi) + else if (inst->opcode() == SpvOpPhi && inst_relaxed) modified = ProcessPhi(inst); - else if (inst->opcode() == SpvOpCompositeExtract) - modified = ProcessExtract(inst); else if (inst->opcode() == SpvOpFConvert) modified = ProcessConvert(inst); else if (image_ops_.count(inst->opcode()) != 0) @@ -323,13 +272,62 @@ bool ConvertToHalfPass::GenHalfCode(Instruction* inst) { return modified; } +bool ConvertToHalfPass::CloseRelaxInst(Instruction* inst) { + if (inst->result_id() == 0) return false; + if (IsRelaxed(inst->result_id())) return false; + if (!IsFloat(inst, 32)) return false; + if (IsDecoratedRelaxed(inst)) { + AddRelaxed(inst->result_id()); + return true; + } + if (closure_ops_.count(inst->opcode()) == 0) return false; + // Can relax if all float operands are relaxed + bool relax = true; + inst->ForEachInId([&relax, this](uint32_t* idp) { + Instruction* op_inst = get_def_use_mgr()->GetDef(*idp); + if (!IsFloat(op_inst, 32)) return; + if (!IsRelaxed(*idp)) relax = false; + }); + if (relax) { + AddRelaxed(inst->result_id()); + return true; + } + // Can relax if all uses are relaxed + relax = true; + get_def_use_mgr()->ForEachUser(inst, [&relax, this](Instruction* uinst) { + if (uinst->result_id() == 0 || !IsFloat(uinst, 32) || + (!IsDecoratedRelaxed(uinst) && !IsRelaxed(uinst->result_id()))) { + relax = false; + return; + } + }); + if (relax) { + AddRelaxed(inst->result_id()); + return true; + } + return false; +} + bool ConvertToHalfPass::ProcessFunction(Function* func) { + // Do a closure of Relaxed on composite and phi instructions + bool changed = true; + while (changed) { + changed = false; + cfg()->ForEachBlockInReversePostOrder( + func->entry().get(), [&changed, this](BasicBlock* bb) { + for (auto ii = bb->begin(); ii != bb->end(); ++ii) + changed |= CloseRelaxInst(&*ii); + }); + } + // Do convert of relaxed instructions to half precision bool modified = false; cfg()->ForEachBlockInReversePostOrder( func->entry().get(), [&modified, this](BasicBlock* bb) { for (auto ii = bb->begin(); ii != bb->end(); ++ii) - modified |= GenHalfCode(&*ii); + modified |= GenHalfInst(&*ii); }); + // Replace invalid converts of matrix into equivalent vector extracts, + // converts and finally a composite construct cfg()->ForEachBlockInReversePostOrder( func->entry().get(), [&modified, this](BasicBlock* bb) { for (auto ii = bb->begin(); ii != bb->end(); ++ii) @@ -346,7 +344,7 @@ Pass::Status ConvertToHalfPass::ProcessImpl() { // If modified, make sure module has Float16 capability if (modified) context()->AddCapability(SpvCapabilityFloat16); // Remove all RelaxedPrecision decorations from instructions and globals - for (auto c_id : relaxed_ids_) RemoveRelaxedDecoration(c_id); + for (auto c_id : relaxed_ids_set_) RemoveRelaxedDecoration(c_id); for (auto& val : get_module()->types_values()) { uint32_t v_id = val.result_id(); if (v_id != 0) RemoveRelaxedDecoration(v_id); @@ -366,6 +364,7 @@ void ConvertToHalfPass::Initialize() { SpvOpVectorShuffle, SpvOpCompositeConstruct, SpvOpCompositeInsert, + SpvOpCompositeExtract, SpvOpCopyObject, SpvOpTranspose, SpvOpConvertSToF, @@ -453,7 +452,19 @@ void ConvertToHalfPass::Initialize() { SpvOpImageSparseSampleProjDrefExplicitLod, SpvOpImageSparseDrefGather, }; - relaxed_ids_.clear(); + closure_ops_ = { + SpvOpVectorExtractDynamic, + SpvOpVectorInsertDynamic, + SpvOpVectorShuffle, + SpvOpCompositeConstruct, + SpvOpCompositeInsert, + SpvOpCompositeExtract, + SpvOpCopyObject, + SpvOpTranspose, + SpvOpPhi, + }; + relaxed_ids_set_.clear(); + converted_ids_.clear(); } } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/convert_to_half_pass.h b/3rdparty/spirv-tools/source/opt/convert_to_half_pass.h index 522584861..143aebfa2 100644 --- a/3rdparty/spirv-tools/source/opt/convert_to_half_pass.h +++ b/3rdparty/spirv-tools/source/opt/convert_to_half_pass.h @@ -38,7 +38,8 @@ class ConvertToHalfPass : public Pass { const char* name() const override { return "convert-to-half-pass"; } private: - // Return true if |inst| is an arithmetic op that can be of type float16 + // Return true if |inst| is an arithmetic, composite or phi op that can be + // of type float16 bool IsArithmetic(Instruction* inst); // Return true if |inst| returns scalar, vector or matrix type with base @@ -46,7 +47,13 @@ class ConvertToHalfPass : public Pass { bool IsFloat(Instruction* inst, uint32_t width); // Return true if |inst| is decorated with RelaxedPrecision - bool IsRelaxed(Instruction* inst); + bool IsDecoratedRelaxed(Instruction* inst); + + // Return true if |id| has been added to the relaxed id set + bool IsRelaxed(uint32_t id); + + // Add |id| to the relaxed id set + void AddRelaxed(uint32_t id); // Return type id for float with |width| analysis::Type* FloatScalarType(uint32_t width); @@ -64,19 +71,23 @@ class ConvertToHalfPass : public Pass { // Append instructions to builder to convert value |*val_idp| to type // |ty_id| but with |width|. Set |*val_idp| to the new id. - void GenConvert(uint32_t* val_idp, uint32_t width, - InstructionBuilder* builder); + void GenConvert(uint32_t* val_idp, uint32_t width, Instruction* inst); // Remove RelaxedPrecision decoration of |id|. void RemoveRelaxedDecoration(uint32_t id); + // Add |inst| to relaxed instruction set if warranted. Specifically, if + // it is float32 and either decorated relaxed or a composite or phi + // instruction where all operands are relaxed or all uses are relaxed. + bool CloseRelaxInst(Instruction* inst); + // If |inst| is an arithmetic, phi, extract or convert instruction of float32 // base type and decorated with RelaxedPrecision, change it to the equivalent // float16 based type instruction. Specifically, insert instructions to // convert all operands to float16 (if needed) and change its type to the // equivalent float16 type. Otherwise, insert instructions to convert its // operands back to their original types, if needed. - bool GenHalfCode(Instruction* inst); + bool GenHalfInst(Instruction* inst); // Gen code for relaxed arithmetic |inst| bool GenHalfArith(Instruction* inst); @@ -84,9 +95,6 @@ class ConvertToHalfPass : public Pass { // Gen code for relaxed phi |inst| bool ProcessPhi(Instruction* inst); - // Gen code for relaxed extract |inst| - bool ProcessExtract(Instruction* inst); - // Gen code for relaxed convert |inst| bool ProcessConvert(Instruction* inst); @@ -98,11 +106,11 @@ class ConvertToHalfPass : public Pass { // If |inst| is an FConvert of a matrix type, decompose it to a series // of vector extracts, converts and inserts into an Undef. These are - // generated by GenHalfCode because they are easier to manipulate, but are + // generated by GenHalfInst because they are easier to manipulate, but are // invalid so we need to clean them up. bool MatConvertCleanup(Instruction* inst); - // Call GenHalfCode on every instruction in |func|. + // Call GenHalfInst on every instruction in |func|. // If code is generated for an instruction, replace the instruction // with the new instructions that are generated. bool ProcessFunction(Function* func); @@ -124,8 +132,14 @@ class ConvertToHalfPass : public Pass { // Set of dref sample operations std::unordered_set dref_image_ops_; + // Set of dref sample operations + std::unordered_set closure_ops_; + + // Set of ids of all relaxed instructions + std::unordered_set relaxed_ids_set_; + // Ids of all converted instructions - std::vector relaxed_ids_; + std::unordered_set converted_ids_; }; } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp index 8425a398c..16d9fd563 100644 --- a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp @@ -167,7 +167,6 @@ bool DeadBranchElimPass::MarkLiveBlocks( } if (simplify) { - modified = true; conditions_to_simplify.push_back({block, live_lab_id}); stack.push_back(GetParentBlock(live_lab_id)); } else { @@ -179,24 +178,29 @@ bool DeadBranchElimPass::MarkLiveBlocks( } } - // Traverse |conditions_to_simplify in reverse order. This is done so that we - // simplify nested constructs before simplifying the constructs that contain - // them. + // Traverse |conditions_to_simplify| in reverse order. This is done so that + // we simplify nested constructs before simplifying the constructs that + // contain them. for (auto b = conditions_to_simplify.rbegin(); b != conditions_to_simplify.rend(); ++b) { - SimplifyBranch(b->first, b->second); + modified |= SimplifyBranch(b->first, b->second); } return modified; } -void DeadBranchElimPass::SimplifyBranch(BasicBlock* block, +bool DeadBranchElimPass::SimplifyBranch(BasicBlock* block, uint32_t live_lab_id) { Instruction* merge_inst = block->GetMergeInst(); Instruction* terminator = block->terminator(); if (merge_inst && merge_inst->opcode() == SpvOpSelectionMerge) { if (merge_inst->NextNode()->opcode() == SpvOpSwitch && SwitchHasNestedBreak(block->id())) { + if (terminator->NumInOperands() == 2) { + // We cannot remove the branch, and it already has a single case, so no + // work to do. + return false; + } // We have to keep the switch because it has a nest break, so we // remove all cases except for the live one. Instruction::OperandList new_operands; @@ -231,6 +235,7 @@ void DeadBranchElimPass::SimplifyBranch(BasicBlock* block, AddBranch(live_lab_id, block); context()->KillInst(terminator); } + return true; } void DeadBranchElimPass::MarkUnreachableStructuredTargets( @@ -643,7 +648,8 @@ bool DeadBranchElimPass::SwitchHasNestedBreak(uint32_t switch_header_id) { if (bb->id() == switch_header_id) { return true; } - return (cfg_analysis->ContainingConstruct(inst) == switch_header_id); + return (cfg_analysis->ContainingConstruct(inst) == switch_header_id && + bb->GetMergeInst() == nullptr); }); } diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h index a50933fdb..7841bc470 100644 --- a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h @@ -159,14 +159,15 @@ class DeadBranchElimPass : public MemPass { std::unordered_set* blocks_with_back_edges); // Returns true if there is a brach to the merge node of the selection - // construct |switch_header_id| that is inside a nested selection construct. + // construct |switch_header_id| that is inside a nested selection construct or + // in the header of the nested selection construct. bool SwitchHasNestedBreak(uint32_t switch_header_id); - // Replaces the terminator of |block| with a branch to |live_lab_id|. The - // merge instruction is deleted or moved as needed to maintain structured - // control flow. Assumes that the StructuredCFGAnalysis is valid for the - // constructs containing |block|. - void SimplifyBranch(BasicBlock* block, uint32_t live_lab_id); + // Return true of the terminator of |block| is successfully replaced with a + // branch to |live_lab_id|. The merge instruction is deleted or moved as + // needed to maintain structured control flow. Assumes that the + // StructuredCFGAnalysis is valid for the constructs containing |block|. + bool SimplifyBranch(BasicBlock* block, uint32_t live_lab_id); }; } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/folding_rules.cpp b/3rdparty/spirv-tools/source/opt/folding_rules.cpp index d576b090a..1c8cdc893 100644 --- a/3rdparty/spirv-tools/source/opt/folding_rules.cpp +++ b/3rdparty/spirv-tools/source/opt/folding_rules.cpp @@ -1501,60 +1501,64 @@ FoldingRule CompositeConstructFeedingExtract() { }; } -FoldingRule CompositeExtractFeedingConstruct() { - // If the OpCompositeConstruct is simply putting back together elements that - // where extracted from the same souce, we can simlpy reuse the source. - // - // This is a common code pattern because of the way that scalar replacement - // works. - return [](IRContext* context, Instruction* inst, - const std::vector&) { - assert(inst->opcode() == SpvOpCompositeConstruct && - "Wrong opcode. Should be OpCompositeConstruct."); - analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); - uint32_t original_id = 0; +// If the OpCompositeConstruct is simply putting back together elements that +// where extracted from the same source, we can simply reuse the source. +// +// This is a common code pattern because of the way that scalar replacement +// works. +bool CompositeExtractFeedingConstruct( + IRContext* context, Instruction* inst, + const std::vector&) { + assert(inst->opcode() == SpvOpCompositeConstruct && + "Wrong opcode. Should be OpCompositeConstruct."); + analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + uint32_t original_id = 0; - // Check each element to make sure they are: - // - extractions - // - extracting the same position they are inserting - // - all extract from the same id. - for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { - uint32_t element_id = inst->GetSingleWordInOperand(i); - Instruction* element_inst = def_use_mgr->GetDef(element_id); + if (inst->NumInOperands() == 0) { + // The struct being constructed has no members. + return false; + } - if (element_inst->opcode() != SpvOpCompositeExtract) { - return false; - } + // Check each element to make sure they are: + // - extractions + // - extracting the same position they are inserting + // - all extract from the same id. + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + const uint32_t element_id = inst->GetSingleWordInOperand(i); + Instruction* element_inst = def_use_mgr->GetDef(element_id); - if (element_inst->NumInOperands() != 2) { - return false; - } - - if (element_inst->GetSingleWordInOperand(1) != i) { - return false; - } - - if (i == 0) { - original_id = - element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); - } else if (original_id != element_inst->GetSingleWordInOperand( - kExtractCompositeIdInIdx)) { - return false; - } - } - - // The last check it to see that the object being extracted from is the - // correct type. - Instruction* original_inst = def_use_mgr->GetDef(original_id); - if (original_inst->type_id() != inst->type_id()) { + if (element_inst->opcode() != SpvOpCompositeExtract) { return false; } - // Simplify by using the original object. - inst->SetOpcode(SpvOpCopyObject); - inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}}); - return true; - }; + if (element_inst->NumInOperands() != 2) { + return false; + } + + if (element_inst->GetSingleWordInOperand(1) != i) { + return false; + } + + if (i == 0) { + original_id = + element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + } else if (original_id != + element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx)) { + return false; + } + } + + // The last check it to see that the object being extracted from is the + // correct type. + Instruction* original_inst = def_use_mgr->GetDef(original_id); + if (original_inst->type_id() != inst->type_id()) { + return false; + } + + // Simplify by using the original object. + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}}); + return true; } FoldingRule InsertFeedingExtract() { @@ -2419,7 +2423,7 @@ void FoldingRules::AddFoldingRules() { // Note that the order in which rules are added to the list matters. If a rule // applies to the instruction, the rest of the rules will not be attempted. // Take that into consideration. - rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct()); + rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct); rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract()); rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract()); diff --git a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h index 51d771277..447871bfc 100644 --- a/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h +++ b/3rdparty/spirv-tools/source/opt/inst_bindless_check_pass.h @@ -28,18 +28,19 @@ namespace opt { // external design may change as the layer evolves. class InstBindlessCheckPass : public InstrumentPass { public: - // For test harness only - InstBindlessCheckPass() - : InstrumentPass(7, 23, kInstValidationIdBindless, 1), - input_length_enabled_(true), - input_init_enabled_(true) {} - // For all other interfaces + // Deprecated interface InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, bool input_length_enable, bool input_init_enable, uint32_t version) : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, version), input_length_enabled_(input_length_enable), input_init_enabled_(input_init_enable) {} + // Preferred Interface + InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id, + bool input_length_enable, bool input_init_enable) + : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless), + input_length_enabled_(input_length_enable), + input_init_enabled_(input_init_enable) {} ~InstBindlessCheckPass() override = default; diff --git a/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.h b/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.h index 9ad3528ed..67ffcc392 100644 --- a/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.h +++ b/3rdparty/spirv-tools/source/opt/inst_buff_addr_check_pass.h @@ -28,13 +28,13 @@ namespace opt { // external design of this class may change as the layer evolves. class InstBuffAddrCheckPass : public InstrumentPass { public: - // For test harness only - InstBuffAddrCheckPass() - : InstrumentPass(7, 23, kInstValidationIdBuffAddr, 1) {} - // For all other interfaces + // Deprecated interface InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id, uint32_t version) : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr, version) {} + // Preferred interface + InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id) + : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr) {} ~InstBuffAddrCheckPass() override = default; diff --git a/3rdparty/spirv-tools/source/opt/instruction.h b/3rdparty/spirv-tools/source/opt/instruction.h index d1c4ce148..322e0aac0 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.h +++ b/3rdparty/spirv-tools/source/opt/instruction.h @@ -172,6 +172,9 @@ class Instruction : public utils::IntrusiveNodeBase { return dbg_line_insts_; } + // Clear line-related debug instructions attached to this instruction. + void clear_dbg_line_insts() { dbg_line_insts_.clear(); } + // 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); diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp index 2a3e7f06e..b1a6edb9d 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -185,32 +185,12 @@ void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx, GetUintId(), SpvOpCompositeExtract, load_id, 1); Instruction* z_inst = builder->AddIdLiteralOp( GetUintId(), SpvOpCompositeExtract, load_id, 2); - if (version_ == 1) { - // For version 1 format, as a stopgap, pack uvec3 into first word: - // x << 21 | y << 10 | z. Second word is unused. (DEPRECATED) - Instruction* x_shft_inst = builder->AddBinaryOp( - GetUintId(), SpvOpShiftLeftLogical, x_inst->result_id(), - builder->GetUintConstantId(21)); - Instruction* y_shft_inst = builder->AddBinaryOp( - GetUintId(), SpvOpShiftLeftLogical, y_inst->result_id(), - builder->GetUintConstantId(10)); - Instruction* x_or_y_inst = builder->AddBinaryOp( - GetUintId(), SpvOpBitwiseOr, x_shft_inst->result_id(), - y_shft_inst->result_id()); - Instruction* x_or_y_or_z_inst = - builder->AddBinaryOp(GetUintId(), SpvOpBitwiseOr, - x_or_y_inst->result_id(), z_inst->result_id()); - GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationId, - x_or_y_or_z_inst->result_id(), builder); - } else { - // For version 2 format, write all three words - GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdX, - x_inst->result_id(), builder); - GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdY, - y_inst->result_id(), builder); - GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdZ, - z_inst->result_id(), builder); - } + GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdX, + x_inst->result_id(), builder); + GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdY, + y_inst->result_id(), builder); + GenDebugOutputFieldCode(base_offset_id, kInstCompOutGlobalInvocationIdZ, + z_inst->result_id(), builder); } break; case SpvExecutionModelGeometry: { // Load and store PrimitiveId and InvocationId. @@ -231,30 +211,23 @@ void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx, kInstTessCtlOutPrimitiveId, base_offset_id, builder); } break; case SpvExecutionModelTessellationEvaluation: { - if (version_ == 1) { - // For format version 1, load and store InvocationId. - GenBuiltinOutputCode( - context()->GetBuiltinInputVarId(SpvBuiltInInvocationId), - kInstTessOutInvocationId, base_offset_id, builder); - } else { - // For format version 2, load and store PrimitiveId and TessCoord.uv - GenBuiltinOutputCode( - context()->GetBuiltinInputVarId(SpvBuiltInPrimitiveId), - kInstTessEvalOutPrimitiveId, base_offset_id, builder); - uint32_t load_id = GenVarLoad( - context()->GetBuiltinInputVarId(SpvBuiltInTessCoord), builder); - Instruction* uvec3_cast_inst = - builder->AddUnaryOp(GetVec3UintId(), SpvOpBitcast, load_id); - uint32_t uvec3_cast_id = uvec3_cast_inst->result_id(); - Instruction* u_inst = builder->AddIdLiteralOp( - GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 0); - Instruction* v_inst = builder->AddIdLiteralOp( - GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 1); - GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU, - u_inst->result_id(), builder); - GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV, - v_inst->result_id(), builder); - } + // Load and store PrimitiveId and TessCoord.uv + GenBuiltinOutputCode( + context()->GetBuiltinInputVarId(SpvBuiltInPrimitiveId), + kInstTessEvalOutPrimitiveId, base_offset_id, builder); + uint32_t load_id = GenVarLoad( + context()->GetBuiltinInputVarId(SpvBuiltInTessCoord), builder); + Instruction* uvec3_cast_inst = + builder->AddUnaryOp(GetVec3UintId(), SpvOpBitcast, load_id); + uint32_t uvec3_cast_id = uvec3_cast_inst->result_id(); + Instruction* u_inst = builder->AddIdLiteralOp( + GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 0); + Instruction* v_inst = builder->AddIdLiteralOp( + GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 1); + GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU, + u_inst->result_id(), builder); + GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV, + v_inst->result_id(), builder); } break; case SpvExecutionModelFragment: { // Load FragCoord and convert to Uint @@ -671,8 +644,7 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx, context(), &*new_blk_ptr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); // Gen test if debug output buffer size will not be exceeded. - uint32_t val_spec_offset = - (version_ == 1) ? kInstStageOutCnt : kInst2StageOutCnt; + uint32_t val_spec_offset = kInstStageOutCnt; uint32_t obuf_record_sz = val_spec_offset + val_spec_param_cnt; uint32_t buf_id = GetOutputBufferId(); uint32_t buf_uint_ptr_id = GetOutputBufferPtrId(); @@ -892,6 +864,14 @@ bool InstrumentPass::InstProcessCallTreeFromRoots(InstProcessFunction& pfn, } bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) { + // Check that format version 2 requested + if (version_ != 2u) { + if (consumer()) { + std::string message = "Unsupported instrumentation format requested"; + consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); + } + return false; + } // Make sure all entry points have the same execution model. Do not // instrument if they do not. // TODO(greg-lunarg): Handle mixed stages. Technically, a shader module @@ -905,12 +885,17 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) { for (auto& e : get_module()->entry_points()) { if (ecnt == 0) stage = e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx); - else if (e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) != stage) + else if (e.GetSingleWordInOperand(kEntryPointExecutionModelInIdx) != + stage) { + if (consumer()) { + std::string message = "Mixed stage shader module not supported"; + consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); + } return false; + } ++ecnt; } - // Only supporting vertex, fragment and compute shaders at the moment. - // TODO(greg-lunarg): Handle all stages. + // Check for supported stages if (stage != SpvExecutionModelVertex && stage != SpvExecutionModelFragment && stage != SpvExecutionModelGeometry && stage != SpvExecutionModelGLCompute && @@ -920,8 +905,14 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) { stage != SpvExecutionModelIntersectionNV && stage != SpvExecutionModelAnyHitNV && stage != SpvExecutionModelClosestHitNV && - stage != SpvExecutionModelMissNV && stage != SpvExecutionModelCallableNV) + stage != SpvExecutionModelMissNV && + stage != SpvExecutionModelCallableNV) { + if (consumer()) { + std::string message = "Stage not supported by instrumentation"; + consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str()); + } return false; + } // Add together the roots of all entry points std::queue roots; for (auto& e : get_module()->entry_points()) { @@ -997,6 +988,10 @@ void InstrumentPass::InitializeInstrument() { (void)i; ++module_offset; } + for (auto& i : module->ext_inst_debuginfo()) { + (void)i; + ++module_offset; + } for (auto& i : module->annotations()) { (void)i; ++module_offset; diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.h b/3rdparty/spirv-tools/source/opt/instrument_pass.h index ead3b7306..02568fb7a 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.h +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.h @@ -81,7 +81,16 @@ 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 with format |version|. + // into debug output records. + InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id) + : Pass(), + desc_set_(desc_set), + shader_id_(shader_id), + validation_id_(validation_id), + version_(2u) {} + // 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 with format |version|. Deprecated. InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id, uint32_t version) : Pass(), diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h index 45bf1290e..723a2bbb6 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.h +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -187,6 +187,14 @@ class IRContext { inline IteratorRange debugs3(); inline IteratorRange debugs3() const; + // Iterators for debug info instructions (excluding OpLine & OpNoLine) + // contained in this module. These are OpExtInst for OpenCL.DebugInfo.100 + // or DebugInfo extension placed between section 9 and 10. + inline Module::inst_iterator ext_inst_debuginfo_begin(); + inline Module::inst_iterator ext_inst_debuginfo_end(); + inline IteratorRange ext_inst_debuginfo(); + inline IteratorRange ext_inst_debuginfo() const; + // Add |capability| to the module, if it is not already enabled. inline void AddCapability(SpvCapability capability); @@ -215,6 +223,8 @@ class IRContext { // Appends a debug 3 instruction (OpModuleProcessed) to this module. // This is due to decision by the SPIR Working Group, pending publication. inline void AddDebug3Inst(std::unique_ptr&& d); + // Appends a OpExtInst for DebugInfo to this module. + inline void AddExtInstDebugInfo(std::unique_ptr&& d); // Appends an annotation instruction to this module. inline void AddAnnotationInst(std::unique_ptr&& a); // Appends a type-declaration instruction to this module. @@ -925,6 +935,23 @@ IteratorRange IRContext::debugs3() const { return ((const Module*)module_.get())->debugs3(); } +Module::inst_iterator IRContext::ext_inst_debuginfo_begin() { + return module()->ext_inst_debuginfo_begin(); +} + +Module::inst_iterator IRContext::ext_inst_debuginfo_end() { + return module()->ext_inst_debuginfo_end(); +} + +IteratorRange IRContext::ext_inst_debuginfo() { + return module()->ext_inst_debuginfo(); +} + +IteratorRange IRContext::ext_inst_debuginfo() + const { + return ((const Module*)module_.get())->ext_inst_debuginfo(); +} + void IRContext::AddCapability(SpvCapability capability) { if (!get_feature_mgr()->HasCapability(capability)) { std::unique_ptr capability_inst(new Instruction( @@ -1018,6 +1045,10 @@ void IRContext::AddDebug3Inst(std::unique_ptr&& d) { module()->AddDebug3Inst(std::move(d)); } +void IRContext::AddExtInstDebugInfo(std::unique_ptr&& d) { + module()->AddExtInstDebugInfo(std::move(d)); +} + void IRContext::AddAnnotationInst(std::unique_ptr&& a) { if (AreAnalysesValid(kAnalysisDecorations)) { get_decoration_mgr()->AddDecoration(a.get()); diff --git a/3rdparty/spirv-tools/source/opt/ir_loader.cpp b/3rdparty/spirv-tools/source/opt/ir_loader.cpp index c68b3e26b..836012f17 100644 --- a/3rdparty/spirv-tools/source/opt/ir_loader.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_loader.cpp @@ -16,6 +16,9 @@ #include +#include "DebugInfo.h" +#include "OpenCLDebugInfo100.h" +#include "source/ext_inst.h" #include "source/opt/log.h" #include "source/opt/reflect.h" #include "source/util/make_unique.h" @@ -113,11 +116,17 @@ 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 == SpvOpUndef || + (opcode == SpvOpExtInst && + spvExtInstIsNonSemantic(inst->ext_inst_type))) { module_->AddGlobalValue(std::move(spv_inst)); + } else if (opcode == SpvOpExtInst && + spvExtInstIsDebugInfo(inst->ext_inst_type)) { + module_->AddExtInstDebugInfo(std::move(spv_inst)); } else { Errorf(consumer_, src, loc, - "Unhandled inst type (opcode: %d) found outside function definition.", + "Unhandled inst type (opcode: %d) found outside function " + "definition.", opcode); return false; } @@ -132,6 +141,39 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { } function_->AddParameter(std::move(spv_inst)); } else { + if (opcode == SpvOpExtInst && + spvExtInstIsDebugInfo(inst->ext_inst_type)) { + const uint32_t ext_inst_index = inst->words[4]; + if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) { + const OpenCLDebugInfo100Instructions ext_inst_key = + OpenCLDebugInfo100Instructions(ext_inst_index); + if (ext_inst_key != OpenCLDebugInfo100DebugScope && + ext_inst_key != OpenCLDebugInfo100DebugNoScope && + ext_inst_key != OpenCLDebugInfo100DebugDeclare && + ext_inst_key != OpenCLDebugInfo100DebugValue) { + Errorf(consumer_, src, loc, + "Debug info extension instruction other than DebugScope, " + "DebugNoScope, DebugDeclare, and DebugValue found inside " + "function", + opcode); + return false; + } + } else { + const DebugInfoInstructions ext_inst_key = + DebugInfoInstructions(ext_inst_index); + if (ext_inst_key != DebugInfoDebugScope && + ext_inst_key != DebugInfoDebugNoScope && + ext_inst_key != DebugInfoDebugDeclare && + ext_inst_key != DebugInfoDebugValue) { + Errorf(consumer_, src, loc, + "Debug info extension instruction other than DebugScope, " + "DebugNoScope, DebugDeclare, and DebugValue found inside " + "function", + opcode); + return false; + } + } + } block_->AddInstruction(std::move(spv_inst)); } } diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp index 18c49f5a0..bbac4bb6c 100644 --- a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp @@ -69,6 +69,32 @@ Pass::Status MergeReturnPass::Process() { return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } +void MergeReturnPass::GenerateState(BasicBlock* block) { + if (Instruction* mergeInst = block->GetMergeInst()) { + if (mergeInst->opcode() == SpvOpLoopMerge) { + // If new loop, break to this loop merge block + state_.emplace_back(mergeInst, mergeInst); + } else { + auto branchInst = mergeInst->NextNode(); + if (branchInst->opcode() == SpvOpSwitch) { + // If switch inside of loop, break to innermost loop merge block. + // Otherwise need to break to this switch merge block. + auto lastMergeInst = state_.back().BreakMergeInst(); + if (lastMergeInst && lastMergeInst->opcode() == SpvOpLoopMerge) + state_.emplace_back(lastMergeInst, mergeInst); + else + state_.emplace_back(mergeInst, mergeInst); + } else { + // If branch conditional inside loop, always break to innermost + // loop merge block. If branch conditional inside switch, break to + // innermost switch merge block. + auto lastMergeInst = state_.back().BreakMergeInst(); + state_.emplace_back(lastMergeInst, mergeInst); + } + } + } +} + bool MergeReturnPass::ProcessStructured( Function* function, const std::vector& return_blocks) { if (HasNontrivialUnreachableBlocks(function)) { @@ -82,7 +108,7 @@ bool MergeReturnPass::ProcessStructured( } RecordImmediateDominators(function); - AddDummyLoopAroundFunction(); + AddDummySwitchAroundFunction(); std::list order; cfg()->ComputeStructuredOrder(function, &*function->begin(), &order); @@ -103,12 +129,8 @@ bool MergeReturnPass::ProcessStructured( ProcessStructuredBlock(block); - // Generate state for next block - if (Instruction* mergeInst = block->GetMergeInst()) { - Instruction* loopMergeInst = block->GetLoopMergeInst(); - if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst(); - state_.emplace_back(loopMergeInst, mergeInst); - } + // Generate state for next block if warranted + GenerateState(block); } state_.clear(); @@ -133,12 +155,8 @@ bool MergeReturnPass::ProcessStructured( } } - // Generate state for next block - if (Instruction* mergeInst = block->GetMergeInst()) { - Instruction* loopMergeInst = block->GetLoopMergeInst(); - if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst(); - state_.emplace_back(loopMergeInst, mergeInst); - } + // Generate state for next block if warranted + GenerateState(block); } // We have not kept the dominator tree up-to-date. @@ -202,8 +220,8 @@ void MergeReturnPass::ProcessStructuredBlock(BasicBlock* block) { if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue || tail_opcode == SpvOpUnreachable) { - assert(CurrentState().InLoop() && "Should be in the dummy loop."); - BranchToBlock(block, CurrentState().LoopMergeId()); + assert(CurrentState().InBreakable() && "Should be in the dummy construct."); + BranchToBlock(block, CurrentState().BreakMergeId()); return_blocks_.insert(block->id()); } } @@ -337,8 +355,8 @@ bool MergeReturnPass::PredicateBlocks( std::unordered_set seen; if (block->id() == state->CurrentMergeId()) { state++; - } else if (block->id() == state->LoopMergeId()) { - while (state->LoopMergeId() == block->id()) { + } else if (block->id() == state->BreakMergeId()) { + while (state->BreakMergeId() == block->id()) { state++; } } @@ -346,15 +364,14 @@ bool MergeReturnPass::PredicateBlocks( while (block != nullptr && block != final_return_block_) { if (!predicated->insert(block).second) break; // Skip structured subgraphs. - assert(state->InLoop() && "Should be in the dummy loop at the very least."); - Instruction* current_loop_merge_inst = state->LoopMergeInst(); - uint32_t merge_block_id = - current_loop_merge_inst->GetSingleWordInOperand(0); - while (state->LoopMergeId() == merge_block_id) { + assert(state->InBreakable() && + "Should be in the dummy 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) { state++; } - if (!BreakFromConstruct(block, predicated, order, - current_loop_merge_inst)) { + if (!BreakFromConstruct(block, predicated, order, break_merge_inst)) { return false; } block = context()->get_instr_block(merge_block_id); @@ -364,9 +381,7 @@ bool MergeReturnPass::PredicateBlocks( bool MergeReturnPass::BreakFromConstruct( BasicBlock* block, std::unordered_set* predicated, - std::list* order, Instruction* loop_merge_inst) { - assert(loop_merge_inst->opcode() == SpvOpLoopMerge && - "loop_merge_inst must be a loop merge instruction."); + std::list* order, Instruction* break_merge_inst) { // Make sure the CFG is build here. If we don't then it becomes very hard // to know which new blocks need to be updated. context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG); @@ -388,7 +403,7 @@ bool MergeReturnPass::BreakFromConstruct( } } - uint32_t merge_block_id = loop_merge_inst->GetSingleWordInOperand(0); + uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0); BasicBlock* merge_block = context()->get_instr_block(merge_block_id); if (merge_block->GetLoopMergeInst()) { cfg()->SplitLoopHeader(merge_block); @@ -416,9 +431,10 @@ bool MergeReturnPass::BreakFromConstruct( // If |block| was a continue target for a loop |old_body| is now the correct // continue target. - if (loop_merge_inst->GetSingleWordInOperand(1) == block->id()) { - loop_merge_inst->SetInOperand(1, {old_body->id()}); - context()->UpdateDefUse(loop_merge_inst); + if (break_merge_inst->opcode() == SpvOpLoopMerge && + break_merge_inst->GetSingleWordInOperand(1) == block->id()) { + break_merge_inst->SetInOperand(1, {old_body->id()}); + context()->UpdateDefUse(break_merge_inst); } // Update |order| so old_block will be traversed. @@ -430,8 +446,8 @@ bool MergeReturnPass::BreakFromConstruct( // 3. Update OpPhi instructions in |merge_block|. // 4. Update the CFG. // - // Sine we are branching to the merge block of the current construct, there is - // no need for an OpSelectionMerge. + // Since we are branching to the merge block of the current construct, there + // is no need for an OpSelectionMerge. InstructionBuilder builder( context(), block, @@ -710,7 +726,7 @@ void MergeReturnPass::InsertAfterElement(BasicBlock* element, list->insert(pos, new_element); } -void MergeReturnPass::AddDummyLoopAroundFunction() { +void MergeReturnPass::AddDummySwitchAroundFunction() { CreateReturnBlock(); CreateReturn(final_return_block_); @@ -718,7 +734,7 @@ void MergeReturnPass::AddDummyLoopAroundFunction() { cfg()->RegisterBlock(final_return_block_); } - CreateDummyLoop(final_return_block_); + CreateDummySwitch(final_return_block_); } BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { @@ -753,14 +769,8 @@ BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { return new_block; } -void MergeReturnPass::CreateDummyLoop(BasicBlock* merge_target) { - std::unique_ptr label( - new Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {})); - - // Create the new basic block - std::unique_ptr block(new BasicBlock(std::move(label))); - - // Insert the new block before any code is run. We have to split the entry +void MergeReturnPass::CreateDummySwitch(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(); auto split_pos = start_block->begin(); @@ -771,38 +781,16 @@ void MergeReturnPass::CreateDummyLoop(BasicBlock* merge_target) { BasicBlock* old_block = start_block->SplitBasicBlock(context(), TakeNextId(), split_pos); - // The new block must be inserted after the entry block. We cannot make the - // entry block the header for the dummy loop because it is not valid to have a - // branch to the entry block, and the continue target must branch back to the - // loop header. - auto pos = function_->begin(); - pos++; - BasicBlock* header_block = &*pos.InsertBefore(std::move(block)); - context()->AnalyzeDefUse(header_block->GetLabelInst()); - header_block->SetParent(function_); - - // We have to create the continue block before OpLoopMerge instruction. - // Otherwise the def-use manager will compalain that there is a use without a - // definition. - uint32_t continue_target = CreateContinueTarget(header_block->id())->id(); - - // Add the code the the header block. + // Add the switch to the end of the entry block. InstructionBuilder builder( - context(), header_block, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - - builder.AddLoopMerge(merge_target->id(), continue_target); - builder.AddBranch(old_block->id()); - - // Fix up the entry block by adding a branch to the loop header. - InstructionBuilder builder2( context(), start_block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - builder2.AddBranch(header_block->id()); + + builder.AddSwitch(builder.GetUintConstantId(0u), old_block->id(), {}, + merge_target->id()); if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) { cfg()->RegisterBlock(old_block); - cfg()->RegisterBlock(header_block); cfg()->AddEdges(start_block); } } diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.h b/3rdparty/spirv-tools/source/opt/merge_return_pass.h index f8edd27b5..fe85557af 100644 --- a/3rdparty/spirv-tools/source/opt/merge_return_pass.h +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.h @@ -33,7 +33,8 @@ namespace opt { * Structured control flow guarantees that the CFG will converge at a given * point (the merge block). Within structured control flow, all blocks must be * post-dominated by the merge block, except return blocks and break blocks. - * A break block is a block that branches to the innermost loop's merge block. + * A break block is a block that branches to a containing construct's merge + * block. * * Beyond this, we further assume that all unreachable blocks have been * cleaned up. This means that the only unreachable blocks are those necessary @@ -46,13 +47,14 @@ namespace opt { * with a branch. If current block is not within structured control flow, this * is the final return. This block should branch to the new return block (its * direct successor). If the current block is within structured control flow, - * the branch destination should be the innermost loop's merge. This loop will - * always exist because a dummy loop 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 it is contained in. - *Once structured control flow has been exited, it will be at the merge of the - *dummy loop, with will simply return. + * the branch destination should be the innermost construct's merge. This + * merge will always exist because a dummy 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. * * In the final return block, the return value should be loaded and returned. * Memory promotion passes should be able to promote the newly introduced @@ -71,7 +73,7 @@ namespace opt { * || * \/ * - * 0 (dummy loop header) + * 0 (dummy switch header) * | * 1 (loop header) * / \ @@ -81,11 +83,11 @@ namespace opt { * / \ * | 3 (original code in 3) * \ / - * (ret) 4 (dummy loop merge) + * (ret) 4 (dummy switch merge) * * In the above (simple) example, the return originally in |2| is passed through - * the merge. That merge is predicated such that the old body of the block is - * the else branch. The branch condition is based on the value of the "has + * the loop merge. That merge is predicated such that the old body of the block + * is the else branch. The branch condition is based on the value of the "has * returned" variable. * ******************************************************************************/ @@ -108,17 +110,17 @@ class MergeReturnPass : public MemPass { } private: - // This class is used to store the a loop merge instruction and a selection - // merge instruction. The intended use is that is represent the inner most - // contain selection construct and the inner most loop construct. + // This class is used to store the a break merge instruction and a current + // merge instruction. The intended use is to keep track of the block to + // break to and the current innermost control flow construct merge block. class StructuredControlState { public: - StructuredControlState(Instruction* loop, Instruction* merge) - : loop_merge_(loop), current_merge_(merge) {} + StructuredControlState(Instruction* break_merge, Instruction* merge) + : break_merge_(break_merge), current_merge_(merge) {} StructuredControlState(const StructuredControlState&) = default; - bool InLoop() const { return loop_merge_; } + bool InBreakable() const { return break_merge_; } bool InStructuredFlow() const { return CurrentMergeId() != 0; } uint32_t CurrentMergeId() const { @@ -132,20 +134,14 @@ class MergeReturnPass : public MemPass { : 0; } - uint32_t LoopMergeId() const { - return loop_merge_ ? loop_merge_->GetSingleWordInOperand(0u) : 0u; + uint32_t BreakMergeId() const { + return break_merge_ ? break_merge_->GetSingleWordInOperand(0u) : 0u; } - uint32_t CurrentLoopHeader() const { - return loop_merge_ - ? loop_merge_->context()->get_instr_block(loop_merge_)->id() - : 0; - } - - Instruction* LoopMergeInst() const { return loop_merge_; } + Instruction* BreakMergeInst() const { return break_merge_; } private: - Instruction* loop_merge_; + Instruction* break_merge_; Instruction* current_merge_; }; @@ -159,6 +155,9 @@ class MergeReturnPass : public MemPass { void MergeReturnBlocks(Function* function, const std::vector& returnBlocks); + // Generate and push new control flow state if |block| contains a merge. + void GenerateState(BasicBlock* block); + // Merges the return instruction in |function| so that it has a single return // statement. It is assumed that |function| has structured control flow, and // that |return_blocks| is a list of all of the basic blocks in |function| @@ -219,9 +218,9 @@ class MergeReturnPass : public MemPass { std::list* order); // Add a conditional branch at the start of |block| that either jumps to - // the merge block of |loop_merge_inst| or the original code in |block| + // the merge block of |break_merge_inst| or the original code in |block| // depending on the value in |return_flag_|. The continue target in - // |loop_merge_inst| will be updated if needed. + // |break_merge_inst| will be updated if needed. // // If new blocks that are created will be added to |order|. This way a call // can traverse these new block in structured order. @@ -230,7 +229,7 @@ class MergeReturnPass : public MemPass { bool BreakFromConstruct(BasicBlock* block, std::unordered_set* predicated, std::list* order, - Instruction* loop_merge_inst); + Instruction* break_merge_inst); // Add an |OpReturn| or |OpReturnValue| to the end of |block|. If an // |OpReturnValue| is needed, the return value is loaded from |return_value_|. @@ -274,27 +273,28 @@ class MergeReturnPass : public MemPass { void InsertAfterElement(BasicBlock* element, BasicBlock* new_element, std::list* list); - // Creates a single iteration loop around all of the exectuable code of the - // current function and returns after the loop is done. Sets + // Creates a single case switch around all of the exectuable code of the + // 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 AddDummyLoopAroundFunction(); + void AddDummySwitchAroundFunction(); // 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 // function. BasicBlock* CreateContinueTarget(uint32_t header_label_id); - // Creates a loop around the executable code of the function with + // Creates a one case switch around the executable code of the function with // |merge_target| as the merge node. - void CreateDummyLoop(BasicBlock* merge_target); + void CreateDummySwitch(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 // 1 instruction which is OpUnreachable. bool HasNontrivialUnreachableBlocks(Function* function); - // A stack used to keep track of the innermost contain loop and selection - // constructs. + // A stack used to keep track of the break and current control flow construct + // merge blocks. std::vector state_; // The current function being transformed. diff --git a/3rdparty/spirv-tools/source/opt/module.cpp b/3rdparty/spirv-tools/source/opt/module.cpp index c7fc2473d..4403894d8 100644 --- a/3rdparty/spirv-tools/source/opt/module.cpp +++ b/3rdparty/spirv-tools/source/opt/module.cpp @@ -95,6 +95,7 @@ void Module::ForEachInst(const std::function& f, DELEGATE(debugs1_); DELEGATE(debugs2_); DELEGATE(debugs3_); + DELEGATE(ext_inst_debuginfo_); DELEGATE(annotations_); DELEGATE(types_values_); for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts); @@ -117,6 +118,7 @@ void Module::ForEachInst(const std::function& f, for (auto& i : debugs3_) DELEGATE(i); for (auto& i : annotations_) DELEGATE(i); for (auto& i : types_values_) DELEGATE(i); + for (auto& i : ext_inst_debuginfo_) DELEGATE(i); for (auto& i : functions_) { static_cast(i.get())->ForEachInst(f, run_on_debug_line_insts); diff --git a/3rdparty/spirv-tools/source/opt/module.h b/3rdparty/spirv-tools/source/opt/module.h index aefa2a548..fc53d35a1 100644 --- a/3rdparty/spirv-tools/source/opt/module.h +++ b/3rdparty/spirv-tools/source/opt/module.h @@ -102,6 +102,10 @@ class Module { // This is due to decision by the SPIR Working Group, pending publication. inline void AddDebug3Inst(std::unique_ptr d); + // Appends a debug info extension (OpenCL.DebugInfo.100 or DebugInfo) + // instruction to this module. + inline void AddExtInstDebugInfo(std::unique_ptr d); + // Appends an annotation instruction to this module. inline void AddAnnotationInst(std::unique_ptr a); @@ -182,6 +186,14 @@ class Module { inline IteratorRange debugs3(); inline IteratorRange debugs3() const; + // Iterators for debug info instructions (excluding OpLine & OpNoLine) + // contained in this module. These are OpExtInst for OpenCL.DebugInfo.100 + // or DebugInfo extension placed between section 9 and 10. + inline inst_iterator ext_inst_debuginfo_begin(); + inline inst_iterator ext_inst_debuginfo_end(); + inline IteratorRange ext_inst_debuginfo(); + inline IteratorRange ext_inst_debuginfo() const; + // Iterators for entry point instructions contained in this module inline IteratorRange entry_points(); inline IteratorRange entry_points() const; @@ -274,6 +286,7 @@ class Module { InstructionList debugs1_; InstructionList debugs2_; InstructionList debugs3_; + InstructionList ext_inst_debuginfo_; InstructionList annotations_; // Type declarations, constants, and global variable declarations. InstructionList types_values_; @@ -323,6 +336,10 @@ inline void Module::AddDebug3Inst(std::unique_ptr d) { debugs3_.push_back(std::move(d)); } +inline void Module::AddExtInstDebugInfo(std::unique_ptr d) { + ext_inst_debuginfo_.push_back(std::move(d)); +} + inline void Module::AddAnnotationInst(std::unique_ptr a) { annotations_.push_back(std::move(a)); } @@ -403,6 +420,22 @@ inline IteratorRange Module::debugs3() const { return make_range(debugs3_.begin(), debugs3_.end()); } +inline Module::inst_iterator Module::ext_inst_debuginfo_begin() { + return ext_inst_debuginfo_.begin(); +} +inline Module::inst_iterator Module::ext_inst_debuginfo_end() { + return ext_inst_debuginfo_.end(); +} + +inline IteratorRange Module::ext_inst_debuginfo() { + return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end()); +} + +inline IteratorRange Module::ext_inst_debuginfo() + const { + return make_range(ext_inst_debuginfo_.begin(), ext_inst_debuginfo_.end()); +} + inline IteratorRange Module::entry_points() { return make_range(entry_points_.begin(), entry_points_.end()); } diff --git a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp index 9e7fad0b2..c86ce5782 100644 --- a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp @@ -19,14 +19,63 @@ namespace spvtools { namespace opt { Pass::Status StripDebugInfoPass::Process() { - bool modified = !context()->debugs1().empty() || - !context()->debugs2().empty() || - !context()->debugs3().empty(); + bool uses_non_semantic_info = false; + for (auto& inst : context()->module()->extensions()) { + const char* ext_name = + reinterpret_cast(&inst.GetInOperand(0).words[0]); + if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) { + uses_non_semantic_info = true; + } + } std::vector to_kill; - for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg); + + // if we use non-semantic info, it may reference OpString. Do a more + // expensive pass checking the uses of the OpString to see if any are + // OpExtInst on a non-semantic instruction set. If we're not using the + // extension then we can do a simpler pass and kill all debug1 instructions + if (uses_non_semantic_info) { + for (auto& inst : context()->module()->debugs1()) { + switch (inst.opcode()) { + case SpvOpString: { + analysis::DefUseManager* def_use = context()->get_def_use_mgr(); + + // see if this string is used anywhere by a non-semantic instruction + bool no_nonsemantic_use = + def_use->WhileEachUser(&inst, [def_use](Instruction* use) { + if (use->opcode() == SpvOpExtInst) { + auto ext_inst_set = + def_use->GetDef(use->GetSingleWordInOperand(0u)); + const char* extension_name = reinterpret_cast( + &ext_inst_set->GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) { + // found a non-semantic use, return false as we cannot + // remove this OpString + return false; + } + } + + // other instructions can't be a non-semantic use + return true; + }); + + if (no_nonsemantic_use) to_kill.push_back(&inst); + + break; + } + + default: + to_kill.push_back(&inst); + break; + } + } + } else { + for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg); + } + for (auto& dbg : context()->debugs2()) to_kill.push_back(&dbg); for (auto& dbg : context()->debugs3()) to_kill.push_back(&dbg); + for (auto& dbg : context()->ext_inst_debuginfo()) to_kill.push_back(&dbg); // OpName must come first, since they may refer to other debug instructions. // If they are after the instructions that refer to, then they will be killed @@ -38,8 +87,11 @@ Pass::Status StripDebugInfoPass::Process() { return false; }); + bool modified = !to_kill.empty(); + for (auto* inst : to_kill) context()->KillInst(inst); + // clear OpLine information context()->module()->ForEachInst([&modified](Instruction* inst) { modified |= !inst->dbg_line_insts().empty(); inst->dbg_line_insts().clear(); diff --git a/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp index 984073f9d..8b0f2db7f 100644 --- a/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp @@ -67,9 +67,55 @@ Pass::Status StripReflectInfoPass::Process() { } else if (!other_uses_for_decorate_string && 0 == std::strcmp(ext_name, "SPV_GOOGLE_decorate_string")) { to_remove.push_back(&inst); + } else if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) { + to_remove.push_back(&inst); } } + // clear all debug data now if it hasn't been cleared already, to remove any + // remaining OpString that may have been referenced by non-semantic extinsts + for (auto& dbg : context()->debugs1()) to_remove.push_back(&dbg); + for (auto& dbg : context()->debugs2()) to_remove.push_back(&dbg); + for (auto& dbg : context()->debugs3()) to_remove.push_back(&dbg); + for (auto& dbg : context()->ext_inst_debuginfo()) to_remove.push_back(&dbg); + + // remove any extended inst imports that are non semantic + std::unordered_set non_semantic_sets; + for (auto& inst : context()->module()->ext_inst_imports()) { + assert(inst.opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast(&inst.GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) { + non_semantic_sets.insert(inst.result_id()); + to_remove.push_back(&inst); + } + } + + // if we removed some non-semantic sets, then iterate over the instructions in + // the module to remove any OpExtInst that referenced those sets + if (!non_semantic_sets.empty()) { + context()->module()->ForEachInst( + [&non_semantic_sets, &to_remove](Instruction* inst) { + if (inst->opcode() == SpvOpExtInst) { + if (non_semantic_sets.find(inst->GetSingleWordInOperand(0)) != + non_semantic_sets.end()) { + to_remove.push_back(inst); + } + } + }); + } + + // OpName must come first, since they may refer to other debug instructions. + // If they are after the instructions that refer to, then they will be killed + // when that instruction is killed, which will lead to a double kill. + std::sort(to_remove.begin(), to_remove.end(), + [](Instruction* lhs, Instruction* rhs) -> bool { + if (lhs->opcode() == SpvOpName && rhs->opcode() != SpvOpName) + return true; + return false; + }); + for (auto* inst : to_remove) { modified = true; context()->KillInst(inst); diff --git a/3rdparty/spirv-tools/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp b/3rdparty/spirv-tools/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp index da61c8dba..ce6669115 100644 --- a/3rdparty/spirv-tools/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp +++ b/3rdparty/spirv-tools/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp @@ -52,6 +52,13 @@ RemoveUnreferencedInstructionReductionOpportunityFinder:: result.push_back(MakeUnique(&inst)); } + for (auto& inst : context->module()->ext_inst_debuginfo()) { + if (context->get_def_use_mgr()->NumUses(&inst) > 0) { + continue; + } + result.push_back(MakeUnique(&inst)); + } + for (auto& inst : context->module()->types_values()) { if (context->get_def_use_mgr()->NumUsers(&inst) > 0) { continue; diff --git a/3rdparty/spirv-tools/source/spirv_target_env.cpp b/3rdparty/spirv-tools/source/spirv_target_env.cpp index 86f2c8d1f..e2ff99cb0 100644 --- a/3rdparty/spirv-tools/source/spirv_target_env.cpp +++ b/3rdparty/spirv-tools/source/spirv_target_env.cpp @@ -68,6 +68,8 @@ const char* spvTargetEnvDescription(spv_target_env env) { return "SPIR-V 1.4 (under Vulkan 1.1 semantics)"; case SPV_ENV_UNIVERSAL_1_5: return "SPIR-V 1.5"; + case SPV_ENV_VULKAN_1_2: + return "SPIR-V 1.5 (under Vulkan 1.2 semantics)"; } return ""; } @@ -102,6 +104,7 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) { case SPV_ENV_VULKAN_1_1_SPIRV_1_4: return SPV_SPIRV_VERSION_WORD(1, 4); case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: return SPV_SPIRV_VERSION_WORD(1, 5); } return SPV_SPIRV_VERSION_WORD(0, 0); @@ -111,6 +114,7 @@ static const std::pair spvTargetEnvNameMap[] = { {"vulkan1.1spv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4}, {"vulkan1.0", SPV_ENV_VULKAN_1_0}, {"vulkan1.1", SPV_ENV_VULKAN_1_1}, + {"vulkan1.2", SPV_ENV_VULKAN_1_2}, {"spv1.0", SPV_ENV_UNIVERSAL_1_0}, {"spv1.1", SPV_ENV_UNIVERSAL_1_1}, {"spv1.2", SPV_ENV_UNIVERSAL_1_2}, @@ -149,6 +153,34 @@ bool spvParseTargetEnv(const char* s, spv_target_env* env) { return false; } +#define VULKAN_VER(MAJOR, MINOR) ((MAJOR << 22) | (MINOR << 12)) +#define SPIRV_VER(MAJOR, MINOR) ((MAJOR << 16) | (MINOR << 8)) + +struct VulkanEnv { + spv_target_env vulkan_env; + uint32_t vulkan_ver; + uint32_t spirv_ver; +}; +// Maps each Vulkan target environment enum to the Vulkan version, and the +// maximum supported SPIR-V version for that Vulkan environment. +// Keep this ordered from least capable to most capable. +static const VulkanEnv ordered_vulkan_envs[] = { + {SPV_ENV_VULKAN_1_0, VULKAN_VER(1, 0), SPIRV_VER(1, 0)}, + {SPV_ENV_VULKAN_1_1, VULKAN_VER(1, 1), SPIRV_VER(1, 3)}, + {SPV_ENV_VULKAN_1_1_SPIRV_1_4, VULKAN_VER(1, 1), SPIRV_VER(1, 4)}, + {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)}}; + +bool spvParseVulkanEnv(uint32_t vulkan_ver, uint32_t spirv_ver, + spv_target_env* env) { + for (auto triple : ordered_vulkan_envs) { + if (triple.vulkan_ver >= vulkan_ver && triple.spirv_ver >= spirv_ver) { + *env = triple.vulkan_env; + return true; + } + } + return false; +} + bool spvIsVulkanEnv(spv_target_env env) { switch (env) { case SPV_ENV_UNIVERSAL_1_0: @@ -175,6 +207,7 @@ bool spvIsVulkanEnv(spv_target_env env) { case SPV_ENV_VULKAN_1_0: case SPV_ENV_VULKAN_1_1: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: + case SPV_ENV_VULKAN_1_2: return true; } return false; @@ -197,6 +230,7 @@ bool spvIsOpenCLEnv(spv_target_env env) { case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: return false; case SPV_ENV_OPENCL_1_2: case SPV_ENV_OPENCL_EMBEDDED_1_2: @@ -235,6 +269,7 @@ bool spvIsWebGPUEnv(spv_target_env env) { case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: return false; case SPV_ENV_WEBGPU_0: return true; @@ -262,6 +297,7 @@ bool spvIsOpenGLEnv(spv_target_env env) { case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: return false; case SPV_ENV_OPENGL_4_0: case SPV_ENV_OPENGL_4_1: @@ -299,7 +335,8 @@ std::string spvLogStringForEnv(spv_target_env env) { case SPV_ENV_VULKAN_1_0: case SPV_ENV_VULKAN_1_1: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: { - return "Vulkan"; + case SPV_ENV_VULKAN_1_2: + return "Vulkan"; } case SPV_ENV_WEBGPU_0: { return "WebGPU"; diff --git a/3rdparty/spirv-tools/source/table.cpp b/3rdparty/spirv-tools/source/table.cpp index b7a96cc95..8340e8e21 100644 --- a/3rdparty/spirv-tools/source/table.cpp +++ b/3rdparty/spirv-tools/source/table.cpp @@ -41,6 +41,7 @@ spv_context spvContextCreate(spv_target_env env) { case SPV_ENV_WEBGPU_0: case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_2: break; default: return nullptr; diff --git a/3rdparty/spirv-tools/source/text.cpp b/3rdparty/spirv-tools/source/text.cpp index d88d4f7f6..88a8e8ffa 100644 --- a/3rdparty/spirv-tools/source/text.cpp +++ b/3rdparty/spirv-tools/source/text.cpp @@ -242,14 +242,37 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, // The assembler accepts the symbolic name for an extended instruction, // and emits its corresponding number. spv_ext_inst_desc extInst; - if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst)) { - return context->diagnostic() - << "Invalid extended instruction name '" << textValue << "'."; - } - spvInstructionAddWord(pInst, extInst->ext_inst); + if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst) == + SPV_SUCCESS) { + // if we know about this extended instruction, push the numeric value + spvInstructionAddWord(pInst, extInst->ext_inst); - // Prepare to parse the operands for the extended instructions. - spvPushOperandTypes(extInst->operandTypes, pExpectedOperands); + // Prepare to parse the operands for the extended instructions. + spvPushOperandTypes(extInst->operandTypes, pExpectedOperands); + } else { + // if we don't know this extended instruction and the set isn't + // non-semantic, we cannot process further + if (!spvExtInstIsNonSemantic(pInst->extInstType)) { + return context->diagnostic() + << "Invalid extended instruction name '" << textValue << "'."; + } else { + // for non-semantic instruction sets, as long as the text name is an + // integer value we can encode it since we know the form of all such + // extended instructions + spv_literal_t extInstValue; + if (spvTextToLiteral(textValue, &extInstValue) || + extInstValue.type != SPV_LITERAL_TYPE_UINT_32) { + return context->diagnostic() + << "Couldn't translate unknown extended instruction name '" + << textValue << "' to unsigned integer."; + } + + spvInstructionAddWord(pInst, extInstValue.value.u32); + + // opcode contains an unknown number of IDs. + pExpectedOperands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID); + } + } } break; case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { @@ -377,7 +400,8 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar, case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: case SPV_OPERAND_TYPE_SELECTION_CONTROL: - case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: { + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: { uint32_t value; if (grammar.parseMaskOperand(type, textValue, &value)) { return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) diff --git a/3rdparty/spirv-tools/source/val/construct.cpp b/3rdparty/spirv-tools/source/val/construct.cpp index 156444923..733856cb8 100644 --- a/3rdparty/spirv-tools/source/val/construct.cpp +++ b/3rdparty/spirv-tools/source/val/construct.cpp @@ -169,9 +169,22 @@ bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const { return true; } + // The next block in the traversal is either: + // i. The header block that declares |block| as its merge block. + // ii. The immediate dominator of |block|. + auto NextBlock = [](const BasicBlock* block) -> const BasicBlock* { + for (auto& use : block->label()->uses()) { + if ((use.first->opcode() == SpvOpLoopMerge || + use.first->opcode() == SpvOpSelectionMerge) && + use.second == 1) + return use.first->block(); + } + return block->immediate_dominator(); + }; + bool seen_switch = false; auto header = entry_block(); - auto block = header->immediate_dominator(); + auto block = NextBlock(header); while (block) { auto terminator = block->terminator(); auto index = terminator - &_.ordered_instructions()[0]; @@ -183,7 +196,7 @@ bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const { auto merge_target = merge_inst->GetOperandAs(0u); auto merge_block = merge_inst->function()->GetBlock(merge_target).first; if (merge_block->dominates(*header)) { - block = block->immediate_dominator(); + block = NextBlock(block); continue; } @@ -197,13 +210,15 @@ bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const { } } - if (terminator->opcode() == SpvOpSwitch) seen_switch = true; + if (terminator->opcode() == SpvOpSwitch) { + seen_switch = true; + } // Hit an enclosing loop and didn't break or continue. if (merge_inst->opcode() == SpvOpLoopMerge) return false; } - block = block->immediate_dominator(); + block = NextBlock(block); } } diff --git a/3rdparty/spirv-tools/source/val/instruction.h b/3rdparty/spirv-tools/source/val/instruction.h index 1fa855fca..617cb0660 100644 --- a/3rdparty/spirv-tools/source/val/instruction.h +++ b/3rdparty/spirv-tools/source/val/instruction.h @@ -21,6 +21,7 @@ #include #include +#include "source/ext_inst.h" #include "source/table.h" #include "spirv-tools/libspirv.h" @@ -85,6 +86,17 @@ class Instruction { return inst_.ext_inst_type; } + bool IsNonSemantic() const { + return opcode() == SpvOp::SpvOpExtInst && + spvExtInstIsNonSemantic(inst_.ext_inst_type); + } + + /// True if this is an OpExtInst for debug info extension. + bool IsDebugInfo() const { + return opcode() == SpvOp::SpvOpExtInst && + spvExtInstIsDebugInfo(inst_.ext_inst_type); + } + // Casts the words belonging to the operand under |index| to |T| and returns. template T GetOperandAs(size_t index) const { diff --git a/3rdparty/spirv-tools/source/val/validate_adjacency.cpp b/3rdparty/spirv-tools/source/val/validate_adjacency.cpp index 108e36106..64655b0dc 100644 --- a/3rdparty/spirv-tools/source/val/validate_adjacency.cpp +++ b/3rdparty/spirv-tools/source/val/validate_adjacency.cpp @@ -54,6 +54,16 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) { adjacency_status = adjacency_status == IN_NEW_FUNCTION ? IN_ENTRY_BLOCK : PHI_VALID; break; + case SpvOpExtInst: + // If it is a debug info instruction, we do not change the status to + // allow debug info instructions before OpVariable in a function. + // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We need + // to discuss the location of DebugScope, DebugNoScope, DebugDeclare, + // and DebugValue. + if (!spvExtInstIsDebugInfo(inst.ext_inst_type())) { + adjacency_status = PHI_AND_VAR_INVALID; + } + break; case SpvOpPhi: if (adjacency_status != PHI_VALID) { return _.diag(SPV_ERROR_INVALID_DATA, &inst) diff --git a/3rdparty/spirv-tools/source/val/validate_annotation.cpp b/3rdparty/spirv-tools/source/val/validate_annotation.cpp index 269528035..df38f1b16 100644 --- a/3rdparty/spirv-tools/source/val/validate_annotation.cpp +++ b/3rdparty/spirv-tools/source/val/validate_annotation.cpp @@ -286,7 +286,8 @@ spv_result_t ValidateDecorationGroup(ValidationState_t& _, auto use = pair.first; if (use->opcode() != SpvOpDecorate && use->opcode() != SpvOpGroupDecorate && use->opcode() != SpvOpGroupMemberDecorate && - use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId) { + use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId && + !use->IsNonSemantic()) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Result id of OpDecorationGroup can only " << "be targeted by OpName, OpGroupDecorate, " diff --git a/3rdparty/spirv-tools/source/val/validate_capability.cpp b/3rdparty/spirv-tools/source/val/validate_capability.cpp index ad6cb2654..8a356bf7f 100644 --- a/3rdparty/spirv-tools/source/val/validate_capability.cpp +++ b/3rdparty/spirv-tools/source/val/validate_capability.cpp @@ -55,6 +55,15 @@ bool IsSupportGuaranteedVulkan_1_1(uint32_t capability) { return false; } +bool IsSupportGuaranteedVulkan_1_2(uint32_t capability) { + if (IsSupportGuaranteedVulkan_1_1(capability)) return true; + switch (capability) { + case SpvCapabilityShaderNonUniform: + return true; + } + return false; +} + bool IsSupportOptionalVulkan_1_0(uint32_t capability) { switch (capability) { case SpvCapabilityGeometry: @@ -121,6 +130,38 @@ bool IsSupportOptionalVulkan_1_1(uint32_t capability) { return false; } +bool IsSupportOptionalVulkan_1_2(uint32_t capability) { + if (IsSupportOptionalVulkan_1_1(capability)) return true; + + switch (capability) { + case SpvCapabilityDenormPreserve: + case SpvCapabilityDenormFlushToZero: + case SpvCapabilitySignedZeroInfNanPreserve: + case SpvCapabilityRoundingModeRTE: + case SpvCapabilityRoundingModeRTZ: + case SpvCapabilityVulkanMemoryModel: + case SpvCapabilityVulkanMemoryModelDeviceScope: + case SpvCapabilityStorageBuffer8BitAccess: + case SpvCapabilityUniformAndStorageBuffer8BitAccess: + case SpvCapabilityStoragePushConstant8: + case SpvCapabilityShaderViewportIndex: + case SpvCapabilityShaderLayer: + case SpvCapabilityPhysicalStorageBufferAddresses: + case SpvCapabilityRuntimeDescriptorArray: + case SpvCapabilityUniformTexelBufferArrayDynamicIndexing: + case SpvCapabilityStorageTexelBufferArrayDynamicIndexing: + case SpvCapabilityUniformBufferArrayNonUniformIndexing: + case SpvCapabilitySampledImageArrayNonUniformIndexing: + case SpvCapabilityStorageBufferArrayNonUniformIndexing: + case SpvCapabilityStorageImageArrayNonUniformIndexing: + case SpvCapabilityInputAttachmentArrayNonUniformIndexing: + case SpvCapabilityUniformTexelBufferArrayNonUniformIndexing: + case SpvCapabilityStorageTexelBufferArrayNonUniformIndexing: + return true; + } + return false; +} + bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { switch (capability) { case SpvCapabilityAddresses: @@ -284,6 +325,15 @@ spv_result_t CapabilityPass(ValidationState_t& _, const Instruction* inst) { << " is not allowed by Vulkan 1.1 specification" << " (or requires extension)"; } + } else if (env == SPV_ENV_VULKAN_1_2) { + if (!IsSupportGuaranteedVulkan_1_2(capability) && + !IsSupportOptionalVulkan_1_2(capability) && + !IsEnabledByExtension(_, capability)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) + << "Capability " << capability_str() + << " is not allowed by Vulkan 1.2 specification" + << " (or requires extension)"; + } } else if (env == SPV_ENV_OPENCL_1_2 || env == SPV_ENV_OPENCL_EMBEDDED_1_2) { if (!IsSupportGuaranteedOpenCL_1_2(capability, opencl_embedded) && !IsSupportOptionalOpenCL_1_2(capability) && diff --git a/3rdparty/spirv-tools/source/val/validate_cfg.cpp b/3rdparty/spirv-tools/source/val/validate_cfg.cpp index acce1fbde..f3019d17f 100644 --- a/3rdparty/spirv-tools/source/val/validate_cfg.cpp +++ b/3rdparty/spirv-tools/source/val/validate_cfg.cpp @@ -728,10 +728,10 @@ spv_result_t StructuredControlFlowChecks( } Construct::ConstructBlockSet construct_blocks = construct.blocks(function); + std::string construct_name, header_name, exit_name; + std::tie(construct_name, header_name, exit_name) = + ConstructNames(construct.type()); for (auto block : construct_blocks) { - std::string construct_name, header_name, exit_name; - std::tie(construct_name, header_name, exit_name) = - ConstructNames(construct.type()); // Check that all exits from the construct are via structured exits. for (auto succ : *block->successors()) { if (block->reachable() && !construct_blocks.count(succ) && @@ -769,7 +769,7 @@ spv_result_t StructuredControlFlowChecks( << "Header block " << _.getIdName(block->id()) << " is contained in the " << construct_name << " construct headed by " << _.getIdName(header->id()) - << ", but it's merge block " << _.getIdName(merge_id) + << ", but its merge block " << _.getIdName(merge_id) << " is not"; } } diff --git a/3rdparty/spirv-tools/source/val/validate_decorations.cpp b/3rdparty/spirv-tools/source/val/validate_decorations.cpp index d513a2574..3b4483337 100644 --- a/3rdparty/spirv-tools/source/val/validate_decorations.cpp +++ b/3rdparty/spirv-tools/source/val/validate_decorations.cpp @@ -1275,6 +1275,7 @@ spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate, const auto store = use.first; if (store->opcode() == SpvOpFConvert) continue; if (spvOpcodeIsDebug(store->opcode())) continue; + if (store->IsNonSemantic()) continue; if (spvOpcodeIsDecoration(store->opcode())) continue; if (store->opcode() != SpvOpStore) { return vstate.diag(SPV_ERROR_INVALID_ID, &inst) diff --git a/3rdparty/spirv-tools/source/val/validate_extensions.cpp b/3rdparty/spirv-tools/source/val/validate_extensions.cpp index 1a6460593..070cc4c6a 100644 --- a/3rdparty/spirv-tools/source/val/validate_extensions.cpp +++ b/3rdparty/spirv-tools/source/val/validate_extensions.cpp @@ -61,8 +61,8 @@ spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) { spv_result_t ValidateExtInstImport(ValidationState_t& _, const Instruction* inst) { + const auto name_id = 1; if (spvIsWebGPUEnv(_.context()->target_env)) { - const auto name_id = 1; const std::string name(reinterpret_cast( inst->words().data() + inst->operands()[name_id].offset)); if (name != "GLSL.std.450") { @@ -72,6 +72,16 @@ spv_result_t ValidateExtInstImport(ValidationState_t& _, } } + if (!_.HasExtension(kSPV_KHR_non_semantic_info)) { + const std::string name(reinterpret_cast( + inst->words().data() + inst->operands()[name_id].offset)); + if (name.find("NonSemantic.") == 0) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "NonSemantic extended instruction sets cannot be declared " + "without SPV_KHR_non_semantic_info."; + } + } + return SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validate_function.cpp b/3rdparty/spirv-tools/source/val/validate_function.cpp index b98319416..f130eacda 100644 --- a/3rdparty/spirv-tools/source/val/validate_function.cpp +++ b/3rdparty/spirv-tools/source/val/validate_function.cpp @@ -87,7 +87,8 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) { for (auto& pair : inst->uses()) { const auto* use = pair.first; if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) == - acceptable.end()) { + acceptable.end() && + !use->IsNonSemantic() && !use->IsDebugInfo()) { return _.diag(SPV_ERROR_INVALID_ID, use) << "Invalid use of function result id " << _.getIdName(inst->id()) << "."; diff --git a/3rdparty/spirv-tools/source/val/validate_id.cpp b/3rdparty/spirv-tools/source/val/validate_id.cpp index d80e1b859..c171d310d 100644 --- a/3rdparty/spirv-tools/source/val/validate_id.cpp +++ b/3rdparty/spirv-tools/source/val/validate_id.cpp @@ -131,7 +131,11 @@ spv_result_t CheckIdDefinitionDominateUse(ValidationState_t& _) { // instruction operand's ID can be forward referenced. spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { auto can_have_forward_declared_ids = - spvOperandCanBeForwardDeclaredFunction(inst->opcode()); + inst->opcode() == SpvOpExtInst && + spvExtInstIsDebugInfo(inst->ext_inst_type()) + ? spvDbgInfoExtOperandCanBeForwardDeclaredFunction( + inst->ext_inst_type(), inst->word(4)) + : spvOperandCanBeForwardDeclaredFunction(inst->opcode()); // Keep track of a result id defined by this instruction. 0 means it // does not define an id. @@ -167,7 +171,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { const auto opcode = inst->opcode(); if (spvOpcodeGeneratesType(def->opcode()) && !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) && - !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction && + !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) && + opcode != SpvOpFunction && opcode != SpvOpCooperativeMatrixLengthNV && !(opcode == SpvOpSpecConstantOp && inst->word(3) == SpvOpCooperativeMatrixLengthNV)) { @@ -175,7 +180,7 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { << "Operand " << _.getIdName(operand_word) << " cannot be a type"; } else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) && - !spvOpcodeIsDebug(opcode) && + !spvOpcodeIsDebug(opcode) && !inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) && !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi && opcode != SpvOpExtInst && opcode != SpvOpExtInstImport && @@ -187,6 +192,11 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Operand " << _.getIdName(operand_word) << " requires a type"; + } else if (def->IsNonSemantic() && !inst->IsNonSemantic()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Operand " << _.getIdName(operand_word) + << " in semantic instruction cannot be a non-semantic " + "instruction"; } else { ret = SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validate_layout.cpp b/3rdparty/spirv-tools/source/val/validate_layout.cpp index 53c28355f..707ad5248 100644 --- a/3rdparty/spirv-tools/source/val/validate_layout.cpp +++ b/3rdparty/spirv-tools/source/val/validate_layout.cpp @@ -14,15 +14,16 @@ // Source code for logical layout validation as described in section 2.4 -#include "source/val/validate.h" - #include +#include "DebugInfo.h" +#include "OpenCLDebugInfo100.h" #include "source/diagnostic.h" #include "source/opcode.h" #include "source/operand.h" #include "source/val/function.h" #include "source/val/instruction.h" +#include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { @@ -34,6 +35,77 @@ namespace { // checked. spv_result_t ModuleScopedInstructions(ValidationState_t& _, const Instruction* inst, SpvOp opcode) { + switch (opcode) { + case SpvOpExtInst: + if (spvExtInstIsNonSemantic(inst->ext_inst_type())) { + // non-semantic extinst opcodes are allowed beginning in the types + // section, but since they must name a return type they cannot be the + // first instruction in the types section. Therefore check that we are + // already in it. + if (_.current_layout_section() < kLayoutTypes) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Non-semantic OpExtInst must not appear before types " + << "section"; + } + } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) { + const uint32_t ext_inst_index = inst->word(4); + bool local_debug_info = false; + if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) { + const OpenCLDebugInfo100Instructions ext_inst_key = + OpenCLDebugInfo100Instructions(ext_inst_index); + if (ext_inst_key == OpenCLDebugInfo100DebugScope || + ext_inst_key == OpenCLDebugInfo100DebugNoScope || + ext_inst_key == OpenCLDebugInfo100DebugDeclare || + ext_inst_key == OpenCLDebugInfo100DebugValue) { + local_debug_info = true; + } + } else { + const DebugInfoInstructions ext_inst_key = + DebugInfoInstructions(ext_inst_index); + if (ext_inst_key == DebugInfoDebugScope || + ext_inst_key == DebugInfoDebugNoScope || + ext_inst_key == DebugInfoDebugDeclare || + ext_inst_key == DebugInfoDebugValue) { + local_debug_info = true; + } + } + + if (local_debug_info) { + if (_.in_function_body() == false) { + // DebugScope, DebugNoScope, DebugDeclare, DebugValue must + // appear in a function body. + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "DebugScope, DebugNoScope, DebugDeclare, DebugValue " + << "of debug info extension must appear in a function " + << "body"; + } + } else { + // Debug info extinst opcodes other than DebugScope, DebugNoScope, + // DebugDeclare, DebugValue must be placed between section 9 (types, + // constants, global variables) and section 10 (function + // declarations). + if (_.current_layout_section() < kLayoutTypes || + _.current_layout_section() >= kLayoutFunctionDeclarations) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Debug info extension instructions other than " + << "DebugScope, DebugNoScope, DebugDeclare, DebugValue " + << "must appear between section 9 (types, constants, " + << "global variables) and section 10 (function " + << "declarations)"; + } + } + } else { + // otherwise they must be used in a block + if (_.current_layout_section() < kLayoutFunctionDefinitions) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << spvOpcodeString(opcode) << " must appear in a block"; + } + } + break; + default: + break; + } + while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) { _.ProgressToNextLayoutSectionOrder(); @@ -144,6 +216,76 @@ spv_result_t FunctionScopedInstructions(ValidationState_t& _, } break; + case SpvOpExtInst: + if (spvExtInstIsNonSemantic(inst->ext_inst_type())) { + // non-semantic extinst opcodes are allowed beginning in the types + // section, but must either be placed outside a function declaration, + // or inside a block. + if (_.current_layout_section() < kLayoutTypes) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Non-semantic OpExtInst must not appear before types " + << "section"; + } else if (_.in_function_body() && _.in_block() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Non-semantic OpExtInst within function definition must " + "appear in a block"; + } + } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) { + const uint32_t ext_inst_index = inst->word(4); + bool local_debug_info = false; + if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) { + const OpenCLDebugInfo100Instructions ext_inst_key = + OpenCLDebugInfo100Instructions(ext_inst_index); + if (ext_inst_key == OpenCLDebugInfo100DebugScope || + ext_inst_key == OpenCLDebugInfo100DebugNoScope || + ext_inst_key == OpenCLDebugInfo100DebugDeclare || + ext_inst_key == OpenCLDebugInfo100DebugValue) { + local_debug_info = true; + } + } else { + const DebugInfoInstructions ext_inst_key = + DebugInfoInstructions(ext_inst_index); + if (ext_inst_key == DebugInfoDebugScope || + ext_inst_key == DebugInfoDebugNoScope || + ext_inst_key == DebugInfoDebugDeclare || + ext_inst_key == DebugInfoDebugValue) { + local_debug_info = true; + } + } + + if (local_debug_info) { + if (_.in_function_body() == false) { + // DebugScope, DebugNoScope, DebugDeclare, DebugValue must + // appear in a function body. + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "DebugScope, DebugNoScope, DebugDeclare, DebugValue " + << "of debug info extension must appear in a function " + << "body"; + } + } else { + // Debug info extinst opcodes other than DebugScope, DebugNoScope, + // DebugDeclare, DebugValue must be placed between section 9 (types, + // constants, global variables) and section 10 (function + // declarations). + if (_.current_layout_section() < kLayoutTypes || + _.current_layout_section() >= kLayoutFunctionDeclarations) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << "Debug info extension instructions other than " + << "DebugScope, DebugNoScope, DebugDeclare, DebugValue " + << "must appear between section 9 (types, constants, " + << "global variables) and section 10 (function " + << "declarations)"; + } + } + } else { + // otherwise they must be used in a block + if (_.in_block() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT, inst) + << spvOpcodeString(opcode) << " must appear in a block"; + } + } + break; + default: if (_.current_layout_section() == kLayoutFunctionDeclarations && _.in_function_body()) { diff --git a/3rdparty/spirv-tools/source/val/validate_scopes.cpp b/3rdparty/spirv-tools/source/val/validate_scopes.cpp index 1b9878636..320d82821 100644 --- a/3rdparty/spirv-tools/source/val/validate_scopes.cpp +++ b/3rdparty/spirv-tools/source/val/validate_scopes.cpp @@ -212,13 +212,14 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, << "Device, Workgroup and Invocation"; } // Vulkan 1.1 specifc rules - if (_.context()->target_env == SPV_ENV_VULKAN_1_1 && + if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 || + _.context()->target_env == SPV_ENV_VULKAN_1_2) && value != SpvScopeDevice && value != SpvScopeWorkgroup && value != SpvScopeSubgroup && value != SpvScopeInvocation) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) - << ": in Vulkan 1.1 environment Memory Scope is limited to " - << "Device, Workgroup and Invocation"; + << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited " + << "to Device, Workgroup and Invocation"; } } diff --git a/3rdparty/spirv-tools/source/val/validate_type.cpp b/3rdparty/spirv-tools/source/val/validate_type.cpp index 1f171cf17..5924c69fa 100644 --- a/3rdparty/spirv-tools/source/val/validate_type.cpp +++ b/3rdparty/spirv-tools/source/val/validate_type.cpp @@ -536,7 +536,7 @@ spv_result_t ValidateTypeFunction(ValidationState_t& _, for (auto& pair : inst->uses()) { const auto* use = pair.first; if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) && - !spvOpcodeIsDecoration(use->opcode())) { + !use->IsNonSemantic() && !spvOpcodeIsDecoration(use->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, use) << "Invalid use of function type result id " << _.getIdName(inst->id()) << "."; diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index 20eaf88e4..51aebbe51 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -93,6 +93,9 @@ bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) { case SpvOpLine: case SpvOpNoLine: case SpvOpUndef: + // SpvOpExtInst is only allowed here for certain extended instruction + // sets. This will be checked separately + case SpvOpExtInst: out = true; break; default: break; diff --git a/3rdparty/spirv-tools/utils/check_copyright.py b/3rdparty/spirv-tools/utils/check_copyright.py index cfeef80a6..2d288a122 100755 --- a/3rdparty/spirv-tools/utils/check_copyright.py +++ b/3rdparty/spirv-tools/utils/check_copyright.py @@ -32,9 +32,9 @@ AUTHORS = ['The Khronos Group Inc.', 'Google LLC', 'Pierre Moreau', 'Samsung Inc'] -CURRENT_YEAR='2019' +CURRENT_YEAR='2020' -YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2018|2019)' +YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2017-2019|2018|2019|2020)' COPYRIGHT_RE = re.compile( 'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS))) diff --git a/3rdparty/spirv-tools/utils/generate_grammar_tables.py b/3rdparty/spirv-tools/utils/generate_grammar_tables.py index ed24bd06e..2a6773369 100755 --- a/3rdparty/spirv-tools/utils/generate_grammar_tables.py +++ b/3rdparty/spirv-tools/utils/generate_grammar_tables.py @@ -30,6 +30,7 @@ SPV_AMD_gcn_shader SPV_AMD_gpu_shader_half_float SPV_AMD_gpu_shader_int16 SPV_AMD_shader_trinary_minmax +SPV_KHR_non_semantic_info """ @@ -359,14 +360,22 @@ def generate_instruction_table(inst_table): return '{}\n\n{}\n\n{}'.format(caps_arrays, exts_arrays, '\n'.join(insts)) -def generate_extended_instruction_table(inst_table, set_name): +def generate_extended_instruction_table(json_grammar, set_name, operand_kind_prefix=""): """Returns the info table containing all SPIR-V extended instructions, sorted by opcode, and prefixed by capability arrays. Arguments: - inst_table: a list containing all SPIR-V instructions. - set_name: the name of the extended instruction set. + - operand_kind_prefix: the prefix, if any, to add to the front + of operand kind names. """ + if operand_kind_prefix: + prefix_operand_kind_names(operand_kind_prefix, json_grammar) + + inst_table = json_grammar["instructions"] + set_name = set_name.replace(".", "_") + inst_table = sorted(inst_table, key=lambda k: k['opcode']) caps = [inst.get('capabilities', []) for inst in inst_table] caps_arrays = generate_capability_arrays(caps) @@ -451,6 +460,7 @@ def generate_enum_operand_kind_entry(entry, extension_map): def generate_enum_operand_kind(enum, synthetic_exts_list): """Returns the C definition for the given operand kind. + It's a static const named array of spv_operand_desc_t. Also appends to |synthetic_exts_list| a list of extension lists used. @@ -680,6 +690,26 @@ def precondition_operand_kinds(operand_kinds): return operand_kinds +def prefix_operand_kind_names(prefix, json_dict): + """Modifies json_dict, by prefixing all the operand kind names + with the given prefix. Also modifies their uses in the instructions + to match. + """ + + old_to_new = {} + for operand_kind in json_dict["operand_kinds"]: + old_name = operand_kind["kind"] + new_name = prefix + old_name + operand_kind["kind"] = new_name + old_to_new[old_name] = new_name + + for instruction in json_dict["instructions"]: + for operand in instruction.get("operands", []): + replacement = old_to_new.get(operand["kind"]) + if replacement is not None: + operand["kind"] = replacement + + def main(): import argparse parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') @@ -692,6 +722,10 @@ def main(): type=str, required=False, default=None, help='input JSON grammar file for DebugInfo extended ' 'instruction set') + parser.add_argument('--extinst-cldebuginfo100-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for OpenCL.DebugInfo.100 ' + 'extended instruction set') parser.add_argument('--extinst-glsl-grammar', metavar='', type=str, required=False, default=None, help='input JSON grammar file for GLSL extended ' @@ -726,16 +760,27 @@ def main(): parser.add_argument('--vendor-insts-output', metavar='', type=str, required=False, default=None, help='output file for vendor extended instruction set') + parser.add_argument('--vendor-operand-kind-prefix', metavar='', + type=str, required=False, default=None, + help='prefix for operand kinds (to disambiguate operand type enums)') args = parser.parse_args() + # The GN build system needs this because it doesn't handle quoting + # empty string arguments well. + if args.vendor_operand_kind_prefix == "...nil...": + args.vendor_operand_kind_prefix = "" + if (args.core_insts_output is None) != \ (args.operand_kinds_output is None): print('error: --core-insts-output and --operand-kinds-output ' 'should be specified together.') exit(1) - if args.operand_kinds_output and not (args.spirv_core_grammar and args.extinst_debuginfo_grammar): + if args.operand_kinds_output and not (args.spirv_core_grammar and + args.extinst_debuginfo_grammar and + args.extinst_cldebuginfo100_grammar): print('error: --operand-kinds-output requires --spirv-core-grammar ' - 'and --exinst-debuginfo-grammar') + 'and --extinst-debuginfo-grammar ' + 'and --extinst-cldebuginfo100-grammar') exit(1) if (args.glsl_insts_output is None) != \ (args.extinst_glsl_grammar is None): @@ -766,14 +811,19 @@ def main(): core_grammar = json.loads(json_file.read()) with open(args.extinst_debuginfo_grammar) as debuginfo_json_file: debuginfo_grammar = json.loads(debuginfo_json_file.read()) - instructions = [] - instructions.extend(core_grammar['instructions']) - instructions.extend(debuginfo_grammar['instructions']) - operand_kinds = [] - operand_kinds.extend(core_grammar['operand_kinds']) - operand_kinds.extend(debuginfo_grammar['operand_kinds']) - extensions = get_extension_list(instructions, operand_kinds) - operand_kinds = precondition_operand_kinds(operand_kinds) + with open(args.extinst_cldebuginfo100_grammar) as cldebuginfo100_json_file: + cldebuginfo100_grammar = json.loads(cldebuginfo100_json_file.read()) + prefix_operand_kind_names("CLDEBUG100_", cldebuginfo100_grammar) + instructions = [] + instructions.extend(core_grammar['instructions']) + instructions.extend(debuginfo_grammar['instructions']) + instructions.extend(cldebuginfo100_grammar['instructions']) + operand_kinds = [] + operand_kinds.extend(core_grammar['operand_kinds']) + operand_kinds.extend(debuginfo_grammar['operand_kinds']) + operand_kinds.extend(cldebuginfo100_grammar['operand_kinds']) + extensions = get_extension_list(instructions, operand_kinds) + operand_kinds = precondition_operand_kinds(operand_kinds) if args.core_insts_output is not None: make_path_to_file(args.core_insts_output) make_path_to_file(args.operand_kinds_output) @@ -798,7 +848,7 @@ def main(): make_path_to_file(args.glsl_insts_output) with open(args.glsl_insts_output, 'w') as f: f.write(generate_extended_instruction_table( - grammar['instructions'], 'glsl')) + grammar, 'glsl')) if args.extinst_opencl_grammar is not None: with open(args.extinst_opencl_grammar) as json_file: @@ -806,7 +856,7 @@ def main(): make_path_to_file(args.opencl_insts_output) with open(args.opencl_insts_output, 'w') as f: f.write(generate_extended_instruction_table( - grammar['instructions'], 'opencl')) + grammar, 'opencl')) if args.extinst_vendor_grammar is not None: with open(args.extinst_vendor_grammar) as json_file: @@ -817,7 +867,7 @@ def main(): name = name[start:-len('.grammar.json')].replace('-', '_') with open(args.vendor_insts_output, 'w') as f: f.write(generate_extended_instruction_table( - grammar['instructions'], name)) + grammar, name, args.vendor_operand_kind_prefix)) if __name__ == '__main__':