Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2021-10-31 20:37:36 -07:00
parent b3beb85688
commit 0abe793a67
57 changed files with 1504 additions and 447 deletions

View File

@@ -1 +1 @@
"v2021.4-dev", "SPIRV-Tools v2021.4-dev e180849b89618fde67379735eae3a633a97e293c"
"v2021.4-dev", "SPIRV-Tools v2021.4-dev 7e0cccc269f6476f79172f4446ab902f6c451f17"

View File

@@ -7,6 +7,7 @@ static const SpvCapability pygen_variable_caps_ArbitraryPrecisionFloatingPointIN
static const SpvCapability pygen_variable_caps_AsmINTEL[] = {SpvCapabilityAsmINTEL};
static const SpvCapability pygen_variable_caps_AtomicFloat16AddEXTAtomicFloat32AddEXTAtomicFloat64AddEXT[] = {SpvCapabilityAtomicFloat16AddEXT, SpvCapabilityAtomicFloat32AddEXT, SpvCapabilityAtomicFloat64AddEXT};
static const SpvCapability pygen_variable_caps_AtomicFloat16MinMaxEXTAtomicFloat32MinMaxEXTAtomicFloat64MinMaxEXT[] = {SpvCapabilityAtomicFloat16MinMaxEXT, SpvCapabilityAtomicFloat32MinMaxEXT, SpvCapabilityAtomicFloat64MinMaxEXT};
static const SpvCapability pygen_variable_caps_BindlessTextureNV[] = {SpvCapabilityBindlessTextureNV};
static const SpvCapability pygen_variable_caps_BlockingPipesINTEL[] = {SpvCapabilityBlockingPipesINTEL};
static const SpvCapability pygen_variable_caps_CooperativeMatrixNV[] = {SpvCapabilityCooperativeMatrixNV};
static const SpvCapability pygen_variable_caps_DemoteToHelperInvocationEXT[] = {SpvCapabilityDemoteToHelperInvocationEXT};
@@ -498,6 +499,13 @@ static const spv_opcode_desc_t kOpcodeTableEntries[] = {
{"EndInvocationInterlockEXT", SpvOpEndInvocationInterlockEXT, 3, pygen_variable_caps_FragmentShaderSampleInterlockEXTFragmentShaderPixelInterlockEXTFragmentShaderShadingRateInterlockEXT, 0, {}, 0, 0, 1, pygen_variable_exts_SPV_EXT_fragment_shader_interlock, 0xffffffffu, 0xffffffffu},
{"DemoteToHelperInvocationEXT", SpvOpDemoteToHelperInvocationEXT, 1, pygen_variable_caps_DemoteToHelperInvocationEXT, 0, {}, 0, 0, 1, pygen_variable_exts_SPV_EXT_demote_to_helper_invocation, 0xffffffffu, 0xffffffffu},
{"IsHelperInvocationEXT", SpvOpIsHelperInvocationEXT, 1, pygen_variable_caps_DemoteToHelperInvocationEXT, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 1, pygen_variable_exts_SPV_EXT_demote_to_helper_invocation, 0xffffffffu, 0xffffffffu},
{"ConvertUToImageNV", SpvOpConvertUToImageNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"ConvertUToSamplerNV", SpvOpConvertUToSamplerNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"ConvertImageToUNV", SpvOpConvertImageToUNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"ConvertSamplerToUNV", SpvOpConvertSamplerToUNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"ConvertUToSampledImageNV", SpvOpConvertUToSampledImageNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"ConvertSampledImageToUNV", SpvOpConvertSampledImageToUNV, 1, pygen_variable_caps_BindlessTextureNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"SamplerImageAddressingModeNV", SpvOpSamplerImageAddressingModeNV, 1, pygen_variable_caps_BindlessTextureNV, 1, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0, 0, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"SubgroupShuffleINTEL", SpvOpSubgroupShuffleINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"SubgroupShuffleDownINTEL", SpvOpSubgroupShuffleDownINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},
{"SubgroupShuffleUpINTEL", SpvOpSubgroupShuffleUpINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu, 0xffffffffu},

View File

@@ -162,6 +162,8 @@ const char* ExtensionToString(Extension extension) {
return "SPV_KHR_workgroup_memory_explicit_layout";
case Extension::kSPV_NVX_multiview_per_view_attributes:
return "SPV_NVX_multiview_per_view_attributes";
case Extension::kSPV_NV_bindless_texture:
return "SPV_NV_bindless_texture";
case Extension::kSPV_NV_compute_shader_derivatives:
return "SPV_NV_compute_shader_derivatives";
case Extension::kSPV_NV_cooperative_matrix:
@@ -199,8 +201,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_atomic_float16_add", "SPV_EXT_shader_atomic_float_add", "SPV_EXT_shader_atomic_float_min_max", "SPV_EXT_shader_image_int64", "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_arbitrary_precision_fixed_point", "SPV_INTEL_arbitrary_precision_floating_point", "SPV_INTEL_arbitrary_precision_integers", "SPV_INTEL_blocking_pipes", "SPV_INTEL_debug_module", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_float_controls2", "SPV_INTEL_fp_fast_math_mode", "SPV_INTEL_fpga_buffer_location", "SPV_INTEL_fpga_cluster_attributes", "SPV_INTEL_fpga_loop_controls", "SPV_INTEL_fpga_memory_accesses", "SPV_INTEL_fpga_memory_attributes", "SPV_INTEL_fpga_reg", "SPV_INTEL_function_pointers", "SPV_INTEL_inline_assembly", "SPV_INTEL_io_pipes", "SPV_INTEL_kernel_attributes", "SPV_INTEL_long_constant_composite", "SPV_INTEL_loop_fuse", "SPV_INTEL_media_block_io", "SPV_INTEL_optnone", "SPV_INTEL_shader_integer_functions2", "SPV_INTEL_subgroups", "SPV_INTEL_unstructured_loop_controls", "SPV_INTEL_usm_storage_classes", "SPV_INTEL_variable_length_array", "SPV_INTEL_vector_compute", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_bit_instructions", "SPV_KHR_device_group", "SPV_KHR_expect_assume", "SPV_KHR_float_controls", "SPV_KHR_fragment_shading_rate", "SPV_KHR_integer_dot_product", "SPV_KHR_linkonce_odr", "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_ray_query", "SPV_KHR_ray_tracing", "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_uniform_control_flow", "SPV_KHR_subgroup_vote", "SPV_KHR_terminate_invocation", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_KHR_workgroup_memory_explicit_layout", "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_ray_tracing_motion_blur", "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_atomic_float16_add, Extension::kSPV_EXT_shader_atomic_float_add, Extension::kSPV_EXT_shader_atomic_float_min_max, Extension::kSPV_EXT_shader_image_int64, 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_arbitrary_precision_fixed_point, Extension::kSPV_INTEL_arbitrary_precision_floating_point, Extension::kSPV_INTEL_arbitrary_precision_integers, Extension::kSPV_INTEL_blocking_pipes, Extension::kSPV_INTEL_debug_module, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_float_controls2, Extension::kSPV_INTEL_fp_fast_math_mode, Extension::kSPV_INTEL_fpga_buffer_location, Extension::kSPV_INTEL_fpga_cluster_attributes, Extension::kSPV_INTEL_fpga_loop_controls, Extension::kSPV_INTEL_fpga_memory_accesses, Extension::kSPV_INTEL_fpga_memory_attributes, Extension::kSPV_INTEL_fpga_reg, Extension::kSPV_INTEL_function_pointers, Extension::kSPV_INTEL_inline_assembly, Extension::kSPV_INTEL_io_pipes, Extension::kSPV_INTEL_kernel_attributes, Extension::kSPV_INTEL_long_constant_composite, Extension::kSPV_INTEL_loop_fuse, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_optnone, Extension::kSPV_INTEL_shader_integer_functions2, Extension::kSPV_INTEL_subgroups, Extension::kSPV_INTEL_unstructured_loop_controls, Extension::kSPV_INTEL_usm_storage_classes, Extension::kSPV_INTEL_variable_length_array, Extension::kSPV_INTEL_vector_compute, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_bit_instructions, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_expect_assume, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_fragment_shading_rate, Extension::kSPV_KHR_integer_dot_product, Extension::kSPV_KHR_linkonce_odr, 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_ray_query, Extension::kSPV_KHR_ray_tracing, 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_uniform_control_flow, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_terminate_invocation, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_KHR_workgroup_memory_explicit_layout, 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_ray_tracing_motion_blur, 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_atomic_float16_add", "SPV_EXT_shader_atomic_float_add", "SPV_EXT_shader_atomic_float_min_max", "SPV_EXT_shader_image_int64", "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_arbitrary_precision_fixed_point", "SPV_INTEL_arbitrary_precision_floating_point", "SPV_INTEL_arbitrary_precision_integers", "SPV_INTEL_blocking_pipes", "SPV_INTEL_debug_module", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_float_controls2", "SPV_INTEL_fp_fast_math_mode", "SPV_INTEL_fpga_buffer_location", "SPV_INTEL_fpga_cluster_attributes", "SPV_INTEL_fpga_loop_controls", "SPV_INTEL_fpga_memory_accesses", "SPV_INTEL_fpga_memory_attributes", "SPV_INTEL_fpga_reg", "SPV_INTEL_function_pointers", "SPV_INTEL_inline_assembly", "SPV_INTEL_io_pipes", "SPV_INTEL_kernel_attributes", "SPV_INTEL_long_constant_composite", "SPV_INTEL_loop_fuse", "SPV_INTEL_media_block_io", "SPV_INTEL_optnone", "SPV_INTEL_shader_integer_functions2", "SPV_INTEL_subgroups", "SPV_INTEL_unstructured_loop_controls", "SPV_INTEL_usm_storage_classes", "SPV_INTEL_variable_length_array", "SPV_INTEL_vector_compute", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_bit_instructions", "SPV_KHR_device_group", "SPV_KHR_expect_assume", "SPV_KHR_float_controls", "SPV_KHR_fragment_shading_rate", "SPV_KHR_integer_dot_product", "SPV_KHR_linkonce_odr", "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_ray_query", "SPV_KHR_ray_tracing", "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_uniform_control_flow", "SPV_KHR_subgroup_vote", "SPV_KHR_terminate_invocation", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_KHR_workgroup_memory_explicit_layout", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_bindless_texture", "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_ray_tracing_motion_blur", "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_atomic_float16_add, Extension::kSPV_EXT_shader_atomic_float_add, Extension::kSPV_EXT_shader_atomic_float_min_max, Extension::kSPV_EXT_shader_image_int64, 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_arbitrary_precision_fixed_point, Extension::kSPV_INTEL_arbitrary_precision_floating_point, Extension::kSPV_INTEL_arbitrary_precision_integers, Extension::kSPV_INTEL_blocking_pipes, Extension::kSPV_INTEL_debug_module, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_float_controls2, Extension::kSPV_INTEL_fp_fast_math_mode, Extension::kSPV_INTEL_fpga_buffer_location, Extension::kSPV_INTEL_fpga_cluster_attributes, Extension::kSPV_INTEL_fpga_loop_controls, Extension::kSPV_INTEL_fpga_memory_accesses, Extension::kSPV_INTEL_fpga_memory_attributes, Extension::kSPV_INTEL_fpga_reg, Extension::kSPV_INTEL_function_pointers, Extension::kSPV_INTEL_inline_assembly, Extension::kSPV_INTEL_io_pipes, Extension::kSPV_INTEL_kernel_attributes, Extension::kSPV_INTEL_long_constant_composite, Extension::kSPV_INTEL_loop_fuse, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_optnone, Extension::kSPV_INTEL_shader_integer_functions2, Extension::kSPV_INTEL_subgroups, Extension::kSPV_INTEL_unstructured_loop_controls, Extension::kSPV_INTEL_usm_storage_classes, Extension::kSPV_INTEL_variable_length_array, Extension::kSPV_INTEL_vector_compute, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_bit_instructions, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_expect_assume, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_fragment_shading_rate, Extension::kSPV_KHR_integer_dot_product, Extension::kSPV_KHR_linkonce_odr, 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_ray_query, Extension::kSPV_KHR_ray_tracing, 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_uniform_control_flow, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_terminate_invocation, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_KHR_workgroup_memory_explicit_layout, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_bindless_texture, 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_ray_tracing_motion_blur, 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(
@@ -502,6 +504,8 @@ const char* CapabilityToString(SpvCapability capability) {
return "FragmentShaderPixelInterlockEXT";
case SpvCapabilityDemoteToHelperInvocationEXT:
return "DemoteToHelperInvocationEXT";
case SpvCapabilityBindlessTextureNV:
return "BindlessTextureNV";
case SpvCapabilitySubgroupShuffleINTEL:
return "SubgroupShuffleINTEL";
case SpvCapabilitySubgroupBufferBlockIOINTEL:

View File

@@ -79,6 +79,7 @@ kSPV_KHR_variable_pointers,
kSPV_KHR_vulkan_memory_model,
kSPV_KHR_workgroup_memory_explicit_layout,
kSPV_NVX_multiview_per_view_attributes,
kSPV_NV_bindless_texture,
kSPV_NV_compute_shader_derivatives,
kSPV_NV_cooperative_matrix,
kSPV_NV_fragment_shader_barycentric,

View File

@@ -28,4 +28,5 @@
{27, "Embark Studios", "Rust GPU Compiler Backend", "Embark Studios Rust GPU Compiler Backend"},
{28, "gfx-rs community", "Naga", "gfx-rs community Naga"},
{29, "Mikkosoft Productions", "MSP Shader Compiler", "Mikkosoft Productions MSP Shader Compiler"},
{30, "SpvGenTwo community", "SpvGenTwo SPIR-V IR Tools", "SpvGenTwo community SpvGenTwo SPIR-V IR Tools"},
{30, "SpvGenTwo community", "SpvGenTwo SPIR-V IR Tools", "SpvGenTwo community SpvGenTwo SPIR-V IR Tools"},
{31, "Google", "Skia SkSL", "Google Skia SkSL"},

View File

@@ -2,6 +2,7 @@ static const SpvCapability pygen_variable_caps_Addresses[] = {SpvCapabilityAddre
static const SpvCapability pygen_variable_caps_ArbitraryPrecisionFixedPointINTEL[] = {SpvCapabilityArbitraryPrecisionFixedPointINTEL};
static const SpvCapability pygen_variable_caps_AsmINTEL[] = {SpvCapabilityAsmINTEL};
static const SpvCapability pygen_variable_caps_AtomicStorage[] = {SpvCapabilityAtomicStorage};
static const SpvCapability pygen_variable_caps_BindlessTextureNV[] = {SpvCapabilityBindlessTextureNV};
static const SpvCapability pygen_variable_caps_ClipDistance[] = {SpvCapabilityClipDistance};
static const SpvCapability pygen_variable_caps_ComputeDerivativeGroupLinearNV[] = {SpvCapabilityComputeDerivativeGroupLinearNV};
static const SpvCapability pygen_variable_caps_ComputeDerivativeGroupQuadsNV[] = {SpvCapabilityComputeDerivativeGroupQuadsNV};
@@ -194,6 +195,7 @@ static const spvtools::Extension pygen_variable_exts_SPV_KHR_vulkan_memory_model
static const spvtools::Extension pygen_variable_exts_SPV_KHR_workgroup_memory_explicit_layout[] = {spvtools::Extension::kSPV_KHR_workgroup_memory_explicit_layout};
static const spvtools::Extension pygen_variable_exts_SPV_NVX_multiview_per_view_attributes[] = {spvtools::Extension::kSPV_NVX_multiview_per_view_attributes};
static const spvtools::Extension pygen_variable_exts_SPV_NVX_multiview_per_view_attributesSPV_NV_mesh_shader[] = {spvtools::Extension::kSPV_NVX_multiview_per_view_attributes, spvtools::Extension::kSPV_NV_mesh_shader};
static const spvtools::Extension pygen_variable_exts_SPV_NV_bindless_texture[] = {spvtools::Extension::kSPV_NV_bindless_texture};
static const spvtools::Extension pygen_variable_exts_SPV_NV_compute_shader_derivatives[] = {spvtools::Extension::kSPV_NV_compute_shader_derivatives};
static const spvtools::Extension pygen_variable_exts_SPV_NV_cooperative_matrix[] = {spvtools::Extension::kSPV_NV_cooperative_matrix};
static const spvtools::Extension pygen_variable_exts_SPV_NV_fragment_shader_barycentric[] = {spvtools::Extension::kSPV_NV_fragment_shader_barycentric};
@@ -228,7 +230,8 @@ static const spv_operand_desc_t pygen_variable_ImageOperandsEntries[] = {
{"VolatileTexel", 0x0800, 1, pygen_variable_caps_VulkanMemoryModel, 1, pygen_variable_exts_SPV_KHR_vulkan_memory_model, {}, SPV_SPIRV_VERSION_WORD(1,5), 0xffffffffu},
{"VolatileTexelKHR", 0x0800, 1, pygen_variable_caps_VulkanMemoryModel, 1, pygen_variable_exts_SPV_KHR_vulkan_memory_model, {}, SPV_SPIRV_VERSION_WORD(1,5), 0xffffffffu},
{"SignExtend", 0x1000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,4), 0xffffffffu},
{"ZeroExtend", 0x2000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,4), 0xffffffffu}
{"ZeroExtend", 0x2000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,4), 0xffffffffu},
{"Offsets", 0x10000, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0), 0xffffffffu}
};
static const spv_operand_desc_t pygen_variable_FPFastMathModeEntries[] = {
@@ -726,6 +729,10 @@ static const spv_operand_desc_t pygen_variable_DecorationEntries[] = {
{"RestrictPointerEXT", 5355, 1, pygen_variable_caps_PhysicalStorageBufferAddresses, 2, pygen_variable_exts_SPV_EXT_physical_storage_bufferSPV_KHR_physical_storage_buffer, {}, SPV_SPIRV_VERSION_WORD(1,5), 0xffffffffu},
{"AliasedPointer", 5356, 1, pygen_variable_caps_PhysicalStorageBufferAddresses, 2, pygen_variable_exts_SPV_EXT_physical_storage_bufferSPV_KHR_physical_storage_buffer, {}, SPV_SPIRV_VERSION_WORD(1,5), 0xffffffffu},
{"AliasedPointerEXT", 5356, 1, pygen_variable_caps_PhysicalStorageBufferAddresses, 2, pygen_variable_exts_SPV_EXT_physical_storage_bufferSPV_KHR_physical_storage_buffer, {}, SPV_SPIRV_VERSION_WORD(1,5), 0xffffffffu},
{"BindlessSamplerNV", 5398, 1, pygen_variable_caps_BindlessTextureNV, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"BindlessImageNV", 5399, 1, pygen_variable_caps_BindlessTextureNV, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"BoundSamplerNV", 5400, 1, pygen_variable_caps_BindlessTextureNV, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"BoundImageNV", 5401, 1, pygen_variable_caps_BindlessTextureNV, 0, nullptr, {}, 0xffffffffu, 0xffffffffu},
{"SIMTCallINTEL", 5599, 1, pygen_variable_caps_VectorComputeINTEL, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0xffffffffu, 0xffffffffu},
{"ReferencedIndirectlyINTEL", 5602, 1, pygen_variable_caps_IndirectReferencesINTEL, 1, pygen_variable_exts_SPV_INTEL_function_pointers, {}, 0xffffffffu, 0xffffffffu},
{"ClobberINTEL", 5607, 1, pygen_variable_caps_AsmINTEL, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0xffffffffu, 0xffffffffu},
@@ -1079,6 +1086,7 @@ static const spv_operand_desc_t pygen_variable_CapabilityEntries[] = {
{"ShaderSMBuiltinsNV", 5373, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_NV_shader_sm_builtins, {}, 0xffffffffu, 0xffffffffu},
{"FragmentShaderPixelInterlockEXT", 5378, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_fragment_shader_interlock, {}, 0xffffffffu, 0xffffffffu},
{"DemoteToHelperInvocationEXT", 5379, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_demote_to_helper_invocation, {}, 0xffffffffu, 0xffffffffu},
{"BindlessTextureNV", 5390, 0, nullptr, 1, pygen_variable_exts_SPV_NV_bindless_texture, {}, 0xffffffffu, 0xffffffffu},
{"SubgroupShuffleINTEL", 5568, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu, 0xffffffffu},
{"SubgroupBufferBlockIOINTEL", 5569, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu, 0xffffffffu},
{"SubgroupImageBlockIOINTEL", 5570, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu, 0xffffffffu},

View File

@@ -517,6 +517,7 @@ typedef enum {
SPV_ENV_UNIVERSAL_1_5, // SPIR-V 1.5 latest revision, no other restrictions.
SPV_ENV_VULKAN_1_2, // Vulkan 1.2 latest revision.
SPV_ENV_MAX // Keep this as the last enum value.
} spv_target_env;
// SPIR-V Validator can be parameterized with the following Universal Limits.

View File

@@ -833,6 +833,13 @@ Optimizer::PassToken CreateFixStorageClassPass();
// inclusive.
Optimizer::PassToken CreateGraphicsRobustAccessPass();
// Create a pass to replace a descriptor access using variable index.
// This pass replaces every access using a variable index to array variable
// |desc| that has a DescriptorSet and Binding decorations with a constant
// element of the array. In order to replace the access using a variable index
// with the constant element, it uses a switch statement.
Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass();
// Create descriptor scalar replacement pass.
// This pass replaces every array variable |desc| that has a DescriptorSet and
// Binding decorations with a new variable for each element of the array.

View File

@@ -34,6 +34,7 @@
#include "source/opt/pass_manager.h"
#include "source/opt/remove_duplicates_pass.h"
#include "source/opt/type_manager.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/util/make_unique.h"
#include "spirv-tools/libspirv.hpp"
@@ -207,7 +208,7 @@ spv_result_t GenerateHeader(const MessageConsumer& consumer,
header->magic_number = SpvMagicNumber;
header->version = version;
header->generator = 17u;
header->generator = SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_LINKER, 0);
header->bound = max_id_bound;
header->reserved = 0u;

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2017 The Khronos Group Inc.
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG Inc.
// Copyright (c) 2018 Google LLC
// Copyright (c) 2018-2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
#include "source/cfa.h"
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/eliminate_dead_functions_util.h"
#include "source/opt/ir_builder.h"
#include "source/opt/iterator.h"
#include "source/opt/reflect.h"
#include "source/spirv_constant.h"
@@ -38,6 +39,7 @@ const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
const uint32_t kCopyMemoryTargetAddrInIdx = 0;
const uint32_t kCopyMemorySourceAddrInIdx = 1;
const uint32_t kLoadSourceAddrInIdx = 0;
const uint32_t kDebugDeclareOperandVariableIndex = 5;
const uint32_t kGlobalVariableVariableIndex = 12;
@@ -95,16 +97,21 @@ bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) {
storageClass;
}
bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
bool AggressiveDCEPass::IsLocalVar(uint32_t varId, Function* func) {
if (IsVarOfStorage(varId, SpvStorageClassFunction)) {
return true;
}
if (!private_like_local_) {
if (!IsVarOfStorage(varId, SpvStorageClassPrivate) &&
!IsVarOfStorage(varId, SpvStorageClassWorkgroup)) {
return false;
}
return IsVarOfStorage(varId, SpvStorageClassPrivate) ||
IsVarOfStorage(varId, SpvStorageClassWorkgroup);
// For a variable in the Private or WorkGroup storage class, the variable will
// get a new instance for every call to an entry point. If the entry point
// does not have a call, then no other function can read or write to that
// instance of the variable.
return IsEntryPointWithNoCalls(func);
}
void AggressiveDCEPass::AddStores(Function* func, uint32_t ptrId) {
@@ -160,14 +167,6 @@ bool AggressiveDCEPass::AllExtensionsSupported() const {
return true;
}
bool AggressiveDCEPass::IsDead(Instruction* inst) {
if (IsLive(inst)) return false;
if ((inst->IsBranch() || inst->opcode() == SpvOpUnreachable) &&
context()->get_instr_block(inst)->GetMergeInst() == nullptr)
return false;
return true;
}
bool AggressiveDCEPass::IsTargetDead(Instruction* inst) {
const uint32_t tId = inst->GetSingleWordInOperand(0);
Instruction* tInst = get_def_use_mgr()->GetDef(tId);
@@ -184,12 +183,12 @@ bool AggressiveDCEPass::IsTargetDead(Instruction* inst) {
});
return dead;
}
return IsDead(tInst);
return !IsLive(tInst);
}
void AggressiveDCEPass::ProcessLoad(Function* func, uint32_t varId) {
// Only process locals
if (!IsLocalVar(varId)) return;
if (!IsLocalVar(varId, func)) return;
// Return if already processed
if (live_local_vars_.find(varId) != live_local_vars_.end()) return;
// Mark all stores to varId as live
@@ -265,170 +264,36 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
}
bool AggressiveDCEPass::AggressiveDCE(Function* func) {
// Mark function parameters as live.
AddToWorklist(&func->DefInst());
MarkFunctionParameterAsLive(func);
std::list<BasicBlock*> structuredOrder;
cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
bool modified = false;
std::list<BasicBlock*> structured_order;
cfg()->ComputeStructuredOrder(func, &*func->begin(), &structured_order);
live_local_vars_.clear();
InitializeWorkList(func, structuredOrder);
InitializeWorkList(func, structured_order);
ProcessWorkList(func);
return KillDeadInstructions(func, structured_order);
}
// Perform closure on live instruction set.
while (!worklist_.empty()) {
Instruction* liveInst = worklist_.front();
// Add all operand instructions of Debug[No]Lines
for (auto& lineInst : liveInst->dbg_line_insts()) {
if (lineInst.IsDebugLineInst()) {
lineInst.ForEachInId([this](const uint32_t* iid) {
AddToWorklist(get_def_use_mgr()->GetDef(*iid));
});
}
}
// Add all operand instructions if not already live
liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) {
Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
// Do not add label if an operand of a branch. This is not needed
// as part of live code discovery and can create false live code,
// for example, the branch to a header of a loop.
if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return;
AddToWorklist(inInst);
});
if (liveInst->type_id() != 0) {
AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id()));
}
BasicBlock* basic_block = context()->get_instr_block(liveInst);
if (basic_block != nullptr) {
AddToWorklist(basic_block->GetLabelInst());
}
// If in a structured if or loop construct, add the controlling
// conditional branch and its merge.
BasicBlock* blk = context()->get_instr_block(liveInst);
Instruction* branchInst = GetHeaderBranch(blk);
if (branchInst != nullptr) {
AddToWorklist(branchInst);
Instruction* mergeInst = GetMergeInstruction(branchInst);
AddToWorklist(mergeInst);
}
// If the block is a header, add the next outermost controlling
// conditional branch and its merge.
Instruction* nextBranchInst = GetBranchForNextHeader(blk);
if (nextBranchInst != nullptr) {
AddToWorklist(nextBranchInst);
Instruction* mergeInst = GetMergeInstruction(nextBranchInst);
AddToWorklist(mergeInst);
}
// If local load, add all variable's stores if variable not already live
if (liveInst->opcode() == SpvOpLoad || liveInst->IsAtomicWithLoad()) {
uint32_t varId;
(void)GetPtr(liveInst, &varId);
if (varId != 0) {
ProcessLoad(func, varId);
}
// Process memory copies like loads
} else if (liveInst->opcode() == SpvOpCopyMemory ||
liveInst->opcode() == SpvOpCopyMemorySized) {
uint32_t varId;
(void)GetPtr(liveInst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx),
&varId);
if (varId != 0) {
ProcessLoad(func, varId);
}
// If DebugDeclare, process as load of variable
} else if (liveInst->GetCommonDebugOpcode() ==
CommonDebugInfoDebugDeclare) {
uint32_t varId =
liveInst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
ProcessLoad(func, varId);
// If DebugValue with Deref, process as load of variable
} else if (liveInst->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
uint32_t varId = context()
->get_debug_info_mgr()
->GetVariableIdOfDebugValueUsedForDeclare(liveInst);
if (varId != 0) ProcessLoad(func, varId);
// If merge, add other branches that are part of its control structure
} else if (liveInst->opcode() == SpvOpLoopMerge ||
liveInst->opcode() == SpvOpSelectionMerge) {
AddBreaksAndContinuesToWorklist(liveInst);
// If function call, treat as if it loads from all pointer arguments
} else if (liveInst->opcode() == SpvOpFunctionCall) {
liveInst->ForEachInId([this, func](const uint32_t* iid) {
// Skip non-ptr args
if (!IsPtr(*iid)) return;
uint32_t varId;
(void)GetPtr(*iid, &varId);
ProcessLoad(func, varId);
});
// If function parameter, treat as if it's result id is loaded from
} else if (liveInst->opcode() == SpvOpFunctionParameter) {
ProcessLoad(func, liveInst->result_id());
// We treat an OpImageTexelPointer as a load of the pointer, and
// that value is manipulated to get the result.
} else if (liveInst->opcode() == SpvOpImageTexelPointer) {
uint32_t varId;
(void)GetPtr(liveInst, &varId);
if (varId != 0) {
ProcessLoad(func, varId);
}
}
// Add OpDecorateId instructions that apply to this instruction to the work
// list. We use the decoration manager to look through the group
// decorations to get to the OpDecorate* instructions themselves.
auto decorations =
get_decoration_mgr()->GetDecorationsFor(liveInst->result_id(), false);
for (Instruction* dec : decorations) {
// We only care about OpDecorateId instructions because the are the only
// decorations that will reference an id that will have to be kept live
// because of that use.
if (dec->opcode() != SpvOpDecorateId) {
continue;
}
if (dec->GetSingleWordInOperand(1) ==
SpvDecorationHlslCounterBufferGOOGLE) {
// These decorations should not force the use id to be live. It will be
// removed if either the target or the in operand are dead.
continue;
}
AddToWorklist(dec);
}
// Add DebugScope and DebugInlinedAt for |liveInst| to the work list.
if (liveInst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
auto* scope = get_def_use_mgr()->GetDef(
liveInst->GetDebugScope().GetLexicalScope());
AddToWorklist(scope);
}
if (liveInst->GetDebugInlinedAt() != kNoInlinedAt) {
auto* inlined_at =
get_def_use_mgr()->GetDef(liveInst->GetDebugInlinedAt());
AddToWorklist(inlined_at);
}
worklist_.pop();
}
// Kill dead instructions and remember dead blocks
for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) {
uint32_t mergeBlockId = 0;
(*bi)->ForEachInst([this, &modified, &mergeBlockId](Instruction* inst) {
if (!IsDead(inst)) return;
bool AggressiveDCEPass::KillDeadInstructions(
const Function* func, std::list<BasicBlock*>& structured_order) {
bool modified = false;
for (auto bi = structured_order.begin(); bi != structured_order.end();) {
uint32_t merge_block_id = 0;
(*bi)->ForEachInst([this, &modified, &merge_block_id](Instruction* inst) {
if (IsLive(inst)) return;
if (inst->opcode() == SpvOpLabel) return;
// If dead instruction is selection merge, remember merge block
// for new branch at end of block
if (inst->opcode() == SpvOpSelectionMerge ||
inst->opcode() == SpvOpLoopMerge)
mergeBlockId = inst->GetSingleWordInOperand(0);
merge_block_id = inst->GetSingleWordInOperand(0);
to_kill_.push_back(inst);
modified = true;
});
// If a structured if or loop was deleted, add a branch to its merge
// block, and traverse to the merge block and continue processing there.
// We know the block still exists because the label is not deleted.
if (mergeBlockId != 0) {
AddBranch(mergeBlockId, *bi);
for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
if (merge_block_id != 0) {
AddBranch(merge_block_id, *bi);
for (++bi; (*bi)->id() != merge_block_id; ++bi) {
}
auto merge_terminator = (*bi)->terminator();
@@ -451,94 +316,250 @@ bool AggressiveDCEPass::AggressiveDCE(Function* func) {
live_insts_.Set(merge_terminator->unique_id());
}
} else {
Instruction* inst = (*bi)->terminator();
if (!IsLive(inst)) {
// If the terminator is not live, this block has no live instructions,
// and it will be unreachable.
AddUnreachable(*bi);
}
++bi;
}
}
return modified;
}
void AggressiveDCEPass::ProcessWorkList(Function* func) {
while (!worklist_.empty()) {
Instruction* live_inst = worklist_.front();
worklist_.pop();
AddOperandsToWorkList(live_inst);
MarkBlockAsLive(live_inst);
MarkLoadedVariablesAsLive(func, live_inst);
AddDecorationsToWorkList(live_inst);
AddDebugInstructionsToWorkList(live_inst);
}
}
void AggressiveDCEPass::AddDebugInstructionsToWorkList(
const Instruction* inst) {
for (auto& line_inst : inst->dbg_line_insts()) {
if (line_inst.IsDebugLineInst()) {
AddOperandsToWorkList(&line_inst);
}
}
if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
auto* scope =
get_def_use_mgr()->GetDef(inst->GetDebugScope().GetLexicalScope());
AddToWorklist(scope);
}
if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
auto* inlined_at = get_def_use_mgr()->GetDef(inst->GetDebugInlinedAt());
AddToWorklist(inlined_at);
}
}
void AggressiveDCEPass::AddDecorationsToWorkList(const Instruction* inst) {
// Add OpDecorateId instructions that apply to this instruction to the work
// list. We use the decoration manager to look through the group
// decorations to get to the OpDecorate* instructions themselves.
auto decorations =
get_decoration_mgr()->GetDecorationsFor(inst->result_id(), false);
for (Instruction* dec : decorations) {
// We only care about OpDecorateId instructions because the are the only
// decorations that will reference an id that will have to be kept live
// because of that use.
if (dec->opcode() != SpvOpDecorateId) {
continue;
}
if (dec->GetSingleWordInOperand(1) ==
SpvDecorationHlslCounterBufferGOOGLE) {
// These decorations should not force the use id to be live. It will be
// removed if either the target or the in operand are dead.
continue;
}
AddToWorklist(dec);
}
}
void AggressiveDCEPass::MarkLoadedVariablesAsLive(Function* func,
Instruction* inst) {
std::vector<uint32_t> live_variables = GetLoadedVariables(inst);
for (uint32_t var_id : live_variables) {
ProcessLoad(func, var_id);
}
}
std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariables(Instruction* inst) {
if (inst->opcode() == SpvOpFunctionCall) {
return GetLoadedVariablesFromFunctionCall(inst);
}
uint32_t var_id = GetLoadedVariableFromNonFunctionCalls(inst);
if (var_id == 0) {
return {};
}
return {var_id};
}
uint32_t AggressiveDCEPass::GetLoadedVariableFromNonFunctionCalls(
Instruction* inst) {
std::vector<uint32_t> live_variables;
if (inst->IsAtomicWithLoad()) {
return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
}
switch (inst->opcode()) {
case SpvOpLoad:
case SpvOpImageTexelPointer:
return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
case SpvOpCopyMemory:
case SpvOpCopyMemorySized:
return GetVariableId(
inst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx));
default:
break;
}
switch (inst->GetCommonDebugOpcode()) {
case CommonDebugInfoDebugDeclare:
return inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
case CommonDebugInfoDebugValue: {
analysis::DebugInfoManager* debug_info_mgr =
context()->get_debug_info_mgr();
return debug_info_mgr->GetVariableIdOfDebugValueUsedForDeclare(inst);
}
default:
break;
}
return 0;
}
std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariablesFromFunctionCall(
const Instruction* inst) {
assert(inst->opcode() == SpvOpFunctionCall);
std::vector<uint32_t> live_variables;
inst->ForEachInId([this, &live_variables](const uint32_t* operand_id) {
if (!IsPtr(*operand_id)) return;
uint32_t var_id = GetVariableId(*operand_id);
live_variables.push_back(var_id);
});
return live_variables;
}
uint32_t AggressiveDCEPass::GetVariableId(uint32_t ptr_id) {
assert(IsPtr(ptr_id) &&
"Cannot get the variable when input is not a pointer.");
uint32_t varId = 0;
(void)GetPtr(ptr_id, &varId);
return varId;
}
void AggressiveDCEPass::MarkBlockAsLive(Instruction* inst) {
BasicBlock* basic_block = context()->get_instr_block(inst);
if (basic_block == nullptr) {
return;
}
// If we intend to keep this instruction, we need the block label and
// block terminator to have a valid block for the instruction.
AddToWorklist(basic_block->GetLabelInst());
// We need to mark the successors blocks that follow as live. If this is
// header of the merge construct, the construct may be folded, but we will
// definitely need the merge label. If it is not a construct, the terminator
// must be live, and the successor blocks will be marked as live when
// processing the terminator.
uint32_t merge_id = basic_block->MergeBlockIdIfAny();
if (merge_id == 0) {
AddToWorklist(basic_block->terminator());
} else {
AddToWorklist(context()->get_def_use_mgr()->GetDef(merge_id));
}
// Mark the structured control flow constructs that contains this block as
// live. If |inst| is an instruction in the loop header, then it is part of
// the loop, so the loop construct must be live. We exclude the label because
// it does not matter how many times it is executed. This could be extended
// to more instructions, but we will need it for now.
if (inst->opcode() != SpvOpLabel)
MarkLoopConstructAsLiveIfLoopHeader(basic_block);
Instruction* next_branch_inst = GetBranchForNextHeader(basic_block);
if (next_branch_inst != nullptr) {
AddToWorklist(next_branch_inst);
Instruction* mergeInst = GetMergeInstruction(next_branch_inst);
AddToWorklist(mergeInst);
}
if (inst->opcode() == SpvOpLoopMerge ||
inst->opcode() == SpvOpSelectionMerge) {
AddBreaksAndContinuesToWorklist(inst);
}
}
void AggressiveDCEPass::MarkLoopConstructAsLiveIfLoopHeader(
BasicBlock* basic_block) {
// If this is the header for a loop, then loop structure needs to keep as well
// because the loop header is also part of the loop.
Instruction* merge_inst = basic_block->GetLoopMergeInst();
if (merge_inst != nullptr) {
AddToWorklist(basic_block->terminator());
AddToWorklist(merge_inst);
}
}
void AggressiveDCEPass::AddOperandsToWorkList(const Instruction* inst) {
inst->ForEachInId([this](const uint32_t* iid) {
Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
AddToWorklist(inInst);
});
if (inst->type_id() != 0) {
AddToWorklist(get_def_use_mgr()->GetDef(inst->type_id()));
}
}
void AggressiveDCEPass::InitializeWorkList(
Function* func, std::list<BasicBlock*>& structuredOrder) {
Function* func, std::list<BasicBlock*>& structured_order) {
AddToWorklist(&func->DefInst());
MarkFunctionParameterAsLive(func);
MarkFirstBlockAsLive(func);
// Add instructions with external side effects to the worklist. Also add
// branches that are not attached to a structured construct.
// TODO(s-perron): The handling of branch seems to be adhoc. This needs to be
// cleaned up.
bool call_in_func = false;
bool func_is_entry_point = false;
// TODO(s-perron): We need to check if this is actually needed. In cases
// where private variable can be treated as if they are function scope, the
// private-to-local pass should be able to change them to function scope.
std::vector<Instruction*> private_stores;
for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) {
for (auto& bi : structured_order) {
for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
SpvOp op = ii->opcode();
if (ii->IsBranch()) {
continue;
}
switch (op) {
case SpvOpStore: {
uint32_t varId;
(void)GetPtr(&*ii, &varId);
// Mark stores as live if their variable is not function scope
// and is not private scope. Remember private stores for possible
// later inclusion. We cannot call IsLocalVar at this point because
// private_like_local_ has not been set yet.
if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
IsVarOfStorage(varId, SpvStorageClassWorkgroup))
private_stores.push_back(&*ii);
else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
AddToWorklist(&*ii);
uint32_t var_id = 0;
(void)GetPtr(&*ii, &var_id);
if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
} break;
case SpvOpCopyMemory:
case SpvOpCopyMemorySized: {
uint32_t varId;
(void)GetPtr(ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx),
&varId);
if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
IsVarOfStorage(varId, SpvStorageClassWorkgroup))
private_stores.push_back(&*ii);
else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
AddToWorklist(&*ii);
} break;
case SpvOpSwitch:
case SpvOpBranch:
case SpvOpBranchConditional:
case SpvOpUnreachable: {
bool branchRelatedToConstruct =
(GetMergeInstruction(&*ii) == nullptr &&
GetHeaderBlock(context()->get_instr_block(&*ii)) == nullptr);
if (branchRelatedToConstruct) {
AddToWorklist(&*ii);
}
uint32_t var_id = 0;
uint32_t target_addr_id =
ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx);
(void)GetPtr(target_addr_id, &var_id);
if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
} break;
case SpvOpLoopMerge:
case SpvOpSelectionMerge:
case SpvOpUnreachable:
break;
default: {
// Function calls, atomics, function params, function returns, etc.
if (!ii->IsOpcodeSafeToDelete()) {
AddToWorklist(&*ii);
}
// Remember function calls
if (op == SpvOpFunctionCall) call_in_func = true;
} break;
}
}
}
// See if current function is an entry point
for (auto& ei : get_module()->entry_points()) {
if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) ==
func->result_id()) {
func_is_entry_point = true;
break;
}
}
// If the current function is an entry point and has no function calls,
// we can optimize private variables as locals
private_like_local_ = func_is_entry_point && !call_in_func;
// If privates are not like local, add their stores to worklist
if (!private_like_local_)
for (auto& ps : private_stores) AddToWorklist(ps);
}
void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
@@ -596,16 +617,25 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
}
// For each DebugInfo GlobalVariable keep all operands except the Variable.
// Later, if the variable is dead, we will set the operand to DebugInfoNone.
// Later, if the variable is killed with KillInst(), we will set the operand
// to DebugInfoNone. Create and save DebugInfoNone now for this possible
// later use. This is slightly unoptimal, but it avoids generating it during
// instruction killing when the module is not consistent.
bool debug_global_seen = false;
for (auto& dbg : get_module()->ext_inst_debuginfo()) {
if (dbg.GetCommonDebugOpcode() != CommonDebugInfoDebugGlobalVariable)
continue;
debug_global_seen = true;
dbg.ForEachInId([this](const uint32_t* iid) {
Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
if (inInst->opcode() == SpvOpVariable) return;
AddToWorklist(inInst);
Instruction* in_inst = get_def_use_mgr()->GetDef(*iid);
if (in_inst->opcode() == SpvOpVariable) return;
AddToWorklist(in_inst);
});
}
if (debug_global_seen) {
auto dbg_none = context()->get_debug_info_mgr()->GetDebugInfoNone();
AddToWorklist(dbg_none);
}
}
Pass::Status AggressiveDCEPass::ProcessImpl() {
@@ -743,7 +773,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
uint32_t counter_buffer_id = annotation->GetSingleWordInOperand(2);
Instruction* counter_buffer_inst =
get_def_use_mgr()->GetDef(counter_buffer_id);
if (IsDead(counter_buffer_inst)) {
if (!IsLive(counter_buffer_inst)) {
context()->KillInst(annotation);
modified = true;
}
@@ -758,7 +788,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
for (uint32_t i = 1; i < annotation->NumOperands();) {
Instruction* opInst =
get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
if (IsDead(opInst)) {
if (!IsLive(opInst)) {
// Don't increment |i|.
annotation->RemoveOperand(i);
modified = true;
@@ -785,7 +815,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
for (uint32_t i = 1; i < annotation->NumOperands();) {
Instruction* opInst =
get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
if (IsDead(opInst)) {
if (!IsLive(opInst)) {
// Don't increment |i|.
annotation->RemoveOperand(i + 1);
annotation->RemoveOperand(i);
@@ -819,13 +849,13 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
}
for (auto& dbg : get_module()->ext_inst_debuginfo()) {
if (!IsDead(&dbg)) continue;
if (IsLive(&dbg)) continue;
// Save GlobalVariable if its variable is live, otherwise null out variable
// index
if (dbg.GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) {
auto var_id = dbg.GetSingleWordOperand(kGlobalVariableVariableIndex);
Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
if (!IsDead(var_inst)) continue;
if (IsLive(var_inst)) continue;
context()->ForgetUses(&dbg);
dbg.SetOperand(
kGlobalVariableVariableIndex,
@@ -840,7 +870,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
// Since ADCE is disabled for non-shaders, we don't check for export linkage
// attributes here.
for (auto& val : get_module()->types_values()) {
if (IsDead(&val)) {
if (!IsLive(&val)) {
// Save forwarded pointer if pointer is live since closure does not mark
// this live as it does not have a result id. This is a little too
// conservative since it is not known if the structure type that needed
@@ -848,7 +878,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
if (val.opcode() == SpvOpTypeForwardPointer) {
uint32_t ptr_ty_id = val.GetSingleWordInOperand(0);
Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
if (!IsDead(ptr_ty_inst)) continue;
if (IsLive(ptr_ty_inst)) continue;
}
to_kill_.push_back(&val);
modified = true;
@@ -867,7 +897,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
} else {
auto* var =
get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
if (!IsDead(var)) {
if (IsLive(var)) {
new_operands.push_back(entry.GetInOperand(i));
}
}
@@ -1018,5 +1048,43 @@ bool AggressiveDCEPass::BlockIsInConstruct(BasicBlock* header_block,
return false;
}
bool AggressiveDCEPass::IsEntryPointWithNoCalls(Function* func) {
auto cached_result = entry_point_with_no_calls_cache_.find(func->result_id());
if (cached_result != entry_point_with_no_calls_cache_.end()) {
return cached_result->second;
}
bool result = IsEntryPoint(func) && !HasCall(func);
entry_point_with_no_calls_cache_[func->result_id()] = result;
return result;
}
bool AggressiveDCEPass::IsEntryPoint(Function* func) {
for (const Instruction& entry_point : get_module()->entry_points()) {
uint32_t entry_point_id =
entry_point.GetSingleWordInOperand(kEntryPointFunctionIdInIdx);
if (entry_point_id == func->result_id()) {
return true;
}
}
return false;
}
bool AggressiveDCEPass::HasCall(Function* func) {
return !func->WhileEachInst(
[](Instruction* inst) { return inst->opcode() != SpvOpFunctionCall; });
}
void AggressiveDCEPass::MarkFirstBlockAsLive(Function* func) {
BasicBlock* first_block = &*func->begin();
MarkBlockAsLive(first_block->GetLabelInst());
}
void AggressiveDCEPass::AddUnreachable(BasicBlock*& block) {
InstructionBuilder builder(
context(), block,
IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
builder.AddUnreachable();
}
} // namespace opt
} // namespace spvtools

View File

@@ -67,19 +67,16 @@ class AggressiveDCEPass : public MemPass {
// be 0 or the result of an instruction.
bool IsVarOfStorage(uint32_t varId, uint32_t storageClass);
// Return true if |varId| is variable of function storage class or is
// private variable and privates can be optimized like locals (see
// privates_like_local_).
bool IsLocalVar(uint32_t varId);
// Return true if the instance of the variable |varId| can only be access in
// |func|. For example, a function scope variable, or a private variable
// where |func| is an entry point with no function calls.
bool IsLocalVar(uint32_t varId, Function* func);
// Return true if |inst| is marked live.
bool IsLive(const Instruction* inst) const {
return live_insts_.Get(inst->unique_id());
}
// Returns true if |inst| is dead.
bool IsDead(Instruction* inst);
// Adds entry points, execution modes and workgroup size decorations to the
// worklist for processing with the first function.
void InitializeModuleScopeLiveInstructions();
@@ -139,7 +136,50 @@ class AggressiveDCEPass : public MemPass {
// Adds instructions which must be kept because of they have side-effects
// that ADCE cannot model to the work list.
void InitializeWorkList(Function* func,
std::list<BasicBlock*>& structuredOrder);
std::list<BasicBlock*>& structured_order);
// Process each instruction in the work list by marking any instruction that
// that it depends on as live, and adding it to the work list. The work list
// will be empty at the end.
void ProcessWorkList(Function* func);
// Kills any instructions in |func| that have not been marked as live.
bool KillDeadInstructions(const Function* func,
std::list<BasicBlock*>& structured_order);
// Adds the instructions that define the operands of |inst| to the work list.
void AddOperandsToWorkList(const Instruction* inst);
// Marks all of the labels and branch that inst requires as live.
void MarkBlockAsLive(Instruction* inst);
// Marks any variables from which |inst| may require data as live.
void MarkLoadedVariablesAsLive(Function* func, Instruction* inst);
// Returns the id of the variable that |ptr_id| point to. |ptr_id| must be a
// value whose type is a pointer.
uint32_t GetVariableId(uint32_t ptr_id);
// Returns all of the ids for the variables from which |inst| will load data.
std::vector<uint32_t> GetLoadedVariables(Instruction* inst);
// Returns all of the ids for the variables from which |inst| will load data.
// The opcode of |inst| must be OpFunctionCall.
std::vector<uint32_t> GetLoadedVariablesFromFunctionCall(
const Instruction* inst);
// Returns the id of the variable from which |inst| will load data. |inst|
// must not be an OpFunctionCall. Returns 0 if no data is read or the
// variable cannot be determined. Note that in logical addressing mode the
// latter is not possible for function and private storage class because there
// cannot be variable pointers pointing to those storage classes.
uint32_t GetLoadedVariableFromNonFunctionCalls(Instruction* inst);
// Adds all decorations of |inst| to the work list.
void AddDecorationsToWorkList(const Instruction* inst);
// Adds all debug instruction associated with |inst| to the work list.
void AddDebugInstructionsToWorkList(const Instruction* inst);
// Marks all of the OpFunctionParameter instructions in |func| as live.
void MarkFunctionParameterAsLive(const Function* func);
@@ -164,8 +204,29 @@ class AggressiveDCEPass : public MemPass {
// Returns true if |bb| is in the construct with header |header_block|.
bool BlockIsInConstruct(BasicBlock* header_block, BasicBlock* bb);
// True if current function is entry point and has no function calls.
bool private_like_local_;
// Returns true if |func| is an entry point that does not have any function
// calls.
bool IsEntryPointWithNoCalls(Function* func);
// Returns true if |func| is an entry point.
bool IsEntryPoint(Function* func);
// Returns true if |func| contains a function call.
bool HasCall(Function* func);
// Marks the first block, which is the entry block, in |func| as live.
void MarkFirstBlockAsLive(Function* func);
// Adds an OpUnreachable instruction at the end of |block|.
void AddUnreachable(BasicBlock*& block);
// Marks the OpLoopMerge and the terminator in |basic_block| as live if
// |basic_block| is a loop header.
void MarkLoopConstructAsLiveIfLoopHeader(BasicBlock* basic_block);
// The cached results for |IsEntryPointWithNoCalls|. It maps the function's
// result id to the return value.
std::unordered_map<uint32_t, bool> entry_point_with_no_calls_cache_;
// Live Instruction Worklist. An instruction is added to this list
// if it might have a side effect, either directly or indirectly.

View File

@@ -44,7 +44,7 @@ bool BlockMergePass::MergeBlocks(Function* func) {
Pass::Status BlockMergePass::Process() {
// Process all entry point functions.
ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@@ -171,12 +171,17 @@ void MergeWithSuccessor(IRContext* context, Function* func,
// and OpBranchConditional.
auto terminator = bi->terminator();
auto& vec = terminator->dbg_line_insts();
auto& new_vec = merge_inst->dbg_line_insts();
new_vec.insert(new_vec.end(), vec.begin(), vec.end());
terminator->ClearDbgLineInsts();
for (auto& l_inst : new_vec)
context->get_def_use_mgr()->AnalyzeInstDefUse(&l_inst);
if (vec.size() > 0) {
merge_inst->ClearDbgLineInsts();
auto& new_vec = merge_inst->dbg_line_insts();
new_vec.insert(new_vec.end(), vec.begin(), vec.end());
terminator->ClearDbgLineInsts();
for (auto& l_inst : new_vec)
context->get_def_use_mgr()->AnalyzeInstDefUse(&l_inst);
}
// Clear debug scope of terminator to avoid DebugScope
// emitted between terminator and merge.
terminator->SetDebugScope(DebugScope(kNoDebugScope, kNoInlinedAt));
// Move the merge instruction to just before the terminator.
merge_inst->InsertBefore(terminator);
}

View File

@@ -291,6 +291,10 @@ bool CCPPass::ReplaceValues() {
}
bool CCPPass::PropagateConstants(Function* fp) {
if (fp->IsDeclaration()) {
return false;
}
// Mark function parameters as varying.
fp->ForEachParam([this](const Instruction* inst) {
values_[inst->result_id()] = kVaryingSSAId;

View File

@@ -34,6 +34,10 @@ Pass::Status CombineAccessChains::Process() {
}
bool CombineAccessChains::ProcessFunction(Function& function) {
if (function.IsDeclaration()) {
return false;
}
bool modified = false;
cfg()->ForEachBlockInReversePostOrder(

View File

@@ -217,7 +217,8 @@ Instruction* ConstantManager::BuildInstructionAndAddToModule(
auto* new_inst_ptr = new_inst.get();
*pos = pos->InsertBefore(std::move(new_inst));
++(*pos);
context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr);
if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr);
MapConstantToInst(new_const, new_inst_ptr);
return new_inst_ptr;
}

View File

@@ -340,7 +340,7 @@ Pass::Status ConvertToHalfPass::ProcessImpl() {
Pass::ProcessFunction pfn = [this](Function* fp) {
return ProcessFunction(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
// If modified, make sure module has Float16 capability
if (modified) context()->AddCapability(SpvCapabilityFloat16);
// Remove all RelaxedPrecision decorations from instructions and globals

View File

@@ -40,6 +40,10 @@ bool IsDebugDeclareOrValue(Instruction* di) {
Pass::Status CopyPropagateArrays::Process() {
bool modified = false;
for (Function& function : *get_module()) {
if (function.IsDeclaration()) {
continue;
}
BasicBlock* entry_bb = &*function.begin();
for (auto var_inst = entry_bb->begin(); var_inst->opcode() == SpvOpVariable;

View File

@@ -346,6 +346,7 @@ bool DeadBranchElimPass::FixPhiNodesInLiveBlocks(
if (operands.size() == 4) {
// First input data operands is at index 2.
uint32_t replId = operands[2u].words[0];
context()->KillNamesAndDecorates(inst->result_id());
context()->ReplaceAllUsesWith(inst->result_id(), replId);
iter = context()->KillInst(&*inst);
} else {
@@ -419,6 +420,10 @@ bool DeadBranchElimPass::EraseDeadBlocks(
}
bool DeadBranchElimPass::EliminateDeadBranches(Function* func) {
if (func->IsDeclaration()) {
return false;
}
bool modified = false;
std::unordered_set<BasicBlock*> live_blocks;
modified |= MarkLiveBlocks(func, &live_blocks);

View File

@@ -256,7 +256,7 @@ Pass::Status DeadInsertElimPass::Process() {
ProcessFunction pfn = [this](Function* fp) {
return EliminateDeadInserts(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@@ -146,6 +146,25 @@ void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id,
}
}
// Create new constant directly into global value area, bypassing the
// Constant manager. This is used when the DefUse or Constant managers
// are invalid and cannot be regenerated due to the module being in an
// inconsistant state e.g. in the middle of significant modification
// such as inlining. Invalidate Constant and DefUse managers if used.
uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) {
uint32_t id = context->TakeNextId();
std::unique_ptr<Instruction> new_const(new Instruction(
context, SpvOpConstant, context->get_type_mgr()->GetUIntTypeId(), id,
{
{spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER,
{const_value}},
}));
context->module()->AddGlobalValue(std::move(new_const));
context->InvalidateAnalyses(IRContext::kAnalysisConstants);
context->InvalidateAnalyses(IRContext::kAnalysisDefUse);
return id;
}
uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line,
const DebugScope& scope) {
uint32_t setId = GetDbgSetImportId();
@@ -194,10 +213,18 @@ uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line,
line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex);
// If we need the line number as an ID, generate that constant now.
// If Constant or DefUse managers are invalid, generate constant
// directly into the global value section of the module; do not
// use Constant manager which may attempt to invoke building of the
// DefUse manager which cannot be done during inlining. The extra
// constants that may be generated here is likely not significant
// and will likely be cleaned up in later passes.
if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID) {
uint32_t line_id =
context()->get_constant_mgr()->GetUIntConst(line_number);
line_number = line_id;
if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse) ||
!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisConstants))
line_number = AddNewConstInGlobals(context(), line_number);
else
line_number = context()->get_constant_mgr()->GetUIntConst(line_number);
}
}

View File

@@ -490,6 +490,14 @@ void DecorationManager::ForEachDecoration(
});
}
bool DecorationManager::HasDecoration(uint32_t id, uint32_t decoration) {
bool has_decoration = false;
ForEachDecoration(id, decoration, [&has_decoration](const Instruction&) {
has_decoration = true;
});
return has_decoration;
}
bool DecorationManager::FindDecoration(
uint32_t id, uint32_t decoration,
std::function<bool(const Instruction&)> f) {

View File

@@ -90,6 +90,10 @@ class DecorationManager {
bool AreDecorationsTheSame(const Instruction* inst1, const Instruction* inst2,
bool ignore_target) const;
// Returns whether a decoration instruction for |id| with decoration
// |decoration| exists or not.
bool HasDecoration(uint32_t id, uint32_t decoration);
// |f| is run on each decoration instruction for |id| with decoration
// |decoration|. Processed are all decorations which target |id| either
// directly or indirectly by Decoration Groups.

View File

@@ -14,6 +14,7 @@
#include "source/opt/desc_sroa.h"
#include "source/opt/desc_sroa_util.h"
#include "source/util/string_utils.h"
namespace spvtools {
@@ -25,7 +26,7 @@ Pass::Status DescriptorScalarReplacement::Process() {
std::vector<Instruction*> vars_to_kill;
for (Instruction& var : context()->types_values()) {
if (IsCandidate(&var)) {
if (descsroautil::IsDescriptorArray(context(), &var)) {
modified = true;
if (!ReplaceCandidate(&var)) {
return Status::Failure;
@@ -41,72 +42,6 @@ Pass::Status DescriptorScalarReplacement::Process() {
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
bool DescriptorScalarReplacement::IsCandidate(Instruction* var) {
if (var->opcode() != SpvOpVariable) {
return false;
}
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst =
context()->get_def_use_mgr()->GetDef(ptr_type_id);
if (ptr_type_inst->opcode() != SpvOpTypePointer) {
return false;
}
uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
Instruction* var_type_inst =
context()->get_def_use_mgr()->GetDef(var_type_id);
if (var_type_inst->opcode() != SpvOpTypeArray &&
var_type_inst->opcode() != SpvOpTypeStruct) {
return false;
}
// All structures with descriptor assignments must be replaced by variables,
// one for each of their members - with the exceptions of buffers.
if (IsTypeOfStructuredBuffer(var_type_inst)) {
return false;
}
bool has_desc_set_decoration = false;
context()->get_decoration_mgr()->ForEachDecoration(
var->result_id(), SpvDecorationDescriptorSet,
[&has_desc_set_decoration](const Instruction&) {
has_desc_set_decoration = true;
});
if (!has_desc_set_decoration) {
return false;
}
bool has_binding_decoration = false;
context()->get_decoration_mgr()->ForEachDecoration(
var->result_id(), SpvDecorationBinding,
[&has_binding_decoration](const Instruction&) {
has_binding_decoration = true;
});
if (!has_binding_decoration) {
return false;
}
return true;
}
bool DescriptorScalarReplacement::IsTypeOfStructuredBuffer(
const Instruction* type) const {
if (type->opcode() != SpvOpTypeStruct) {
return false;
}
// All buffers have offset decorations for members of their structure types.
// This is how we distinguish it from a structure of descriptors.
bool has_offset_decoration = false;
context()->get_decoration_mgr()->ForEachDecoration(
type->result_id(), SpvDecorationOffset,
[&has_offset_decoration](const Instruction&) {
has_offset_decoration = true;
});
return has_offset_decoration;
}
bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) {
std::vector<Instruction*> access_chain_work_list;
std::vector<Instruction*> load_work_list;
@@ -162,16 +97,15 @@ bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var,
return false;
}
uint32_t idx_id = use->GetSingleWordInOperand(1);
const analysis::Constant* idx_const =
context()->get_constant_mgr()->FindDeclaredConstant(idx_id);
if (idx_const == nullptr) {
const analysis::Constant* const_index =
descsroautil::GetAccessChainIndexAsConst(context(), use);
if (const_index == nullptr) {
context()->EmitErrorMessage("Variable cannot be replaced: invalid index",
use);
return false;
}
uint32_t idx = idx_const->GetU32();
uint32_t idx = const_index->GetU32();
uint32_t replacement_var = GetReplacementVariable(var, idx);
if (use->NumInOperands() == 2) {
@@ -208,39 +142,12 @@ uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var,
uint32_t idx) {
auto replacement_vars = replacement_variables_.find(var);
if (replacement_vars == replacement_variables_.end()) {
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
"Variable should be a pointer to an array or structure.");
uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id);
const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray;
const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct;
assert((is_array || is_struct) &&
"Variable should be a pointer to an array or structure.");
// For arrays, each array element should be replaced with a new replacement
// variable
if (is_array) {
uint32_t array_len_id = pointee_type_inst->GetSingleWordInOperand(1);
const analysis::Constant* array_len_const =
context()->get_constant_mgr()->FindDeclaredConstant(array_len_id);
assert(array_len_const != nullptr && "Array length must be a constant.");
uint32_t array_len = array_len_const->GetU32();
replacement_vars = replacement_variables_
.insert({var, std::vector<uint32_t>(array_len, 0)})
.first;
}
// For structures, each member should be replaced with a new replacement
// variable
if (is_struct) {
const uint32_t num_members = pointee_type_inst->NumInOperands();
replacement_vars =
replacement_variables_
.insert({var, std::vector<uint32_t>(num_members, 0)})
.first;
}
uint32_t number_of_elements =
descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
replacement_vars =
replacement_variables_
.insert({var, std::vector<uint32_t>(number_of_elements, 0)})
.first;
}
if (replacement_vars->second[idx] == 0) {
@@ -377,7 +284,7 @@ uint32_t DescriptorScalarReplacement::GetNumBindingsUsedByType(
// The number of bindings consumed by a structure is the sum of the bindings
// used by its members.
if (type_inst->opcode() == SpvOpTypeStruct &&
!IsTypeOfStructuredBuffer(type_inst)) {
!descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) {
uint32_t sum = 0;
for (uint32_t i = 0; i < type_inst->NumInOperands(); i++)
sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i));

View File

@@ -46,10 +46,6 @@ class DescriptorScalarReplacement : public Pass {
}
private:
// Returns true if |var| is an OpVariable instruction that represents a
// descriptor array. These are the variables that we want to replace.
bool IsCandidate(Instruction* var);
// Replaces all references to |var| by new variables, one for each element of
// the array |var|. The binding for the new variables corresponding to
// element i will be the binding of |var| plus i. Returns true if successful.
@@ -93,11 +89,6 @@ class DescriptorScalarReplacement : public Pass {
// bindings used by its members.
uint32_t GetNumBindingsUsedByType(uint32_t type_id);
// Returns true if |type| is a type that could be used for a structured buffer
// as opposed to a type that would be used for a structure of resource
// descriptors.
bool IsTypeOfStructuredBuffer(const Instruction* type) const;
// A map from an OpVariable instruction to the set of variables that will be
// used to replace it. The entry |replacement_variables_[var][i]| is the id of
// a variable that will be used in the place of the the ith element of the

View File

@@ -0,0 +1,117 @@
// Copyright (c) 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/opt/desc_sroa_util.h"
namespace spvtools {
namespace opt {
namespace {
const uint32_t kOpAccessChainInOperandIndexes = 1;
// Returns the length of array type |type|.
uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) {
assert(type->opcode() == SpvOpTypeArray && "type must be array");
uint32_t length_id = type->GetSingleWordInOperand(1);
const analysis::Constant* length_const =
context->get_constant_mgr()->FindDeclaredConstant(length_id);
assert(length_const != nullptr);
return length_const->GetU32();
}
} // namespace
namespace descsroautil {
bool IsDescriptorArray(IRContext* context, Instruction* var) {
if (var->opcode() != SpvOpVariable) {
return false;
}
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
if (ptr_type_inst->opcode() != SpvOpTypePointer) {
return false;
}
uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id);
if (var_type_inst->opcode() != SpvOpTypeArray &&
var_type_inst->opcode() != SpvOpTypeStruct) {
return false;
}
// All structures with descriptor assignments must be replaced by variables,
// one for each of their members - with the exceptions of buffers.
if (IsTypeOfStructuredBuffer(context, var_type_inst)) {
return false;
}
if (!context->get_decoration_mgr()->HasDecoration(
var->result_id(), SpvDecorationDescriptorSet)) {
return false;
}
return context->get_decoration_mgr()->HasDecoration(var->result_id(),
SpvDecorationBinding);
}
bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) {
if (type->opcode() != SpvOpTypeStruct) {
return false;
}
// All buffers have offset decorations for members of their structure types.
// This is how we distinguish it from a structure of descriptors.
return context->get_decoration_mgr()->HasDecoration(type->result_id(),
SpvDecorationOffset);
}
const analysis::Constant* GetAccessChainIndexAsConst(
IRContext* context, Instruction* access_chain) {
if (access_chain->NumInOperands() <= 1) {
return nullptr;
}
uint32_t idx_id = GetFirstIndexOfAccessChain(access_chain);
const analysis::Constant* idx_const =
context->get_constant_mgr()->FindDeclaredConstant(idx_id);
return idx_const;
}
uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) {
assert(access_chain->NumInOperands() > 1 &&
"OpAccessChain does not have Indexes operand");
return access_chain->GetSingleWordInOperand(kOpAccessChainInOperandIndexes);
}
uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
Instruction* var) {
uint32_t ptr_type_id = var->type_id();
Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
"Variable should be a pointer to an array or structure.");
uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
Instruction* pointee_type_inst =
context->get_def_use_mgr()->GetDef(pointee_type_id);
if (pointee_type_inst->opcode() == SpvOpTypeArray) {
return GetLengthOfArrayType(context, pointee_type_inst);
}
assert(pointee_type_inst->opcode() == SpvOpTypeStruct &&
"Variable should be a pointer to an array or structure.");
return pointee_type_inst->NumInOperands();
}
} // namespace descsroautil
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_OPT_DESC_SROA_UTIL_H_
#define SOURCE_OPT_DESC_SROA_UTIL_H_
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
// Provides functions for the descriptor array SROA.
namespace descsroautil {
// Returns true if |var| is an OpVariable instruction that represents a
// descriptor array.
bool IsDescriptorArray(IRContext* context, Instruction* var);
// Returns true if |type| is a type that could be used for a structured buffer
// as opposed to a type that would be used for a structure of resource
// descriptors.
bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type);
// Returns the first index of the OpAccessChain instruction |access_chain| as
// a constant. Returns nullptr if it is not a constant.
const analysis::Constant* GetAccessChainIndexAsConst(IRContext* context,
Instruction* access_chain);
// Returns the number of elements of an OpVariable instruction |var| whose type
// must be a pointer to an array or a struct.
uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
Instruction* var);
// Returns the first Indexes operand id of the OpAccessChain or
// OpInBoundsAccessChain instruction |access_chain|. The access chain must have
// at least 1 index.
uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain);
} // namespace descsroautil
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_DESC_SROA_UTIL_H_

View File

@@ -968,25 +968,17 @@ FoldingRule MergeDivMulArithmetic() {
FoldingRule MergeDivNegateArithmetic() {
return [](IRContext* context, Instruction* inst,
const std::vector<const analysis::Constant*>& constants) {
assert(inst->opcode() == SpvOpFDiv || inst->opcode() == SpvOpSDiv);
assert(inst->opcode() == SpvOpFDiv);
analysis::ConstantManager* const_mgr = context->get_constant_mgr();
const analysis::Type* type =
context->get_type_mgr()->GetType(inst->type_id());
bool uses_float = HasFloatingPoint(type);
if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
uint32_t width = ElementWidth(type);
if (width != 32 && width != 64) return false;
if (!inst->IsFloatingPointFoldingAllowed()) return false;
const analysis::Constant* const_input1 = ConstInput(constants);
if (!const_input1) return false;
Instruction* other_inst = NonConstInput(context, constants[0], inst);
if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
return false;
if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
bool first_is_variable = constants[0] == nullptr;
if (other_inst->opcode() == SpvOpFNegate ||
other_inst->opcode() == SpvOpSNegate) {
if (other_inst->opcode() == SpvOpFNegate) {
uint32_t neg_id = NegateConstant(const_mgr, const_input1);
if (first_is_variable) {
@@ -2589,8 +2581,6 @@ void FoldingRules::AddFoldingRules() {
rules_[SpvOpPhi].push_back(RedundantPhi());
rules_[SpvOpSDiv].push_back(MergeDivNegateArithmetic());
rules_[SpvOpSNegate].push_back(MergeNegateArithmetic());
rules_[SpvOpSNegate].push_back(MergeNegateMulDivArithmetic());
rules_[SpvOpSNegate].push_back(MergeNegateAddSubArithmetic());

View File

@@ -177,6 +177,9 @@ class Function {
// debuggers.
void Dump() const;
// Returns true is a function declaration and not a function definition.
bool IsDeclaration() { return begin() == end(); }
private:
// The OpFunction instruction that begins the definition of this function.
std::unique_ptr<Instruction> def_inst_;

View File

@@ -65,7 +65,7 @@ Pass::Status InlineExhaustivePass::ProcessImpl() {
status = CombineStatus(status, InlineExhaustive(fp));
return false;
};
context()->ProcessEntryPointCallTree(pfn);
context()->ProcessReachableCallTree(pfn);
return status;
}

View File

@@ -105,7 +105,7 @@ Pass::Status InlineOpaquePass::ProcessImpl() {
status = CombineStatus(status, InlineOpaque(fp));
return false;
};
context()->ProcessEntryPointCallTree(pfn);
context()->ProcessReachableCallTree(pfn);
return status;
}

View File

@@ -142,7 +142,7 @@ inline IteratorRange<IteratorType> make_range(const IteratorType& begin,
template <typename IteratorType>
inline IteratorRange<IteratorType> make_range(IteratorType&& begin,
IteratorType&& end) {
return {std::move(begin), std::move(end)};
return {std::forward<IteratorType>(begin), std::forward<IteratorType>(end)};
}
// Returns a (begin, end) iterator pair for the given container.

View File

@@ -223,7 +223,7 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() {
return LocalSingleBlockLoadStoreElim(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@@ -81,7 +81,7 @@ Pass::Status LocalSingleStoreElimPass::ProcessImpl() {
ProcessFunction pfn = [this](Function* fp) {
return LocalSingleStoreElim(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@@ -1102,6 +1102,10 @@ void LoopUtils::Finalize() {
Pass::Status LoopUnroller::Process() {
bool changed = false;
for (Function& f : *context()->module()) {
if (f.IsDeclaration()) {
continue;
}
LoopDescriptor* LD = context()->GetLoopDescriptor(&f);
for (Loop& loop : *LD) {
LoopUtils loop_utils{context(), &loop};

View File

@@ -86,8 +86,8 @@ bool MemPass::IsPtr(uint32_t ptrId) {
}
const SpvOp op = ptrInst->opcode();
if (op == SpvOpVariable || IsNonPtrAccessChain(op)) return true;
if (op != SpvOpFunctionParameter) return false;
const uint32_t varTypeId = ptrInst->type_id();
if (varTypeId == 0) return false;
const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
return varTypeInst->opcode() == SpvOpTypePointer;
}

View File

@@ -320,6 +320,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
RegisterPass(CreateCombineAccessChainsPass());
} else if (pass_name == "convert-local-access-chains") {
RegisterPass(CreateLocalAccessChainConvertPass());
} else if (pass_name == "replace-desc-array-access-using-var-index") {
RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass());
} else if (pass_name == "descriptor-scalar-replacement") {
RegisterPass(CreateDescriptorScalarReplacementPass());
} else if (pass_name == "eliminate-dead-code-aggressive") {
@@ -958,6 +960,11 @@ Optimizer::PassToken CreateGraphicsRobustAccessPass() {
MakeUnique<opt::GraphicsRobustAccessPass>());
}
Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>());
}
Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DescriptorScalarReplacement>());

View File

@@ -66,6 +66,7 @@
#include "source/opt/relax_float_ops_pass.h"
#include "source/opt/remove_duplicates_pass.h"
#include "source/opt/remove_unused_interface_variables_pass.h"
#include "source/opt/replace_desc_array_access_using_var_index.h"
#include "source/opt/replace_invalid_opc.h"
#include "source/opt/scalar_replacement_pass.h"
#include "source/opt/set_spec_constant_default_value_pass.h"

View File

@@ -24,6 +24,10 @@ Pass::Status RedundancyEliminationPass::Process() {
ValueNumberTable vnTable(context());
for (auto& func : *get_module()) {
if (func.IsDeclaration()) {
continue;
}
// Build the dominator tree for this function. It is how the code is
// traversed.
DominatorTree& dom_tree =

View File

@@ -51,7 +51,8 @@ inline bool IsTypeInst(SpvOp opcode) {
opcode == SpvOpTypeCooperativeMatrixNV;
}
inline bool IsConstantInst(SpvOp opcode) {
return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
return (opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp) ||
opcode == SpvOpConstFunctionPointerINTEL;
}
inline bool IsCompileTimeConstantInst(SpvOp opcode) {
return opcode >= SpvOpConstantTrue && opcode <= SpvOpConstantNull;

View File

@@ -76,7 +76,7 @@ Pass::Status RelaxFloatOpsPass::ProcessImpl() {
Pass::ProcessFunction pfn = [this](Function* fp) {
return ProcessFunction(fp);
};
bool modified = context()->ProcessEntryPointCallTree(pfn);
bool modified = context()->ProcessReachableCallTree(pfn);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}

View File

@@ -0,0 +1,423 @@
// Copyright (c) 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/opt/replace_desc_array_access_using_var_index.h"
#include "source/opt/desc_sroa_util.h"
#include "source/opt/ir_builder.h"
#include "source/util/string_utils.h"
namespace spvtools {
namespace opt {
namespace {
const uint32_t kOpAccessChainInOperandIndexes = 1;
const uint32_t kOpTypePointerInOperandType = 1;
const uint32_t kOpTypeArrayInOperandType = 0;
const uint32_t kOpTypeStructInOperandMember = 0;
IRContext::Analysis kAnalysisDefUseAndInstrToBlockMapping =
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
uint32_t GetValueWithKeyExistenceCheck(
uint32_t key, const std::unordered_map<uint32_t, uint32_t>& map) {
auto itr = map.find(key);
assert(itr != map.end() && "Key does not exist");
return itr->second;
}
} // namespace
Pass::Status ReplaceDescArrayAccessUsingVarIndex::Process() {
Status status = Status::SuccessWithoutChange;
for (Instruction& var : context()->types_values()) {
if (descsroautil::IsDescriptorArray(context(), &var)) {
if (ReplaceVariableAccessesWithConstantElements(&var))
status = Status::SuccessWithChange;
}
}
return status;
}
bool ReplaceDescArrayAccessUsingVarIndex::
ReplaceVariableAccessesWithConstantElements(Instruction* var) const {
std::vector<Instruction*> work_list;
get_def_use_mgr()->ForEachUser(var, [&work_list](Instruction* use) {
switch (use->opcode()) {
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
work_list.push_back(use);
break;
default:
break;
}
});
bool updated = false;
for (Instruction* access_chain : work_list) {
if (descsroautil::GetAccessChainIndexAsConst(context(), access_chain) ==
nullptr) {
ReplaceAccessChain(var, access_chain);
updated = true;
}
}
// Note that we do not consider OpLoad and OpCompositeExtract because
// OpCompositeExtract always has constant literals for indices.
return updated;
}
void ReplaceDescArrayAccessUsingVarIndex::ReplaceAccessChain(
Instruction* var, Instruction* access_chain) const {
uint32_t number_of_elements =
descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
assert(number_of_elements != 0 && "Number of element is 0");
if (number_of_elements == 1) {
UseConstIndexForAccessChain(access_chain, 0);
get_def_use_mgr()->AnalyzeInstUse(access_chain);
return;
}
ReplaceUsersOfAccessChain(access_chain, number_of_elements);
}
void ReplaceDescArrayAccessUsingVarIndex::ReplaceUsersOfAccessChain(
Instruction* access_chain, uint32_t number_of_elements) const {
std::vector<Instruction*> final_users;
CollectRecursiveUsersWithConcreteType(access_chain, &final_users);
for (auto* inst : final_users) {
std::deque<Instruction*> insts_to_be_cloned =
CollectRequiredImageInsts(inst);
ReplaceNonUniformAccessWithSwitchCase(
inst, access_chain, number_of_elements, insts_to_be_cloned);
}
}
void ReplaceDescArrayAccessUsingVarIndex::CollectRecursiveUsersWithConcreteType(
Instruction* access_chain, std::vector<Instruction*>* final_users) const {
std::queue<Instruction*> work_list;
work_list.push(access_chain);
while (!work_list.empty()) {
auto* inst_from_work_list = work_list.front();
work_list.pop();
get_def_use_mgr()->ForEachUser(
inst_from_work_list, [this, final_users, &work_list](Instruction* use) {
// TODO: Support Boolean type as well.
if (!use->HasResultId() || IsConcreteType(use->type_id())) {
final_users->push_back(use);
} else {
work_list.push(use);
}
});
}
}
std::deque<Instruction*>
ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageInsts(
Instruction* user_of_image_insts) const {
std::unordered_set<uint32_t> seen_inst_ids;
std::queue<Instruction*> work_list;
auto decision_to_include_operand = [this, &seen_inst_ids,
&work_list](uint32_t* idp) {
if (!seen_inst_ids.insert(*idp).second) return;
Instruction* operand = get_def_use_mgr()->GetDef(*idp);
if (context()->get_instr_block(operand) != nullptr &&
HasImageOrImagePtrType(operand)) {
work_list.push(operand);
}
};
std::deque<Instruction*> required_image_insts;
required_image_insts.push_front(user_of_image_insts);
user_of_image_insts->ForEachInId(decision_to_include_operand);
while (!work_list.empty()) {
auto* inst_from_work_list = work_list.front();
work_list.pop();
required_image_insts.push_front(inst_from_work_list);
inst_from_work_list->ForEachInId(decision_to_include_operand);
}
return required_image_insts;
}
bool ReplaceDescArrayAccessUsingVarIndex::HasImageOrImagePtrType(
const Instruction* inst) const {
assert(inst != nullptr && inst->type_id() != 0 && "Invalid instruction");
return IsImageOrImagePtrType(get_def_use_mgr()->GetDef(inst->type_id()));
}
bool ReplaceDescArrayAccessUsingVarIndex::IsImageOrImagePtrType(
const Instruction* type_inst) const {
if (type_inst->opcode() == SpvOpTypeImage ||
type_inst->opcode() == SpvOpTypeSampler ||
type_inst->opcode() == SpvOpTypeSampledImage) {
return true;
}
if (type_inst->opcode() == SpvOpTypePointer) {
Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(
type_inst->GetSingleWordInOperand(kOpTypePointerInOperandType));
return IsImageOrImagePtrType(pointee_type_inst);
}
if (type_inst->opcode() == SpvOpTypeArray) {
Instruction* element_type_inst = get_def_use_mgr()->GetDef(
type_inst->GetSingleWordInOperand(kOpTypeArrayInOperandType));
return IsImageOrImagePtrType(element_type_inst);
}
if (type_inst->opcode() != SpvOpTypeStruct) return false;
for (uint32_t in_operand_idx = kOpTypeStructInOperandMember;
in_operand_idx < type_inst->NumInOperands(); ++in_operand_idx) {
Instruction* member_type_inst = get_def_use_mgr()->GetDef(
type_inst->GetSingleWordInOperand(kOpTypeStructInOperandMember));
if (IsImageOrImagePtrType(member_type_inst)) return true;
}
return false;
}
bool ReplaceDescArrayAccessUsingVarIndex::IsConcreteType(
uint32_t type_id) const {
Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
if (type_inst->opcode() == SpvOpTypeInt ||
type_inst->opcode() == SpvOpTypeFloat) {
return true;
}
if (type_inst->opcode() == SpvOpTypeVector ||
type_inst->opcode() == SpvOpTypeMatrix ||
type_inst->opcode() == SpvOpTypeArray) {
return IsConcreteType(type_inst->GetSingleWordInOperand(0));
}
if (type_inst->opcode() == SpvOpTypeStruct) {
for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
if (!IsConcreteType(type_inst->GetSingleWordInOperand(i))) return false;
}
return true;
}
return false;
}
BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateCaseBlock(
Instruction* access_chain, uint32_t element_index,
const std::deque<Instruction*>& insts_to_be_cloned,
uint32_t branch_target_id,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
auto* case_block = CreateNewBlock();
AddConstElementAccessToCaseBlock(case_block, access_chain, element_index,
old_ids_to_new_ids);
CloneInstsToBlock(case_block, access_chain, insts_to_be_cloned,
old_ids_to_new_ids);
AddBranchToBlock(case_block, branch_target_id);
UseNewIdsInBlock(case_block, *old_ids_to_new_ids);
return case_block;
}
void ReplaceDescArrayAccessUsingVarIndex::CloneInstsToBlock(
BasicBlock* block, Instruction* inst_to_skip_cloning,
const std::deque<Instruction*>& insts_to_be_cloned,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
for (auto* inst_to_be_cloned : insts_to_be_cloned) {
if (inst_to_be_cloned == inst_to_skip_cloning) continue;
std::unique_ptr<Instruction> clone(inst_to_be_cloned->Clone(context()));
if (inst_to_be_cloned->HasResultId()) {
uint32_t new_id = context()->TakeNextId();
clone->SetResultId(new_id);
(*old_ids_to_new_ids)[inst_to_be_cloned->result_id()] = new_id;
}
get_def_use_mgr()->AnalyzeInstDefUse(clone.get());
context()->set_instr_block(clone.get(), block);
block->AddInstruction(std::move(clone));
}
}
void ReplaceDescArrayAccessUsingVarIndex::UseNewIdsInBlock(
BasicBlock* block,
const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const {
for (auto block_itr = block->begin(); block_itr != block->end();
++block_itr) {
(&*block_itr)->ForEachInId([&old_ids_to_new_ids](uint32_t* idp) {
auto old_ids_to_new_ids_itr = old_ids_to_new_ids.find(*idp);
if (old_ids_to_new_ids_itr == old_ids_to_new_ids.end()) return;
*idp = old_ids_to_new_ids_itr->second;
});
get_def_use_mgr()->AnalyzeInstUse(&*block_itr);
}
}
void ReplaceDescArrayAccessUsingVarIndex::ReplaceNonUniformAccessWithSwitchCase(
Instruction* access_chain_final_user, Instruction* access_chain,
uint32_t number_of_elements,
const std::deque<Instruction*>& insts_to_be_cloned) const {
// Create merge block and add terminator
auto* block = context()->get_instr_block(access_chain_final_user);
auto* merge_block = SeparateInstructionsIntoNewBlock(
block, access_chain_final_user->NextNode());
auto* function = block->GetParent();
// Add case blocks
std::vector<uint32_t> phi_operands;
std::vector<uint32_t> case_block_ids;
for (uint32_t idx = 0; idx < number_of_elements; ++idx) {
std::unordered_map<uint32_t, uint32_t> old_ids_to_new_ids_for_cloned_insts;
std::unique_ptr<BasicBlock> case_block(CreateCaseBlock(
access_chain, idx, insts_to_be_cloned, merge_block->id(),
&old_ids_to_new_ids_for_cloned_insts));
case_block_ids.push_back(case_block->id());
function->InsertBasicBlockBefore(std::move(case_block), merge_block);
// Keep the operand for OpPhi
if (!access_chain_final_user->HasResultId()) continue;
uint32_t phi_operand =
GetValueWithKeyExistenceCheck(access_chain_final_user->result_id(),
old_ids_to_new_ids_for_cloned_insts);
phi_operands.push_back(phi_operand);
}
// Create default block
std::unique_ptr<BasicBlock> default_block(
CreateDefaultBlock(access_chain_final_user->HasResultId(), &phi_operands,
merge_block->id()));
uint32_t default_block_id = default_block->id();
function->InsertBasicBlockBefore(std::move(default_block), merge_block);
// Create OpSwitch
uint32_t access_chain_index_var_id =
descsroautil::GetFirstIndexOfAccessChain(access_chain);
AddSwitchForAccessChain(block, access_chain_index_var_id, default_block_id,
merge_block->id(), case_block_ids);
// Create phi instructions
if (!phi_operands.empty()) {
uint32_t phi_id = CreatePhiInstruction(merge_block, phi_operands,
case_block_ids, default_block_id);
context()->ReplaceAllUsesWith(access_chain_final_user->result_id(), phi_id);
}
// Replace OpPhi incoming block operand that uses |block| with |merge_block|
ReplacePhiIncomingBlock(block->id(), merge_block->id());
}
BasicBlock*
ReplaceDescArrayAccessUsingVarIndex::SeparateInstructionsIntoNewBlock(
BasicBlock* block, Instruction* separation_begin_inst) const {
auto separation_begin = block->begin();
while (separation_begin != block->end() &&
&*separation_begin != separation_begin_inst) {
++separation_begin;
}
return block->SplitBasicBlock(context(), context()->TakeNextId(),
separation_begin);
}
BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateNewBlock() const {
auto* new_block = new BasicBlock(std::unique_ptr<Instruction>(
new Instruction(context(), SpvOpLabel, 0, context()->TakeNextId(), {})));
get_def_use_mgr()->AnalyzeInstDefUse(new_block->GetLabelInst());
context()->set_instr_block(new_block->GetLabelInst(), new_block);
return new_block;
}
void ReplaceDescArrayAccessUsingVarIndex::UseConstIndexForAccessChain(
Instruction* access_chain, uint32_t const_element_idx) const {
uint32_t const_element_idx_id =
context()->get_constant_mgr()->GetUIntConst(const_element_idx);
access_chain->SetInOperand(kOpAccessChainInOperandIndexes,
{const_element_idx_id});
}
void ReplaceDescArrayAccessUsingVarIndex::AddConstElementAccessToCaseBlock(
BasicBlock* case_block, Instruction* access_chain,
uint32_t const_element_idx,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
std::unique_ptr<Instruction> access_clone(access_chain->Clone(context()));
UseConstIndexForAccessChain(access_clone.get(), const_element_idx);
uint32_t new_access_id = context()->TakeNextId();
(*old_ids_to_new_ids)[access_clone->result_id()] = new_access_id;
access_clone->SetResultId(new_access_id);
get_def_use_mgr()->AnalyzeInstDefUse(access_clone.get());
context()->set_instr_block(access_clone.get(), case_block);
case_block->AddInstruction(std::move(access_clone));
}
void ReplaceDescArrayAccessUsingVarIndex::AddBranchToBlock(
BasicBlock* parent_block, uint32_t branch_destination) const {
InstructionBuilder builder{context(), parent_block,
kAnalysisDefUseAndInstrToBlockMapping};
builder.AddBranch(branch_destination);
}
BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateDefaultBlock(
bool null_const_for_phi_is_needed, std::vector<uint32_t>* phi_operands,
uint32_t merge_block_id) const {
auto* default_block = CreateNewBlock();
AddBranchToBlock(default_block, merge_block_id);
if (!null_const_for_phi_is_needed) return default_block;
// Create null value for OpPhi
Instruction* inst = context()->get_def_use_mgr()->GetDef((*phi_operands)[0]);
auto* null_const_inst = GetConstNull(inst->type_id());
phi_operands->push_back(null_const_inst->result_id());
return default_block;
}
Instruction* ReplaceDescArrayAccessUsingVarIndex::GetConstNull(
uint32_t type_id) const {
assert(type_id != 0 && "Result type is expected");
auto* type = context()->get_type_mgr()->GetType(type_id);
auto* null_const = context()->get_constant_mgr()->GetConstant(type, {});
return context()->get_constant_mgr()->GetDefiningInstruction(null_const);
}
void ReplaceDescArrayAccessUsingVarIndex::AddSwitchForAccessChain(
BasicBlock* parent_block, uint32_t access_chain_index_var_id,
uint32_t default_id, uint32_t merge_id,
const std::vector<uint32_t>& case_block_ids) const {
InstructionBuilder builder{context(), parent_block,
kAnalysisDefUseAndInstrToBlockMapping};
std::vector<std::pair<Operand::OperandData, uint32_t>> cases;
for (uint32_t i = 0; i < static_cast<uint32_t>(case_block_ids.size()); ++i) {
cases.emplace_back(Operand::OperandData{i}, case_block_ids[i]);
}
builder.AddSwitch(access_chain_index_var_id, default_id, cases, merge_id);
}
uint32_t ReplaceDescArrayAccessUsingVarIndex::CreatePhiInstruction(
BasicBlock* parent_block, const std::vector<uint32_t>& phi_operands,
const std::vector<uint32_t>& case_block_ids,
uint32_t default_block_id) const {
std::vector<uint32_t> incomings;
assert(case_block_ids.size() + 1 == phi_operands.size() &&
"Number of Phi operands must be exactly 1 bigger than the one of case "
"blocks");
for (size_t i = 0; i < case_block_ids.size(); ++i) {
incomings.push_back(phi_operands[i]);
incomings.push_back(case_block_ids[i]);
}
incomings.push_back(phi_operands.back());
incomings.push_back(default_block_id);
InstructionBuilder builder{context(), &*parent_block->begin(),
kAnalysisDefUseAndInstrToBlockMapping};
uint32_t phi_result_type_id =
context()->get_def_use_mgr()->GetDef(phi_operands[0])->type_id();
auto* phi = builder.AddPhi(phi_result_type_id, incomings);
return phi->result_id();
}
void ReplaceDescArrayAccessUsingVarIndex::ReplacePhiIncomingBlock(
uint32_t old_incoming_block_id, uint32_t new_incoming_block_id) const {
context()->ReplaceAllUsesWithPredicate(
old_incoming_block_id, new_incoming_block_id,
[](Instruction* use) { return use->opcode() == SpvOpPhi; });
}
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,204 @@
// Copyright (c) 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
#define SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
#include <cstdio>
#include <memory>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "source/opt/function.h"
#include "source/opt/pass.h"
#include "source/opt/type_manager.h"
namespace spvtools {
namespace opt {
// See optimizer.hpp for documentation.
class ReplaceDescArrayAccessUsingVarIndex : public Pass {
public:
ReplaceDescArrayAccessUsingVarIndex() {}
const char* name() const override {
return "replace-desc-array-access-using-var-index";
}
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
}
private:
// Replaces all acceses to |var| using variable indices with constant
// elements of the array |var|. Creates switch-case statements to determine
// the value of the variable index for all the possible cases. Returns
// whether replacement is done or not.
bool ReplaceVariableAccessesWithConstantElements(Instruction* var) const;
// Replaces the OpAccessChain or OpInBoundsAccessChain instruction |use| that
// uses the descriptor variable |var| with the OpAccessChain or
// OpInBoundsAccessChain instruction with a constant Indexes operand.
void ReplaceAccessChain(Instruction* var, Instruction* use) const;
// Updates the first Indexes operand of the OpAccessChain or
// OpInBoundsAccessChain instruction |access_chain| to let it use a constant
// index |const_element_idx|.
void UseConstIndexForAccessChain(Instruction* access_chain,
uint32_t const_element_idx) const;
// Replaces users of the OpAccessChain or OpInBoundsAccessChain instruction
// |access_chain| that accesses an array descriptor variable using variable
// indices with constant elements. |number_of_elements| is the number
// of array elements.
void ReplaceUsersOfAccessChain(Instruction* access_chain,
uint32_t number_of_elements) const;
// Puts all the recursive users of |access_chain| with concrete result types
// or the ones without result it in |final_users|.
void CollectRecursiveUsersWithConcreteType(
Instruction* access_chain, std::vector<Instruction*>* final_users) const;
// Recursively collects the operands of |user_of_image_insts| (and operands
// of the operands) whose result types are images/samplers or pointers/array/
// struct of them and returns them.
std::deque<Instruction*> CollectRequiredImageInsts(
Instruction* user_of_image_insts) const;
// Returns whether result type of |inst| is an image/sampler/pointer of image
// or sampler or not.
bool HasImageOrImagePtrType(const Instruction* inst) const;
// Returns whether |type_inst| is an image/sampler or pointer/array/struct of
// image or sampler or not.
bool IsImageOrImagePtrType(const Instruction* type_inst) const;
// Returns whether the type with |type_id| is a concrete type or not.
bool IsConcreteType(uint32_t type_id) const;
// Replaces the non-uniform access to a descriptor variable
// |access_chain_final_user| with OpSwitch instruction and case blocks. Each
// case block will contain a clone of |access_chain| and clones of
// |non_uniform_accesses_to_clone| that are recursively used by
// |access_chain_final_user|. The clone of |access_chain| (or
// OpInBoundsAccessChain) will have a constant index for its first index. The
// OpSwitch instruction will have the cases for the variable index of
// |access_chain| from 0 to |number_of_elements| - 1.
void ReplaceNonUniformAccessWithSwitchCase(
Instruction* access_chain_final_user, Instruction* access_chain,
uint32_t number_of_elements,
const std::deque<Instruction*>& non_uniform_accesses_to_clone) const;
// Creates and returns a new basic block that contains all instructions of
// |block| after |separation_begin_inst|. The new basic block is added to the
// function in this method.
BasicBlock* SeparateInstructionsIntoNewBlock(
BasicBlock* block, Instruction* separation_begin_inst) const;
// Creates and returns a new block.
BasicBlock* CreateNewBlock() const;
// Returns the first operand id of the OpAccessChain or OpInBoundsAccessChain
// instruction |access_chain|.
uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) const;
// Adds a clone of the OpAccessChain or OpInBoundsAccessChain instruction
// |access_chain| to |case_block|. The clone of |access_chain| will use
// |const_element_idx| for its first index. |old_ids_to_new_ids| keeps the
// mapping from the result id of |access_chain| to the result of its clone.
void AddConstElementAccessToCaseBlock(
BasicBlock* case_block, Instruction* access_chain,
uint32_t const_element_idx,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
// Clones all instructions in |insts_to_be_cloned| and put them to |block|.
// |old_ids_to_new_ids| keeps the mapping from the result id of each
// instruction of |insts_to_be_cloned| to the result of their clones.
void CloneInstsToBlock(
BasicBlock* block, Instruction* inst_to_skip_cloning,
const std::deque<Instruction*>& insts_to_be_cloned,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
// Adds OpBranch to |branch_destination| at the end of |parent_block|.
void AddBranchToBlock(BasicBlock* parent_block,
uint32_t branch_destination) const;
// Replaces in-operands of all instructions in the basic block |block| using
// |old_ids_to_new_ids|. It conducts the replacement only if the in-operand
// id is a key of |old_ids_to_new_ids|.
void UseNewIdsInBlock(
BasicBlock* block,
const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const;
// Creates a case block for |element_index| case. It adds clones of
// |insts_to_be_cloned| and a clone of |access_chain| with |element_index| as
// its first index. The termination instruction of the created case block will
// be a branch to |branch_target_id|. Puts old ids to new ids map for the
// cloned instructions in |old_ids_to_new_ids|.
BasicBlock* CreateCaseBlock(
Instruction* access_chain, uint32_t element_index,
const std::deque<Instruction*>& insts_to_be_cloned,
uint32_t branch_target_id,
std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
// Creates a default block for switch-case statement that has only a single
// instruction OpBranch whose target is a basic block with |merge_block_id|.
// If |null_const_for_phi_is_needed| is true, gets or creates a default null
// constant value for a phi instruction whose operands are |phi_operands| and
// puts it in |phi_operands|.
BasicBlock* CreateDefaultBlock(bool null_const_for_phi_is_needed,
std::vector<uint32_t>* phi_operands,
uint32_t merge_block_id) const;
// Creates and adds an OpSwitch used for the selection of OpAccessChain whose
// first Indexes operand is |access_chain_index_var_id|. The OpSwitch will be
// added at the end of |parent_block|. It will jump to |default_id| for the
// default case and jumps to one of case blocks whoes ids are |case_block_ids|
// if |access_chain_index_var_id| matches the case number. |merge_id| is the
// merge block id.
void AddSwitchForAccessChain(
BasicBlock* parent_block, uint32_t access_chain_index_var_id,
uint32_t default_id, uint32_t merge_id,
const std::vector<uint32_t>& case_block_ids) const;
// Creates a phi instruction with |phi_operands| as values and
// |case_block_ids| and |default_block_id| as incoming blocks. The size of
// |phi_operands| must be exactly 1 larger than the size of |case_block_ids|.
// The last element of |phi_operands| will be used for |default_block_id|. It
// adds the phi instruction to the beginning of |parent_block|.
uint32_t CreatePhiInstruction(BasicBlock* parent_block,
const std::vector<uint32_t>& phi_operands,
const std::vector<uint32_t>& case_block_ids,
uint32_t default_block_id) const;
// Replaces the incoming block operand of OpPhi instructions with
// |new_incoming_block_id| if the incoming block operand is
// |old_incoming_block_id|.
void ReplacePhiIncomingBlock(uint32_t old_incoming_block_id,
uint32_t new_incoming_block_id) const;
// Create an OpConstantNull instruction whose result type id is |type_id|.
Instruction* GetConstNull(uint32_t type_id) const;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_

View File

@@ -35,6 +35,10 @@ namespace opt {
Pass::Status ScalarReplacementPass::Process() {
Status status = Status::SuccessWithoutChange;
for (auto& f : *get_module()) {
if (f.IsDeclaration()) {
continue;
}
Status functionStatus = ProcessFunction(&f);
if (functionStatus == Status::Failure)
return functionStatus;

View File

@@ -85,6 +85,10 @@ std::vector<uint32_t> ParseDefaultValueStr(const char* text,
// with 0x1, which represents a 'true'.
// If all words in the bit pattern are zero, returns a bit pattern with 0x0,
// which represents a 'false'.
// For integer and floating point types narrower than 32 bits, the upper bits
// in the input bit pattern are ignored. Instead the upper bits are set
// according to SPIR-V literal requirements: sign extend a signed integer, and
// otherwise set the upper bits to zero.
std::vector<uint32_t> ParseDefaultValueBitPattern(
const std::vector<uint32_t>& input_bit_pattern,
const analysis::Type* type) {
@@ -98,12 +102,33 @@ std::vector<uint32_t> ParseDefaultValueBitPattern(
}
return result;
} else if (const auto* IT = type->AsInteger()) {
if (IT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
return std::vector<uint32_t>(input_bit_pattern);
const auto width = IT->width();
assert(width > 0);
const auto adjusted_width = std::max(32u, width);
if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
result = std::vector<uint32_t>(input_bit_pattern);
if (width < 32) {
const uint32_t high_active_bit = (1u << width) >> 1;
if (IT->IsSigned() && (high_active_bit & result[0])) {
// Sign extend. This overwrites the sign bit again, but that's ok.
result[0] = result[0] | ~(high_active_bit - 1);
} else {
// Upper bits must be zero.
result[0] = result[0] & ((1u << width) - 1);
}
}
return result;
}
} else if (const auto* FT = type->AsFloat()) {
if (FT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
return std::vector<uint32_t>(input_bit_pattern);
const auto width = FT->width();
const auto adjusted_width = std::max(32u, width);
if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
result = std::vector<uint32_t>(input_bit_pattern);
if (width < 32) {
// Upper bits must be zero.
result[0] = result[0] & ((1u << width) - 1);
}
return result;
}
}
result.clear();

View File

@@ -45,6 +45,10 @@ void SimplificationPass::AddNewOperands(
}
bool SimplificationPass::SimplifyFunction(Function* function) {
if (function->IsDeclaration()) {
return false;
}
bool modified = false;
// Phase 1: Traverse all instructions in dominance order.
// The second phase will only be on the instructions whose inputs have changed

View File

@@ -753,6 +753,9 @@ Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) {
Pass::Status SSARewritePass::Process() {
Status status = Status::SuccessWithoutChange;
for (auto& fn : *get_module()) {
if (fn.IsDeclaration()) {
continue;
}
status =
CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn));
// Kill DebugDeclares for target variables.

View File

@@ -84,6 +84,7 @@ typedef enum spv_generator_t {
SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR = 6,
SPV_GENERATOR_KHRONOS_ASSEMBLER = 7,
SPV_GENERATOR_KHRONOS_GLSLANG = 8,
SPV_GENERATOR_KHRONOS_LINKER = 17,
SPV_GENERATOR_NUM_ENTRIES,
SPV_FORCE_16_BIT_ENUM(spv_generator_t)
} spv_generator_t;

View File

@@ -62,7 +62,7 @@ const char* spvTargetEnvDescription(spv_target_env env) {
case SPV_ENV_VULKAN_1_1:
return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_UNIVERSAL_1_4:
return "SPIR-V 1.4";
@@ -72,6 +72,9 @@ const char* spvTargetEnvDescription(spv_target_env env) {
return "SPIR-V 1.5";
case SPV_ENV_VULKAN_1_2:
return "SPIR-V 1.5 (under Vulkan 1.2 semantics)";
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return "";
}
@@ -102,7 +105,7 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) {
case SPV_ENV_VULKAN_1_1:
return SPV_SPIRV_VERSION_WORD(1, 3);
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_UNIVERSAL_1_4:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
@@ -110,6 +113,9 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) {
case SPV_ENV_UNIVERSAL_1_5:
case SPV_ENV_VULKAN_1_2:
return SPV_SPIRV_VERSION_WORD(1, 5);
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return SPV_SPIRV_VERSION_WORD(0, 0);
}
@@ -212,7 +218,10 @@ bool spvIsVulkanEnv(spv_target_env env) {
case SPV_ENV_VULKAN_1_2:
return true;
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return false;
@@ -246,7 +255,10 @@ bool spvIsOpenCLEnv(spv_target_env env) {
case SPV_ENV_OPENCL_2_2:
return true;
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return false;
@@ -280,7 +292,43 @@ bool spvIsOpenGLEnv(spv_target_env env) {
case SPV_ENV_OPENGL_4_5:
return true;
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return false;
}
bool spvIsValidEnv(spv_target_env env) {
switch (env) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_VULKAN_1_0:
case SPV_ENV_UNIVERSAL_1_1:
case SPV_ENV_UNIVERSAL_1_2:
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_OPENCL_1_2:
case SPV_ENV_OPENCL_EMBEDDED_1_2:
case SPV_ENV_OPENCL_2_0:
case SPV_ENV_OPENCL_EMBEDDED_2_0:
case SPV_ENV_OPENCL_EMBEDDED_2_1:
case SPV_ENV_OPENCL_EMBEDDED_2_2:
case SPV_ENV_OPENCL_2_1:
case SPV_ENV_OPENCL_2_2:
case SPV_ENV_UNIVERSAL_1_4:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
case SPV_ENV_UNIVERSAL_1_5:
case SPV_ENV_VULKAN_1_2:
case SPV_ENV_OPENGL_4_0:
case SPV_ENV_OPENGL_4_1:
case SPV_ENV_OPENGL_4_2:
case SPV_ENV_OPENGL_4_3:
case SPV_ENV_OPENGL_4_5:
return true;
case SPV_ENV_WEBGPU_0:
case SPV_ENV_MAX:
break;
}
return false;
@@ -320,7 +368,10 @@ std::string spvLogStringForEnv(spv_target_env env) {
return "Universal";
}
case SPV_ENV_WEBGPU_0:
assert(false);
assert(false && "Deprecated target environment value.");
break;
case SPV_ENV_MAX:
assert(false && "Invalid target environment value.");
break;
}
return "Unknown";

View File

@@ -28,6 +28,9 @@ bool spvIsOpenCLEnv(spv_target_env env);
// Returns true if |env| is an OPENGL environment, false otherwise.
bool spvIsOpenGLEnv(spv_target_env env);
// Returns true if |env| is an implemented/valid environment, false otherwise.
bool spvIsValidEnv(spv_target_env env);
// Returns the version number for the given SPIR-V target environment.
uint32_t spvVersionForTargetEnv(spv_target_env env);

View File

@@ -659,9 +659,9 @@ spv_result_t ValidateStructuredSelections(
// Mark the upcoming blocks as seen now, but only error out if this block
// was missing a merge instruction and both labels hadn't been seen
// previously.
const bool both_unseen =
seen.insert(true_label).second && seen.insert(false_label).second;
if (!merge && both_unseen) {
const bool true_label_unseen = seen.insert(true_label).second;
const bool false_label_unseen = seen.insert(false_label).second;
if (!merge && true_label_unseen && false_label_unseen) {
return _.diag(SPV_ERROR_INVALID_CFG, terminator)
<< "Selection must be structured";
}

View File

@@ -140,6 +140,8 @@ bool isMissingOffsetInStruct(uint32_t struct_id, ValidationState_t& vstate) {
for (auto& decoration : vstate.id_decorations(struct_id)) {
if (SpvDecorationOffset == decoration.dec_type() &&
Decoration::kInvalidMember != decoration.struct_member_index()) {
// Offset 0xffffffff is not valid so ignore it for simplicity's sake.
if (decoration.params()[0] == 0xffffffff) return true;
hasOffset[decoration.struct_member_index()] = true;
}
}
@@ -995,7 +997,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
const bool phys_storage_buffer =
storageClass == SpvStorageClassPhysicalStorageBufferEXT;
const bool workgroup = storageClass == SpvStorageClassWorkgroup;
const bool workgroup =
storageClass == SpvStorageClassWorkgroup &&
vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR);
if (uniform || push_constant || storage_buffer || phys_storage_buffer ||
workgroup) {
const auto ptrInst = vstate.FindDef(words[1]);

View File

@@ -66,6 +66,11 @@ bool CheckAllImageOperandsHandled() {
case SpvImageOperandsVolatileTexelKHRMask:
case SpvImageOperandsSignExtendMask:
case SpvImageOperandsZeroExtendMask:
// TODO(jaebaek): Move this line properly after handling image offsets
// operand. This line temporarily fixes CI failure that
// blocks other PRs.
// https://github.com/KhronosGroup/SPIRV-Tools/issues/4565
case SpvImageOperandsOffsetsMask:
return true;
}
return false;
@@ -281,13 +286,14 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
// the module to be invalid.
if (mask == 0) return SPV_SUCCESS;
if (spvtools::utils::CountSetBits(
mask & (SpvImageOperandsOffsetMask | SpvImageOperandsConstOffsetMask |
SpvImageOperandsConstOffsetsMask)) > 1) {
if (spvtools::utils::CountSetBits(mask & (SpvImageOperandsOffsetMask |
SpvImageOperandsConstOffsetMask |
SpvImageOperandsConstOffsetsMask |
SpvImageOperandsOffsetsMask)) > 1) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(4662)
<< "Image Operands Offset, ConstOffset, ConstOffsets cannot be used "
<< "together";
<< "Image Operands Offset, ConstOffset, ConstOffsets, Offsets "
"cannot be used together";
}
const bool is_implicit_lod = IsImplicitLod(opcode);
@@ -620,6 +626,10 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
// setup.
}
if (mask & SpvImageOperandsOffsetsMask) {
// TODO: add validation
}
return SPV_SUCCESS;
}
@@ -2058,11 +2068,13 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) {
std::string* message) {
const auto* models = state.GetExecutionModels(entry_point->id());
const auto* modes = state.GetExecutionModes(entry_point->id());
if (models->find(SpvExecutionModelGLCompute) != models->end() &&
modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
modes->end() &&
modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
modes->end()) {
if (models &&
models->find(SpvExecutionModelGLCompute) != models->end() &&
(!modes ||
(modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
modes->end() &&
modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
modes->end()))) {
if (message) {
*message =
std::string(

View File

@@ -318,10 +318,9 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) {
if (module_version < min_version) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvOpcodeString(opcode) << " requires "
<< spvTargetEnvDescription(
static_cast<spv_target_env>(min_version))
<< " at minimum.";
<< spvOpcodeString(opcode) << " requires SPIR-V version "
<< SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "."
<< SPV_SPIRV_VERSION_MINOR_PART(min_version) << " at minimum.";
}
} else if (!_.HasAnyOfExtensions(exts)) {
// Otherwise, we only error out when no enabling extensions are

View File

@@ -199,6 +199,10 @@ uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) {
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
num_components *= type->GetOperandAs<uint32_t>(2);
break;
case SpvOpTypeArray:
// Skip the array.
return NumConsumedComponents(_,
_.FindDef(type->GetOperandAs<uint32_t>(1)));
default:
// This is an error that is validated elsewhere.
break;
@@ -430,17 +434,36 @@ spv_result_t GetLocationsForVariable(
continue;
}
uint32_t end = (location + num_locations) * 4;
if (num_components != 0) {
start += component;
end = location * 4 + component + num_components;
}
for (uint32_t l = start; l < end; ++l) {
if (!locations->insert(l).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l / 4
<< ", component " << l % 4;
if (member->opcode() == SpvOpTypeArray && num_components >= 1 &&
num_components < 4) {
// When an array has an element that takes less than a location in
// size, calculate the used locations in a strided manner.
for (uint32_t l = location; l < num_locations + location; ++l) {
for (uint32_t c = component; c < component + num_components; ++c) {
uint32_t check = 4 * l + c;
if (!locations->insert(check).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l
<< ", component " << c;
}
}
}
} else {
// TODO: There is a hole here is the member is an array of 3- or
// 4-element vectors of 64-bit types.
uint32_t end = (location + num_locations) * 4;
if (num_components != 0) {
start += component;
end = location * 4 + component + num_components;
}
for (uint32_t l = start; l < end; ++l) {
if (!locations->insert(l).second) {
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
<< "Entry-point has conflicting " << storage_class
<< " location assignment at location " << l / 4
<< ", component " << l % 4;
}
}
}
}

View File

@@ -235,11 +235,11 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
}
if (!ok) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(4683)
<< _.VkErrorID(6426)
<< "In the Vulkan environment, GLCompute execution model "
"entry points require either the LocalSize execution "
"mode or an object decorated with WorkgroupSize must be "
"specified.";
"entry points require either the LocalSize or "
"LocalSizeId execution mode or an object decorated with "
"WorkgroupSize must be specified.";
}
}
break;

View File

@@ -1845,8 +1845,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677);
case 4682:
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
case 4683:
return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-04683);
case 6426:
return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-06426); // formally 04683
case 4685:
return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685);
case 4686: