This commit is contained in:
elwrcl committed 2026-06-06 13:25:15 +03:00
1 parent 04d4dc8b09
commit 818cab85dc
12511 files changed
+6156641

No files matched your search

+1
View File
@@ -0,0 +1 @@
max_width = 80
+4
View File
@@ -0,0 +1,4 @@
[[deqp]]
deqp = "/deqp-vk/external/vulkancts/modules/vulkan/deqp-vk"
caselists = ["/deqp-vk/mustpass/vk-main.txt"]
tests_per_group = 10000
+74
View File
@@ -0,0 +1,74 @@
.nvk-valve-rules:
stage: nouveau
rules:
- !reference [.test, rules]
- !reference [.valve-farm-rules, rules]
- !reference [.vulkan-rules, rules]
- changes: &nvk_file_list
- src/nouveau/*
- src/nouveau/codegen**/*
- src/nouveau/compiler/**/*
- src/nouveau/drm/**/*
- src/nouveau/nme/**/*
- src/nouveau/nil/**/*
- src/nouveau/nvidia-headers/**/*
- src/nouveau/vulkan/**/*
- src/nouveau/winsys/**/*
- src/nouveau/ci/gitlab-ci.yml
- src/nouveau/ci/gitlab-ci-inc.yml
- src/nouveau/ci/deqp-$DEQP_SUITE.toml
- src/nouveau/ci/$GPU_VERSION-fails.txt
- src/nouveau/ci/$GPU_VERSION-flakes.txt
- src/nouveau/ci/$GPU_VERSION-skips.txt
- src/nouveau/ci/$GPU_VERSION-vkd3d-fails.txt
- src/nouveau/ci/$GPU_VERSION-vkd3d-flakes.txt
- src/nouveau/ci/$GPU_VERSION-vkd3d-skips.txt
when: on_success
.nvk-valve-manual-rules:
stage: nouveau-nightly
extends: .no-auto-retry
rules:
- !reference [.test, rules]
- !reference [.valve-farm-manual-rules, rules]
- !reference [.vulkan-manual-rules, rules]
- changes:
*nvk_file_list
when: manual
.nvk-ci-tron-common:
variables:
CI_TRON_PATTERN__JOB_WARN__REGEX: 'nouveau .+ job timeout|NMI backtrace for cpu \d+'
CI_TRON_INITRAMFS__DEPMOD__URL: "https://gitlab.freedesktop.org/gfx-ci/boot2container/-/releases/$B2C_VERSION/downloads/linux-x86_64.depmod.cpio.xz"
CI_TRON_INITRAMFS__GPU__URL: "https://gitlab.freedesktop.org/gfx-ci/boot2container/-/releases/$B2C_VERSION/downloads/linux-x86_64.gpu.cpio"
CI_TRON_INITRAMFS__GPU__FORMAT__0__ARCHIVE__KEEP__0__PATH: ${GPU_FIRMWARE_KEEP_PATH_REGEX}
CI_TRON_KERNEL_CMDLINE__LOAD_MODULES: 'modules_load=nouveau'
# GSP is not loaded by default
CI_TRON_KERNEL_CMDLINE__NOUVEAU_LOAD_GSP: 'nouveau.config=NvGspRm=1'
.nvk-vkcts:
variables:
VK_DRIVER: nouveau
DEQP_SUITE: nvk
MESA_VK_IGNORE_CONFORMANCE_WARNING: 1
NVK_I_WANT_A_BROKEN_VULKAN_DRIVER: 1
.nvk-ga106-valve:
extends:
- .nvk-ci-tron-common
variables:
GPU_VERSION: nvk-ga106
FDO_CI_CONCURRENT: 16
RUNNER_FARM_LOCATION: mupuf
CI_TRON_DUT_SETUP_TAGS: nvidia:codename:GA106
GPU_FIRMWARE_KEEP_PATH_REGEX: '(lib/(modules|firmware/nvidia/ga10\d)/.*)'
.nvk-ad106-valve:
extends:
- .nvk-ci-tron-common
variables:
GPU_VERSION: nvk-ad106
FDO_CI_CONCURRENT: 32
RUNNER_FARM_LOCATION: keywords
CI_TRON_DUT_SETUP_TAGS: nvidia:codename:AD106
GPU_FIRMWARE_KEEP_PATH_REGEX: '(lib/(modules|firmware/nvidia/(ga|ad)10\d)/.*)'
+49
View File
@@ -0,0 +1,49 @@
include:
- local: 'src/nouveau/ci/gitlab-ci-inc.yml'
nvk-ga106-vkcts-valve:
extends:
- .ci-tron-x86_64-test-vk-manual
- .nvk-vkcts
- .nvk-ga106-valve
- .nvk-valve-manual-rules
timeout: 95m
variables:
CI_TRON_TIMEOUT__OVERALL__MINUTES: 90 # usual run time: 75min
nvk-ad106-vkcts-valve:
extends:
- .ci-tron-x86_64-test-vk-manual
- .nvk-vkcts
- .nvk-ad106-valve
- .nvk-valve-manual-rules
timeout: 60m
parallel: 2
variables:
CI_TRON_TIMEOUT__OVERALL__MINUTES: 55 # usual run time: 37min
nvk-ga106-vkd3d-valve:
extends:
- .ci-tron-x86_64-test-vk-manual
- .nvk-vkcts
- .nvk-ga106-valve
- .nvk-valve-manual-rules
- .test-vkd3d-proton
timeout: 15m
variables:
CI_TRON_TIMEOUT__OVERALL__MINUTES: 10 # usual run time: 5.5min
# Random tests fail/crash when trying to run them in parallel
FDO_CI_CONCURRENT: 1
nvk-ad106-vkd3d-valve:
extends:
- .ci-tron-x86_64-test-vk-manual
- .nvk-vkcts
- .nvk-ad106-valve
- .nvk-valve-manual-rules
- .test-vkd3d-proton
timeout: 15m
variables:
CI_TRON_TIMEOUT__OVERALL__MINUTES: 10 # usual run time: 5.5min
# Random tests fail/crash when trying to run them in parallel
FDO_CI_CONCURRENT: 1
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
dEQP-VK.api.external.semaphore.sync_fd.export_multiple_times_temporary
dEQP-VK.api.info.vulkan1p2.feature_bits_influence
dEQP-VK.api.object_management.multithreaded_shared_resources.device
dEQP-VK.draw.dynamic_rendering.complete_secondary_cmd_buff.shader_layer.tessellation_shader_256
dEQP-VK.draw.dynamic_rendering.complete_secondary_cmd_buff.shader_layer.vertex_shader_256
dEQP-VK.draw.dynamic_rendering.partial_secondary_cmd_buff.shader_layer.tessellation_shader_256
dEQP-VK.draw.dynamic_rendering.partial_secondary_cmd_buff.shader_layer.vertex_shader_256
dEQP-VK.draw.dynamic_rendering.primary_cmd_buff.shader_layer.tessellation_shader_256
dEQP-VK.draw.dynamic_rendering.primary_cmd_buff.shader_layer.vertex_shader_256
dEQP-VK.draw.renderpass.shader_layer.tessellation_shader_256
dEQP-VK.draw.renderpass.shader_layer.vertex_shader_256
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.*
dEQP-VK.pipeline.monolithic.blend.dual_source.multi_attachments.a2b10g10r10_unorm_pack32
dEQP-VK.pipeline.shader_object_unlinked_spirv.blend.dual_source.multi_attachments.r8g8b8a8_snorm
dEQP-VK.renderpass.suballocation.multisample_resolve.*
dEQP-VK.renderpass2.suballocation.multisample_resolve.*
dEQP-VK.synchronization.basic.timeline_semaphore.chain
dEQP-VK.wsi.display.create_display_mode
dEQP-VK.wsi.display.create_display_plane_surface
dEQP-VK.wsi.display.get_display_mode_properties
dEQP-VK.wsi.display.get_display_mode_properties2
dEQP-VK.wsi.display.get_display_plane_capabilities
dEQP-VK.wsi.display.get_display_plane_capabilities2
dEQP-VK.wsi.display.get_display_plane_properties
dEQP-VK.wsi.display.get_display_plane_properties2
dEQP-VK.wsi.display.get_display_plane_supported_displays
dEQP-VK.wsi.display.get_display_properties
dEQP-VK.wsi.display.get_display_properties2
dEQP-VK.wsi.display.surface_counters
+27
View File
@@ -0,0 +1,27 @@
# These tests are timeout'ing
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.memory.pipeline_barrier.host_write_transfer_src.1048576
dEQP-VK.memory.pipeline_barrier.host_write_vertex_buffer.65536_vertex_buffer_stride_4
dEQP-VK.memory.pipeline_barrier.transfer_src_transfer_dst.1048576
dEQP-VK.sparse_resources.image_sparse_binding.multiple_bind_sparse_info.2d_array.*
dEQP-VK.sparse_resources.image_sparse_binding.multiple_bind_sparse_info.cube_array.*
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_image_opaque_memory_bind_info.3d.*
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_image_opaque_memory_bind_info.cube_array.*
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_memory_bind.cube_array.*
dEQP-VK.sparse_resources.image_sparse_residency.cube_array.*
# https://github.com/KhronosGroup/VK-GL-CTS/issues/546
dEQP-VK.spirv_assembly.instruction.compute.untyped_pointers.vulkan_memory_model.cooperative_matrix.type_punning.*
# https://github.com/KhronosGroup/VK-GL-CTS/issues/553
dEQP-VK.spirv_assembly.instruction.compute.untyped_pointers.vulkan_memory_model.cooperative_matrix.mixed.load.*
+20
View File
@@ -0,0 +1,20 @@
# nouveau 0000:2d:00.0: gsp: mmu fault queued
# nouveau 0000:2d:00.0: gsp: rc engn:00000001 chid:24 type:31 scope:1 part:233
# nouveau 0000:2d:00.0: fifo:c00000:0003:0018:[d3d12[1056]] errored - disabling channel
# nouveau 0000:2d:00.0: d3d12[1056]: channel 24 killed!
test_uav_counter_null_behavior_dxbc,Crash
test_uav_counter_null_behavior_dxil,Crash
test_custom_border_color_limits,Fail
test_derivative_hoisting_dxil,Fail
test_undefined_structured_raw_alias_dxbc,Fail
test_undefined_structured_raw_alias_dxil,Fail
test_undefined_structured_raw_read_typed_dxbc,Fail
test_undefined_structured_raw_read_typed_dxil,Fail
test_large_buffer_descriptors,Crash
test_line_tessellation_dxbc,Fail
test_line_tessellation_dxil,Fail
test_tessellation_read_tesslevel,Fail
+6
View File
@@ -0,0 +1,6 @@
dEQP-VK.pipeline.fast_linked_library.pipeline_cache.robustness2.storage,Fail
dEQP-VK.pipeline.pipeline_library.pipeline_cache.robustness2.storage,Fail
dEQP-VK.pipeline.shader_object_linked_binary.pipeline_cache.robustness2.storage,Fail
dEQP-VK.pipeline.shader_object_linked_spirv.pipeline_cache.robustness2.storage,Fail
dEQP-VK.pipeline.shader_object_unlinked_binary.pipeline_cache.robustness2.storage,Fail
dEQP-VK.pipeline.shader_object_unlinked_spirv.pipeline_cache.robustness2.storage,Fail
+149
View File
@@ -0,0 +1,149 @@
dEQP-VK.api.copy_and_blit.core.image_to_image.dimensions.src32768x6_dst32768x6.r4g4b4a4_unorm_pack16.r16_sfloat.general_general
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.max.graphics_tese_sampler15_resource1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_frag_buffers3_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.mutable_descriptor.graphics_tesc_type_mask8
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.single.graphics_frag_sampled_image
dEQP-VK.binding_model.descriptorset_random.sets8.runtimesize.noubo.sbolimitlow.sampledimghigh.outimgtexlow.iublimithigh.nouab.frag.ialimitlow.0
dEQP-VK.binding_model.mutable_descriptor.single.uniform_texel_buffer.update_copy.nonmutable_source.normal_source.pool_same_types.update_after_bind.no_array.tese
dEQP-VK.binding_model.shader_access.primary_cmd_buf.bind.with_template.sampler_mutable.compute.multiple_contiguous_descriptors.1d_base_slice
dEQP-VK.binding_model.shader_access.primary_cmd_buf.bind.with_template.sampler_mutable.vertex_fragment.multiple_discontiguous_descriptor_sets.multiple_arbitrary_descriptors.2d
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.a8b8g8r8_srgb_pack32.samples_8
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.a8b8g8r8_uint_pack32.samples_8
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.b8g8r8a8_srgb.samples_8_resolve_level_3
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.b8g8r8a8_unorm.samples_8_baseLayer1
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.r16g16b16a16_sint.samples_8_resolve_level_4
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.r8g8b8a8_srgb.samples_8_resolve_level_4
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.r8g8b8a8_unorm.samples_4_resolve_level_3
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_3.r8g8b8a8_unorm.samples_8_resolve_level_3
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.layers_6.r32g32b32a32_uint.samples_2_resolve_level_4
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.r32g32b32a32_sint.samples_8_resolve_level_4
dEQP-VK.dynamic_rendering.primary_cmd_buff.suballocation.multisample_resolve.r8_uint.samples_8_resolve_level_2
dEQP-VK.image.host_image_copy.large_images.image_to_memory_r32_sfloat_4096_4096
dEQP-VK.image.host_image_copy.large_images.image_to_memory_r8g8b8a8_unorm_4096_4096
dEQP-VK.image.host_image_copy.optimal.image_to_image_copy.shader_read_only_optimal.stencil_read_only_optimal.51x63x1_1.64.r8g8b8a8_unorm
dEQP-VK.image.host_image_copy.optimal.image_to_image_copy.transfer_src_optimal.general.32x32x1_2.64.r16_sfloat
dEQP-VK.memory.pipeline_barrier.host_write_transfer_src.1048576
dEQP-VK.pipeline.*.pipeline_cache.robustness2.storage
dEQP-VK.pipeline.monolithic.creation_cache_control.compute_pipelines.duplicate_batch_pipelines_no_cache
dEQP-VK.rasterization.primitive_size.points.point_size_10000
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_3.a8b8g8r8_uint_pack32.samples_8
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_3.r16g16_unorm.samples_8_baseLayer1
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_3.r32g32b32a32_sint.samples_8_baseLayer1
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_3.r32g32b32a32_sint.samples_8_resolve_level_4
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_6.r16g16b16a16_sfloat.samples_4_resolve_level_4
dEQP-VK.renderpass.suballocation.multisample_resolve.layers_6.r32g32b32a32_sfloat.samples_4_resolve_level_4
dEQP-VK.renderpass2.suballocation.multisample_resolve.layers_3.a8b8g8r8_unorm_pack32.samples_8_resolve_level_3
dEQP-VK.renderpass2.suballocation.multisample_resolve.layers_3.r16g16b16a16_uint.samples_8_baseLayer1
dEQP-VK.renderpass2.suballocation.multisample_resolve.layers_3.r32g32b32a32_sfloat.samples_8
dEQP-VK.renderpass2.suballocation.multisample_resolve.layers_3.r8g8b8a8_snorm.samples_8
dEQP-VK.shader_object.rendering.color_attachment_count_4.extra_attachment_after_2.none.none.random_color_formats.after.none.r8g8b8a8_srgb_d24_unorm_s8_uint
dEQP-VK.sparse_resources.image_sparse_binding.cube_array.rg16ui.256_256_6
dEQP-VK.sparse_resources.image_sparse_binding.cube_array.rgba32i.256_256_6
dEQP-VK.sparse_resources.image_sparse_binding.multiple_bind_sparse_info.2d_array.rg16_snorm.1024_128_8
dEQP-VK.sparse_resources.image_sparse_binding.multiple_bind_sparse_info.3d.rgba32ui.1024_128_8
dEQP-VK.sparse_resources.image_sparse_binding.multiple_bind_sparse_info.cube_array.rg32ui.256_256_6
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_image_opaque_memory_bind_info.cube_array.rgba32i.256_256_6
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_image_opaque_memory_bind_info.cube_array.rgba32ui.256_256_6
dEQP-VK.sparse_resources.image_sparse_binding.multiple_sparse_memory_bind.cube_array.rgba32ui.256_256_6
dEQP-VK.sparse_resources.image_sparse_residency.cube.rgba16i.128_128_1
dEQP-VK.sparse_resources.transfer_queue.cube_array.rgba32ui.256_256_6
dEQP-VK.synchronization.signal_order.shared_timeline_semaphore.write_image_compute_indirect_read_blit_image.image_128x128_r16_uint_opaque_fd
dEQP-VK.synchronization.signal_order.shared_timeline_semaphore.write_image_fragment_read_image_vertex.image_128x128_r16g16b16a16_uint_opaque_fd
dEQP-VK.synchronization2.timeline_semaphore.wait_before_signal.write_image_tess_eval_read_image_tess_eval.image_128x128_r16g16b16a16_uint
dEQP-VK.wsi.display.create_display_mode
dEQP-VK.wsi.display.create_display_plane_surface
dEQP-VK.wsi.display.get_display_mode_properties
dEQP-VK.wsi.display.get_display_mode_properties2
dEQP-VK.wsi.display.get_display_plane_capabilities
dEQP-VK.wsi.display.get_display_plane_capabilities2
dEQP-VK.wsi.display.get_display_plane_properties
dEQP-VK.wsi.display.get_display_plane_properties2
dEQP-VK.wsi.display.get_display_plane_supported_displays
dEQP-VK.wsi.display.get_display_properties
dEQP-VK.wsi.display.get_display_properties2
dEQP-VK.wsi.display.surface_counters
KHR-GL46.conditional_render_inverted.functional
KHR-GL46.gpu_shader_fp64.builtin.abs_dvec2
KHR-GL46.gpu_shader_fp64.builtin.abs_dvec4
KHR-GL46.gpu_shader_fp64.builtin.ceil_dvec2
KHR-GL46.gpu_shader_fp64.builtin.clamp_against_scalar_dvec2
KHR-GL46.gpu_shader_fp64.builtin.clamp_against_scalar_dvec3
KHR-GL46.gpu_shader_fp64.builtin.clamp_dvec4
KHR-GL46.gpu_shader_fp64.builtin.dot_dvec3
KHR-GL46.gpu_shader_fp64.builtin.floor_dvec3
KHR-GL46.gpu_shader_fp64.builtin.fma_dvec2
KHR-GL46.gpu_shader_fp64.builtin.fma_dvec3
KHR-GL46.gpu_shader_fp64.builtin.fract_double
KHR-GL46.gpu_shader_fp64.builtin.fract_dvec3
KHR-GL46.gpu_shader_fp64.builtin.frexp_double
KHR-GL46.gpu_shader_fp64.builtin.frexp_dvec4
KHR-GL46.gpu_shader_fp64.builtin.greaterthan_dvec4
KHR-GL46.gpu_shader_fp64.builtin.inverse_dmat2
KHR-GL46.gpu_shader_fp64.builtin.inversesqrt_double
KHR-GL46.gpu_shader_fp64.builtin.ldexp_dvec4
KHR-GL46.gpu_shader_fp64.builtin.length_dvec2
KHR-GL46.gpu_shader_fp64.builtin.lessthanequal_dvec2
KHR-GL46.gpu_shader_fp64.builtin.lessthanequal_dvec3
KHR-GL46.gpu_shader_fp64.builtin.lessthanequal_dvec4
KHR-GL46.gpu_shader_fp64.builtin.matrixcompmult_dmat4
KHR-GL46.gpu_shader_fp64.builtin.max_double
KHR-GL46.gpu_shader_fp64.builtin.max_dvec2
KHR-GL46.gpu_shader_fp64.builtin.max_dvec4
KHR-GL46.gpu_shader_fp64.builtin.min_against_scalar_dvec2
KHR-GL46.gpu_shader_fp64.builtin.min_against_scalar_dvec3
KHR-GL46.gpu_shader_fp64.builtin.min_double
KHR-GL46.gpu_shader_fp64.builtin.min_dvec3
KHR-GL46.gpu_shader_fp64.builtin.min_dvec4
KHR-GL46.gpu_shader_fp64.builtin.mix_double
KHR-GL46.gpu_shader_fp64.builtin.mix_dvec3
KHR-GL46.gpu_shader_fp64.builtin.mix_dvec4
KHR-GL46.gpu_shader_fp64.builtin.mod_against_scalar_dvec2
KHR-GL46.gpu_shader_fp64.builtin.mod_against_scalar_dvec4
KHR-GL46.gpu_shader_fp64.builtin.mod_double
KHR-GL46.gpu_shader_fp64.builtin.mod_dvec2
KHR-GL46.gpu_shader_fp64.builtin.normalize_dvec2
KHR-GL46.gpu_shader_fp64.builtin.normalize_dvec4
KHR-GL46.gpu_shader_fp64.builtin.notequal_dvec3
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat2
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat2x3
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat2x4
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat3
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat3x4
KHR-GL46.gpu_shader_fp64.builtin.outerproduct_dmat4x2
KHR-GL46.gpu_shader_fp64.builtin.reflect_dvec3
KHR-GL46.gpu_shader_fp64.builtin.smoothstep_double
KHR-GL46.gpu_shader_fp64.builtin.smoothstep_dvec2
KHR-GL46.gpu_shader_fp64.builtin.sqrt_dvec2
KHR-GL46.gpu_shader_fp64.builtin.step_against_scalar_dvec2
KHR-GL46.gpu_shader_fp64.builtin.step_dvec2
KHR-GL46.gpu_shader_fp64.builtin.step_dvec3
KHR-GL46.gpu_shader_fp64.builtin.step_dvec4
KHR-GL46.gpu_shader_fp64.builtin.trunc_dvec2
KHR-GL46.gpu_shader_fp64.builtin.unpackdouble2x32_double
KHR-GL46.packed_pixels.pbo_rectangle.rg16f_format_rg
KHR-GL46.packed_pixels.pbo_rectangle.rg8_snorm_format_rg
KHR-GL46.packed_pixels.pbo_rectangle.rgb32f_format_rgb
KHR-GL46.packed_pixels.pbo_rectangle.rgba16_format_bgra
KHR-GL46.packed_pixels.varied_rectangle.rgb32f_format_rgb
KHR-GL46.texture_buffer.texture_buffer_max_size
KHR-GL46.texture_lod_bias.texture_lod_bias_all
dEQP-GLES3.functional.occlusion_query.conservative_depth_write_stencil_clear
dEQP-GLES31.functional.copy_image.mixed.viewclass_128_bits_mixed.rgba32i_rgba_astc_8x6_khr.texture2d_array_to_texture2d_array
dEQP-GLES31.functional.copy_image.mixed.viewclass_128_bits_mixed.rgba32i_srgb8_alpha8_astc_8x6_khr.renderbuffer_to_texture2d_array
dEQP-GLES31.functional.synchronization.inter_invocation.ssbo_atomic_alias_overwrite
glx@glx-multithread-texture
spec@arb_compute_variable_group_size@local-size
spec@arb_point_parameters@arb_point_parameters-point-attenuation
spec@arb_point_parameters@arb_point_parameters-point-attenuation@Aliased combinations
+22
View File
@@ -0,0 +1,22 @@
# Too slow
dEQP-VK.binding_model.descriptor_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.draw.*.shader_layer.(vertex|tessellation)_shader_256
dEQP-VK.memory.pipeline_barrier.transfer_src_transfer_dst.1048576
dEQP-VK.reconvergence.(sub|work)group_uniform_control_flow_(ballot|elect).compute.nesting4.(0.40|7.10)
# New timeouts in 1.4.1.0
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_binding_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.sparse_residency_buffer.multiple.graphics_vert_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.compute_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_comp_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_frag_buffers32_sets1
dEQP-VK.binding_model.descriptor_buffer.traditional_buffer.multiple.graphics_vert_buffers32_sets1
+20
View File
@@ -0,0 +1,20 @@
# nouveau 0000:2d:00.0: gsp: mmu fault queued
# nouveau 0000:2d:00.0: gsp: rc engn:00000001 chid:24 type:31 scope:1 part:233
# nouveau 0000:2d:00.0: fifo:c00000:0003:0018:[d3d12[1056]] errored - disabling channel
# nouveau 0000:2d:00.0: d3d12[1056]: channel 24 killed!
test_uav_counter_null_behavior_dxbc,Crash
test_uav_counter_null_behavior_dxil,Crash
test_custom_border_color_limits,Fail
test_derivative_hoisting_dxil,Fail
test_undefined_structured_raw_alias_dxbc,Fail
test_undefined_structured_raw_alias_dxil,Fail
test_undefined_structured_raw_read_typed_dxbc,Fail
test_undefined_structured_raw_read_typed_dxil,Fail
test_large_buffer_descriptors,Crash
test_line_tessellation_dxbc,Fail
test_line_tessellation_dxil,Fail
test_tessellation_read_tesslevel,Fail
+5
View File
@@ -0,0 +1,5 @@
# To use this config on you editor, follow the instructions at:
# http://editorconfig.org
[*.rs]
max_line_length = 80
+81
View File
@@ -0,0 +1,81 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
// Generated by random.org
const INIT_DATA: [u16; 64] = [
8235, 45378, 61861, 23858, 42241, 27301, 23697, 15333, 52828, 39161, 46572,
7478, 11198, 4926, 21736, 23781, 37217, 20323, 32754, 61367, 12882, 35352,
29343, 26458, 62858, 44415, 35786, 58466, 45062, 30330, 30350, 35134,
28228, 36077, 26793, 27223, 52219, 52544, 3565, 56253, 40962, 40833, 35607,
46482, 64735, 31252, 44165, 40273, 46825, 15929, 27160, 1106, 59035, 48146,
47365, 19901, 37178, 35614, 29628, 23344, 11328, 60105, 28727, 344,
];
/// An implementation of the ACORN random number generator
///
/// See also <http://acorn.wikramaratna.org/concept.html>
///
/// This isn't cryptographically secure but it's easy and fast and good enough
/// for generating test data. More importantly, we own the implementation so it
/// won't change with random library or compiler upgrades.
pub struct Acorn {
y_n: [u64; Self::K],
}
impl Acorn {
const ORD: usize = 60;
const K: usize = 16;
pub fn new() -> Self {
let mut y_0 = [0_u64; Self::K];
for m in 0..Self::K {
y_0[m] |= u64::from(INIT_DATA[m * 4 + 0]) << 0;
y_0[m] |= u64::from(INIT_DATA[m * 4 + 1]) << 16;
y_0[m] |= u64::from(INIT_DATA[m * 4 + 2]) << 32;
y_0[m] |= u64::from(INIT_DATA[m * 4 + 3]) << 48;
y_0[m] &= (1u64 << Self::ORD) - 1;
}
// For good randomness, the seed should be odd
assert!(y_0[0] % 2 == 1);
Acorn { y_n: y_0 }
}
fn next_u60(&mut self) -> u64 {
// y_n[0] remains uncanged
for m in 1..Self::K {
// We go in increasing M so y_n[m] is y_(n-1)[m] when we read it but
// self.y_n[m - 1] is what it says it is
self.y_n[m] = (self.y_n[m] + self.y_n[m - 1]) % (1 << Self::ORD);
}
self.y_n[Self::K - 1]
}
pub fn get_uint(&mut self, bits: usize) -> u64 {
if bits <= Self::ORD {
self.next_u60() >> (Self::ORD - bits)
} else {
let hi = self.next_u60() << (bits - Self::ORD);
hi | self.get_uint(bits - Self::ORD)
}
}
pub fn get_bool(&mut self) -> bool {
self.get_uint(1) != 0
}
pub fn get_u8(&mut self) -> u8 {
self.get_uint(8) as u8
}
pub fn get_u16(&mut self) -> u16 {
self.get_uint(16) as u16
}
pub fn get_u32(&mut self) -> u32 {
self.get_uint(32) as u32
}
pub fn get_u64(&mut self) -> u64 {
self.get_uint(64)
}
}
@@ -0,0 +1,209 @@
#! /usr/bin/env python3
#
# Copyright © 2024 Collabora Ltd. and Red Hat Inc.
# SPDX-License-Identifier: MIT
# This script takes a list of Rust files, each of the form nvh_path_to_mod.rs
# and constructs a lib.rs which puts each of them in ::path::to::mod.
import argparse
import csv
import os
import sys
from mako import template
TEMPLATE_RS = template.Template(text="""\
// Copyright 2024 Red Hat Inc.
// SPDX-License-Identifier: MIT
// This file is generated by lat_rs_gen.py. DO NOT EDIT!
#![allow(unused_variables)]
const fn pred(has_pred: bool, a: u32, b: u32) -> u32 {
if has_pred {
a + b
} else {
b
}
}
% for reg_file, cats in file_cats.items():
<% enum_name = to_camel(reg_file) + 'Latency' + sm.upper() %>
#[derive(PartialEq)]
pub enum ${enum_name} {
% for category in cats[0].header.cats:
${to_camel(category)},
% endfor
}
impl ${to_camel(reg_file)}Latency${sm.upper()} {
% for bigcat in cats:
pub fn ${bigcat.header.latcat}(
${bigcat.header.cat0}: ${enum_name},
${bigcat.header.cat1}: ${enum_name},
has_pred: bool
) -> u32 {
use ${enum_name}::*;
match ${bigcat.header.cat1} {
% for cat in bigcat.header.cats:
#[allow(clippy::match_single_binding)]
${to_camel(cat)} => match ${bigcat.header.cat0} {
<% has_non = False %>
% for cat2 in bigcat.header.cats:
% if bigcat.fields[loop.parent.index].flds[cat2].pred == True:
${to_camel(cat2)} => pred(
has_pred,
${bigcat.fields[loop.parent.index].flds[cat2].value},
${bigcat.fields[loop.parent.index].flds[cat2].pred_val}
),
% elif bigcat.fields[loop.parent.index].flds[cat2].value != "none":
${to_camel(cat2)} => ${bigcat.fields[loop.parent.index].flds[cat2].value},
% else:
<% has_none = True %>
% endif
% endfor
% if has_none:
_ => panic!("Illegal ${bigcat.header.cat0} value in ${bigcat.header.latcat} for ${to_camel(cat)}"),
% endif
}
% endfor
}
}
% endfor
}
% endfor
""")
## A mere convenience to convert snake_case to CamelCase. Numbers are prefixed
## with "_".
def to_camel(snake_str):
result = ''.join(word.title() for word in snake_str.split('_'))
return result if not result[0].isdigit() else '_' + result
def reader(csvfile):
"""Wrapper around csv.reader that skips comments and blanks."""
# csv.reader actually reads the file one line at a time (it was designed to
# open excel generated sheets), so hold the file until all of the lines are
# read.
with open(csvfile, 'r') as f:
for line in csv.reader(f):
if line and not line[0].startswith('#'):
yield line
class Fld(object):
def __init__(self, line):
if "none" in line:
self.valid = False
else:
self.valid = True
self.pred = False
if "+" in line:
self.pred = True
part = line.split("+")
self.value = part[0]
self.pred_val = part[1]
elif " & sb" in line:
self.scoreboard = True
self.value = line.removesuffix(" & sb");
else:
self.scoreboard = False
self.value = line.strip()
class Header(object):
def __init__(self, line):
self.latcat = line[0].strip()
self.cats = line[1:]
if self.latcat == "raw":
self.cat0 = "writer"
self.cat1 = "reader"
elif self.latcat == "war":
self.cat0 = "reader"
self.cat1 = "writer"
elif self.latcat == "waw":
self.cat0 = "writer1"
self.cat1 = "writer2"
class Fields(object):
def __init__(self, header, line):
self.fldcat = line[0].strip()
self.flds = {}
for index, cat in enumerate(header.cats):
self.flds[cat] = Fld(line[index + 1])
class Category(object):
def __init__(self, header, fields):
self.header = header
self.fields = fields
lattypes = ["reg", "ureg", "pred", "upred"]
def emit_cats(dirname, f, sm, lat):
cats = []
for index, cat in enumerate(["raw", "war", "waw"]):
first_line = False
fields = []
for l in reader(dirname + "sm" + sm + "/" + lat + "_" + cat + ".csv"):
if first_line == False:
header = Header(l)
first_line = True
else:
fields.append(Fields(header, l))
cats.append(Category(header, fields))
try:
f.write(TEMPLATE_RS.render(lat=lat, sm=sm, cats=cats))
except Exception:
# In the event there's an error, this imports some helpers from mako
# to print a useful stack trace and prints it, then exits with
# status 1, if python is run with debug; otherwise it just raises
# the exception
import sys
from mako import exceptions
print(exceptions.text_error_template().render(), file=sys.stderr)
sys.exit(1)
def emit_sm(dirname, f, sm):
for lat in lattypes:
emit_cats(dirname, f, sm, lat)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--import-path', required=True)
parser.add_argument('--out-rs', required=True, help='Output Rust file.')
parser.add_argument('--sm', help='SM', required=True)
parser.add_argument('csv_files', metavar='FILE', nargs='*',
action='append',
help='Input CSV filename')
args = parser.parse_args()
sys.path.insert(0, args.import_path)
import util
file_cats = {}
for csv_file in args.csv_files[0]:
split = os.path.basename(csv_file).removesuffix('.csv').split('_')
assert len(split) == 2
reg_file = split[0]
latcat = split[1]
r = reader(csv_file)
header = Header(next(r))
fields = [Fields(header, l) for l in r]
cat = Category(header, fields)
file_cats.setdefault(reg_file, []).append(cat)
environment = dict(
sm=args.sm,
file_cats=file_cats,
to_camel=to_camel,
)
util.write_template_rs(args.out_rs, TEMPLATE_RS, environment)
if __name__ == '__main__':
main()
@@ -0,0 +1,39 @@
#! /usr/bin/env python3
#
# Copyright © 2024 Collabora Ltd. and Red Hat Inc.
# SPDX-License-Identifier: MIT
# This script takes a list of Rust files, each of the form nvh_path_to_mod.rs
# and constructs a lib.rs which puts each of them in ::path::to::mod.
import argparse
import sys
from mako.template import Template
TEMPLATE_RS = Template("""\
// Copyright © 2024 Collabora Ltd. and Red Hat Inc.
// SPDX-License-Identifier: MIT
// This file is generated by lib_rs_gen.py. DO NOT EDIT!
% for mod in mods:
pub mod ${mod};
% endfor
""")
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--import-path', required=True)
parser.add_argument('--out-rs', required=True, help='Output Rust file.')
parser.add_argument('mods', metavar='MOD', nargs='*',
action='append', help='Submodule')
args = parser.parse_args()
sys.path.insert(0, args.import_path)
import util
util.write_template_rs(args.out_rs, TEMPLATE_RS, dict(mods=args.mods[0]))
if __name__ == '__main__':
main()
@@ -0,0 +1,60 @@
# Copyright © 2025 Collabora, Ltd.
# SPDX-License-Identifier: MIT
_nak_lat_sm100_files = [
'pred_raw.csv',
'pred_war.csv',
'pred_waw.csv',
'reg_raw.csv',
'reg_war.csv',
'reg_waw.csv',
'upred_raw.csv',
'upred_war.csv',
'upred_waw.csv',
'ureg_raw.csv',
'ureg_war.csv',
'ureg_waw.csv',
]
_nak_lat_sms = {
'sm100': _nak_lat_sm100_files,
}
_lat_rs_gen = files('lat_rs_gen.py')
_lat_rs_generated = []
foreach sm, csvs : _nak_lat_sms
csv_files = []
foreach i : range(csvs.length())
csv_files += files(sm + '/' + csvs[i])
endforeach
_lat_rs_generated += custom_target(
sm+'.rs',
input : [_lat_rs_gen, csv_files],
output : [sm+'.rs'],
command : [
prog_python, '@INPUT@', '-p', nouveau_util_py_path,
'--out-rs', '@OUTPUT0@', '--sm', sm,
],
depend_files : nouveau_util_py,
)
endforeach
_nak_latencies_lib_rs = custom_target(
'lib.rs',
input : ['lib_rs_gen.py', _lat_rs_generated, nouveau_util_py],
output : ['lib.rs'],
command : [
prog_python, '@INPUT0@', '-p', nouveau_util_py_path,
'--out-rs', '@OUTPUT0@', _nak_lat_sms.keys()
],
)
libnak_latencies_rs = static_library(
'nak_latencies',
_nak_latencies_lib_rs,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
rust_args : rust_global_args,
)
@@ -0,0 +1,13 @@
raw,disp_alu,disp_dual_alu,coupled,dualalu,r2p,r2ur,fma,fp16,hfma2_mma,redirected_fp64,decoupled,guard
disp_alu,13,13,13,13,13,13,13,13,14,14,1 & sb,none
disp_dual_alu,13,13,13,13,13,13,13,13,14,14,1 & sb,none
coupled,4,4,4,4,5,8,5,5,6,6,1 & sb,none
dualalu,4,4,4,4,5,8,5,5,6,6,1 & sb,none
r2p,4,4,4,4,5,8,5,5,6,6,1 & sb,none
r2ur,none,none,none,none,none,none,none,none,none,none,none,none
fma,5,5,5,5,5,13,4,5,6,6,1 & sb,none
fp16,13,13,13,13,13,13,13,5,14,14,1 & sb,none
hfma2_mma,13,13,13,13,13,13,13,13,6,6,1 & sb,none
redirected_fp64,13,13,13,13,13,13,13,13,6,6,1 & sb,none
decoupled,13,13,13,13,13,13,13,13,14,14,1 & sb,none
guard,13,13,13,13,13,13,13,13,14,14,1 & sb,none
1 raw disp_alu disp_dual_alu coupled dualalu r2p r2ur fma fp16 hfma2_mma redirected_fp64 decoupled guard
2 disp_alu 13 13 13 13 13 13 13 13 14 14 1 & sb none
3 disp_dual_alu 13 13 13 13 13 13 13 13 14 14 1 & sb none
4 coupled 4 4 4 4 5 8 5 5 6 6 1 & sb none
5 dualalu 4 4 4 4 5 8 5 5 6 6 1 & sb none
6 r2p 4 4 4 4 5 8 5 5 6 6 1 & sb none
7 r2ur none none none none none none none none none none none none
8 fma 5 5 5 5 5 13 4 5 6 6 1 & sb none
9 fp16 13 13 13 13 13 13 13 5 14 14 1 & sb none
10 hfma2_mma 13 13 13 13 13 13 13 13 6 6 1 & sb none
11 redirected_fp64 13 13 13 13 13 13 13 13 6 6 1 & sb none
12 decoupled 13 13 13 13 13 13 13 13 14 14 1 & sb none
13 guard 13 13 13 13 13 13 13 13 14 14 1 & sb none
@@ -0,0 +1,14 @@
war,disp_alu,disp_dual_alu,coupled,dualalu,r2p,r2ur,fma,fp16,hfma2_mma,redirected_fp64,decoupled,guard
disp_alu,1,1,1,1,1,none,1,1,1,1,1,1
disp_dual_alu,1,1,1,1,1,none,1,1,1,1,1,1
coupled,1,1,1,1,1,none,1,1,1,1,1,1
dualalu,1,1,1,1,1,none,1,1,1,1,1,1
r2p,1,1,1,1,1,none,1,1,1,1,1,1
r2ur,1,1,1,1,1,none,1,1,1,1,1,1
fma,1,1,1,1,1,none,1,1,1,1,1,1
fp16,1,1,1,1,1,none,1,1,1,1,1,1
hfma2_mma,1,1,1,1,1,none,1,1,1,1,1,1
redirected_fp64,1,1,1,1,1,none,1,1,1,1,1,1
decoupled,1,1,1,1,1,none,1,1,1,1,1,1
guard,none,none,none,none,none,none,none,none,none,none,none,none
1 war disp_alu disp_dual_alu coupled dualalu r2p r2ur fma fp16 hfma2_mma redirected_fp64 decoupled guard
2 disp_alu 1 1 1 1 1 none 1 1 1 1 1 1
3 disp_dual_alu 1 1 1 1 1 none 1 1 1 1 1 1
4 coupled 1 1 1 1 1 none 1 1 1 1 1 1
5 dualalu 1 1 1 1 1 none 1 1 1 1 1 1
6 r2p 1 1 1 1 1 none 1 1 1 1 1 1
7 r2ur 1 1 1 1 1 none 1 1 1 1 1 1
8 fma 1 1 1 1 1 none 1 1 1 1 1 1
9 fp16 1 1 1 1 1 none 1 1 1 1 1 1
10 hfma2_mma 1 1 1 1 1 none 1 1 1 1 1 1
11 redirected_fp64 1 1 1 1 1 none 1 1 1 1 1 1
12 decoupled 1 1 1 1 1 none 1 1 1 1 1 1
13 guard none none none none none none none none none none none none
@@ -0,0 +1,13 @@
waw,disp_alu,disp_dual_alu,coupled,dualalu,r2p,r2ur,fma,fp16,hfma2_mma,redirected_fp64,decoupled,guard
disp_alu,1,1,1,1,1,1+7,1,1,2,2,1 & sb,none
disp_dual_alu,1,1,1,1,1,1+7,1,1,2,2,1 & sb,none
coupled,1,1,1,1,1,1+7,1,1,2,2,1 & sb,none
dualalu,1,1,1,1,1,1+7,1,1,2,2,1 & sb,none
r2p,1,1,1,1,1,1+7,1,1,2,2,1 & sb,none
r2ur,1,1,1,1,1,1,1,1,2,2,1 & sb,none
fma,1,1,1,1,1,1+8,1,1,2,2,1 & sb,none
fp16,2+6,2+6,2+6,2+6,2+6,2+6,2+6,1,2+7,2+7,1 & sb,none
hfma2_mma,2+5,2+5,2+5,2+5,2+5,2+5,2+5,2+5,1,1,1 & sb,none
redirected_fp64,2+5,2+5,2+5,2+5,2+5,2+5,2+5,2+5,1,1,1 & sb,none
decoupled,2+10,2+10,2+10,2+10,2+10,2+10,2+10,2+10,1+12,1+12,1 & sb,none
guard,none,none,none,none,none,none,none,none,none,none,none,none
1 waw disp_alu disp_dual_alu coupled dualalu r2p r2ur fma fp16 hfma2_mma redirected_fp64 decoupled guard
2 disp_alu 1 1 1 1 1 1+7 1 1 2 2 1 & sb none
3 disp_dual_alu 1 1 1 1 1 1+7 1 1 2 2 1 & sb none
4 coupled 1 1 1 1 1 1+7 1 1 2 2 1 & sb none
5 dualalu 1 1 1 1 1 1+7 1 1 2 2 1 & sb none
6 r2p 1 1 1 1 1 1+7 1 1 2 2 1 & sb none
7 r2ur 1 1 1 1 1 1 1 1 2 2 1 & sb none
8 fma 1 1 1 1 1 1+8 1 1 2 2 1 & sb none
9 fp16 2+6 2+6 2+6 2+6 2+6 2+6 2+6 1 2+7 2+7 1 & sb none
10 hfma2_mma 2+5 2+5 2+5 2+5 2+5 2+5 2+5 2+5 1 1 1 & sb none
11 redirected_fp64 2+5 2+5 2+5 2+5 2+5 2+5 2+5 2+5 1 1 1 & sb none
12 decoupled 2+10 2+10 2+10 2+10 2+10 2+10 2+10 2+10 1+12 1+12 1 & sb none
13 guard none none none none none none none none none none none none
@@ -0,0 +1,23 @@
raw,alu,dualalu,disp_64,fma,fma_alu,imad_wide_read_ab,imad_wide_read_cl,imad_wide_read_ch,imad_wide_write_dl,imad_wide_write_dh,fp16,fp16_alu,fp16_f32,hfma2_mma,redirected_fp64,imma,hmma,dmma,branch,decoupled,decoupled_agu
alu,4,4,6,5,5,none,none,none,3,5,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
dualalu,4,4,6,5,5,none,none,none,3,5,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
disp_64,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
fma,5,5,6,4,4,none,none,none,2,4,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
fma_alu,5,5,6,4,4,none,none,none,2,4,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_read_ab,5,5,6,4,4,none,none,none,4,6,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_read_cl,5,5,6,4,4,none,none,none,2,4,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_read_ch,3,3,4,2,2,none,none,none,2,2,3,3,3,8,8,19,19,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_write_dl,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_write_dh,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
fp16,5,5,6,5,5,none,none,none,3,5,4,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
fp16_alu,5,5,6,5,5,none,none,none,3,5,5,4,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
fp16_f32,5,5,6,5,5,none,none,none,3,5,5,5,5,10,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
hfma2_mma,6,6,6,6,6,none,none,none,6,6,6,6,6,8,10,19,19,1 & sb,1 & sb,1 & sb,1 & sb
redirected_fp64,6,6,6,6,6,none,none,none,6,6,6,6,6,8,8,19,19,1 & sb,1 & sb,1 & sb,1 & sb
imma,7,7,7,7,7,none,none,none,7,7,7,7,7,11,11,20,20,1 & sb,1 & sb,1 & sb,1 & sb
hmma,7,7,7,7,7,none,none,none,7,7,7,7,7,11,11,20,20,1 & sb,1 & sb,1 & sb,1 & sb
dmma,7,7,7,7,7,none,none,none,7,7,7,7,7,11,11,20,20,1 & sb,1 & sb,1 & sb,1 & sb
branch,4,4,4,4,4,none,none,none,4,4,4,4,4,6,6,19,19,1 & sb,1 & sb,1 & sb,1 & sb
decoupled,4,4,4,4,4,none,none,none,4,4,4,4,4,6,6,19,19,1 & sb,1 & sb,1 & sb,1 & sb
decoupled_agu,5,5,5,5,5,none,none,none,5,5,5,5,5,7,7,19,19,1 & sb,1 & sb,1 & sb,1 & sb
1 raw alu dualalu disp_64 fma fma_alu imad_wide_read_ab imad_wide_read_cl imad_wide_read_ch imad_wide_write_dl imad_wide_write_dh fp16 fp16_alu fp16_f32 hfma2_mma redirected_fp64 imma hmma dmma branch decoupled decoupled_agu
2 alu 4 4 6 5 5 none none none 3 5 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
3 dualalu 4 4 6 5 5 none none none 3 5 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
4 disp_64 none none none none none none none none none none none none none none none none none none none none none
5 fma 5 5 6 4 4 none none none 2 4 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
6 fma_alu 5 5 6 4 4 none none none 2 4 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
7 imad_wide_read_ab 5 5 6 4 4 none none none 4 6 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
8 imad_wide_read_cl 5 5 6 4 4 none none none 2 4 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
9 imad_wide_read_ch 3 3 4 2 2 none none none 2 2 3 3 3 8 8 19 19 1 & sb 1 & sb 1 & sb 1 & sb
10 imad_wide_write_dl none none none none none none none none none none none none none none none none none none none none none
11 imad_wide_write_dh none none none none none none none none none none none none none none none none none none none none none
12 fp16 5 5 6 5 5 none none none 3 5 4 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
13 fp16_alu 5 5 6 5 5 none none none 3 5 5 4 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
14 fp16_f32 5 5 6 5 5 none none none 3 5 5 5 5 10 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
15 hfma2_mma 6 6 6 6 6 none none none 6 6 6 6 6 8 10 19 19 1 & sb 1 & sb 1 & sb 1 & sb
16 redirected_fp64 6 6 6 6 6 none none none 6 6 6 6 6 8 8 19 19 1 & sb 1 & sb 1 & sb 1 & sb
17 imma 7 7 7 7 7 none none none 7 7 7 7 7 11 11 20 20 1 & sb 1 & sb 1 & sb 1 & sb
18 hmma 7 7 7 7 7 none none none 7 7 7 7 7 11 11 20 20 1 & sb 1 & sb 1 & sb 1 & sb
19 dmma 7 7 7 7 7 none none none 7 7 7 7 7 11 11 20 20 1 & sb 1 & sb 1 & sb 1 & sb
20 branch 4 4 4 4 4 none none none 4 4 4 4 4 6 6 19 19 1 & sb 1 & sb 1 & sb 1 & sb
21 decoupled 4 4 4 4 4 none none none 4 4 4 4 4 6 6 19 19 1 & sb 1 & sb 1 & sb 1 & sb
22 decoupled_agu 5 5 5 5 5 none none none 5 5 5 5 5 7 7 19 19 1 & sb 1 & sb 1 & sb 1 & sb
@@ -0,0 +1,23 @@
war,alu,dualalu,disp_64,fma,fma_alu,imad_wide_read_ab,imad_wide_read_cl,imad_wide_read_ch,imad_wide_write_dl,imad_wide_write_dh,fp16,fp16_alu,fp16_f32,hfma2_mma,redirected_fp64,imma,hmma,dmma,branch,decoupled,decoupled_agu
alu,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
dualalu,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
disp_64,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
fma,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
fma_alu,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_read_ab,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_read_cl,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_read_ch,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_write_dl,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_write_dh,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
fp16,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
fp16_alu,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
fp16_f32,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
hfma2_mma,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
redirected_fp64,1,1,1,1,1,1,1,1,none,none,1,1,1,1,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
imma,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
hmma,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
dmma,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
branch,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
decoupled,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
decoupled_agu,2,2,2,2,2,2,2,2,none,none,2,2,2,2,1,1,1,1 & sb,1 & sb,1 & sb,1 & sb
1 war alu dualalu disp_64 fma fma_alu imad_wide_read_ab imad_wide_read_cl imad_wide_read_ch imad_wide_write_dl imad_wide_write_dh fp16 fp16_alu fp16_f32 hfma2_mma redirected_fp64 imma hmma dmma branch decoupled decoupled_agu
2 alu 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
3 dualalu 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
4 disp_64 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
5 fma 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
6 fma_alu 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
7 imad_wide_read_ab none none none none none none none none none none none none none none none none none none none none none
8 imad_wide_read_cl none none none none none none none none none none none none none none none none none none none none none
9 imad_wide_read_ch none none none none none none none none none none none none none none none none none none none none none
10 imad_wide_write_dl 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
11 imad_wide_write_dh 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
12 fp16 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
13 fp16_alu 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
14 fp16_f32 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
15 hfma2_mma 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
16 redirected_fp64 1 1 1 1 1 1 1 1 none none 1 1 1 1 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
17 imma 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
18 hmma 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
19 dmma 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
20 branch 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
21 decoupled 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
22 decoupled_agu 2 2 2 2 2 2 2 2 none none 2 2 2 2 1 1 1 1 & sb 1 & sb 1 & sb 1 & sb
@@ -0,0 +1,22 @@
waw,alu,dualalu,disp_64,fma,fma_alu,imad_wide_read_ab,imad_wide_read_cl,imad_wide_read_ch,imad_wide_write_dl,imad_wide_write_dh,fp16,fp16_alu,fp16_f32,hfma2_mma,redirected_fp64,imma,hmma,dmma,branch,decoupled,decoupled_agu
alu,1,1,1+1,1,1,none,none,none,1,1,1,1,1,3+3,3+3,14+2,14+2,1 & sb,1 & sb,1 & sb,1 & sb
dualalu,1,1,1+1,1,1,none,none,none,1,1,1,1,1,3+3,3+3,14+2,14+2,1 & sb,1 & sb,1 & sb,1 & sb
disp_64,1,1,1,1,1,none,none,none,1,1,1,1,1,3+1,3+1,14+1,14+1,1 & sb,1 & sb,1 & sb,1 & sb
fma,1,1,1+1,1,1,none,none,none,1,1+1,1,1,1,3+3,3+3,14+3,14+3,1 & sb,1 & sb,1 & sb,1 & sb
fma_alu,1,1,1+1,1,1,none,none,none,1,1+1,1,1,1,3+3,3+3,14+3,14+3,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_read_ab,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_read_cl,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_read_ch,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
imad_wide_write_dl,1+2,1+2,1+3,1+1,1+1,none,none,none,1,1+1,1+2,1+2,1+2,5+3,5+3,14+3,14+3,1 & sb,1 & sb,1 & sb,1 & sb
imad_wide_write_dh,1,1,1+1,1,1,none,none,none,1,1,1,1,1,5+1,5+1,14+3,14+3,1 & sb,1 & sb,1 & sb,1 & sb
fp16,1,1,1+1,1,1,none,none,none,1,1,1,1,1,3+3,3+3,14+2,14+2,1 & sb,1 & sb,1 & sb,1 & sb
fp16_alu,1,1,1+1,1,1,none,none,none,1,1,1,1,1,3+3,3+3,14+2,14+2,1 & sb,1 & sb,1 & sb,1 & sb
fp16_f32,1,1,1,1,1,none,none,none,1,1,1,1,1,3+2,3+2,14+2,14+2,1 & sb,1 & sb,1 & sb,1 & sb
hfma2_mma,1,1,1,1,1,none,none,none,1,1,1,1,1,1,3,14,14,1 & sb,1 & sb,1 & sb,1 & sb
redirected_fp64,1,1,1,1,1,none,none,none,1,1,1,1,1,2,1,13,13,1 & sb,1 & sb,1 & sb,1 & sb
imma,2,2,2,2,2,none,none,none,2,2,2,2,2,2,2,1,1,1 & sb,1 & sb,1 & sb,1 & sb
hmma,2,2,2,2,2,none,none,none,2,2,2,2,2,2,2,1,1,1 & sb,1 & sb,1 & sb,1 & sb
dmma,2+4,2+4,2+4,2+4,2+4,none,none,none,2+4,2+4,2+4,2+4,2+4,2+8,2+8,10+9,10+9,1 & sb,1 & sb,1 & sb,1 & sb
branch,1+5,1+5,1+5,1+5,1+5,none,none,none,1+5,1+5,1+5,1+5,1+5,1+9,1+9,13+6,13+6,1 & sb,1 & sb,1 & sb,1 & sb
decoupled,1+5,1+5,1+5,1+5,1+5,none,none,none,1+5,1+5,1+5,1+5,1+5,1+9,1+9,13+6,13+6,1 & sb,1 & sb,1 & sb,1 & sb
decoupled_agu,1+5,1+5,1+5,1+5,1+5,none,none,none,1+5,1+5,1+5,1+5,1+5,1+9,1+9,13+6,13+6,1 & sb,1 & sb,1 & sb,1 & sb
1 waw alu dualalu disp_64 fma fma_alu imad_wide_read_ab imad_wide_read_cl imad_wide_read_ch imad_wide_write_dl imad_wide_write_dh fp16 fp16_alu fp16_f32 hfma2_mma redirected_fp64 imma hmma dmma branch decoupled decoupled_agu
2 alu 1 1 1+1 1 1 none none none 1 1 1 1 1 3+3 3+3 14+2 14+2 1 & sb 1 & sb 1 & sb 1 & sb
3 dualalu 1 1 1+1 1 1 none none none 1 1 1 1 1 3+3 3+3 14+2 14+2 1 & sb 1 & sb 1 & sb 1 & sb
4 disp_64 1 1 1 1 1 none none none 1 1 1 1 1 3+1 3+1 14+1 14+1 1 & sb 1 & sb 1 & sb 1 & sb
5 fma 1 1 1+1 1 1 none none none 1 1+1 1 1 1 3+3 3+3 14+3 14+3 1 & sb 1 & sb 1 & sb 1 & sb
6 fma_alu 1 1 1+1 1 1 none none none 1 1+1 1 1 1 3+3 3+3 14+3 14+3 1 & sb 1 & sb 1 & sb 1 & sb
7 imad_wide_read_ab none none none none none none none none none none none none none none none none none none none none none
8 imad_wide_read_cl none none none none none none none none none none none none none none none none none none none none none
9 imad_wide_read_ch none none none none none none none none none none none none none none none none none none none none none
10 imad_wide_write_dl 1+2 1+2 1+3 1+1 1+1 none none none 1 1+1 1+2 1+2 1+2 5+3 5+3 14+3 14+3 1 & sb 1 & sb 1 & sb 1 & sb
11 imad_wide_write_dh 1 1 1+1 1 1 none none none 1 1 1 1 1 5+1 5+1 14+3 14+3 1 & sb 1 & sb 1 & sb 1 & sb
12 fp16 1 1 1+1 1 1 none none none 1 1 1 1 1 3+3 3+3 14+2 14+2 1 & sb 1 & sb 1 & sb 1 & sb
13 fp16_alu 1 1 1+1 1 1 none none none 1 1 1 1 1 3+3 3+3 14+2 14+2 1 & sb 1 & sb 1 & sb 1 & sb
14 fp16_f32 1 1 1 1 1 none none none 1 1 1 1 1 3+2 3+2 14+2 14+2 1 & sb 1 & sb 1 & sb 1 & sb
15 hfma2_mma 1 1 1 1 1 none none none 1 1 1 1 1 1 3 14 14 1 & sb 1 & sb 1 & sb 1 & sb
16 redirected_fp64 1 1 1 1 1 none none none 1 1 1 1 1 2 1 13 13 1 & sb 1 & sb 1 & sb 1 & sb
17 imma 2 2 2 2 2 none none none 2 2 2 2 2 2 2 1 1 1 & sb 1 & sb 1 & sb 1 & sb
18 hmma 2 2 2 2 2 none none none 2 2 2 2 2 2 2 1 1 1 & sb 1 & sb 1 & sb 1 & sb
19 dmma 2+4 2+4 2+4 2+4 2+4 none none none 2+4 2+4 2+4 2+4 2+4 2+8 2+8 10+9 10+9 1 & sb 1 & sb 1 & sb 1 & sb
20 branch 1+5 1+5 1+5 1+5 1+5 none none none 1+5 1+5 1+5 1+5 1+5 1+9 1+9 13+6 13+6 1 & sb 1 & sb 1 & sb 1 & sb
21 decoupled 1+5 1+5 1+5 1+5 1+5 none none none 1+5 1+5 1+5 1+5 1+5 1+9 1+9 13+6 13+6 1 & sb 1 & sb 1 & sb 1 & sb
22 decoupled_agu 1+5 1+5 1+5 1+5 1+5 none none none 1+5 1+5 1+5 1+5 1+5 1+9 1+9 13+6 13+6 1 & sb 1 & sb 1 & sb 1 & sb
@@ -0,0 +1,8 @@
raw,coupled,udp,voteu,u_guard,bra_jmp,uldc_mma,usetmaxreg
coupled,none,6,1,none,none,none,1 & sb
udp,none,4,1,none,none,none,1 & sb
voteu,none,none,none,none,none,none,none
u_guard,none,11,5,none,none,none,1 & sb
bra_jmp,none,9,2,none,none,none,1 & sb
uldc_mma,none,11,5,none,none,none,1 & sb
usetmaxreg,none,none,none,none,none,none,none
1 raw coupled udp voteu u_guard bra_jmp uldc_mma usetmaxreg
2 coupled none 6 1 none none none 1 & sb
3 udp none 4 1 none none none 1 & sb
4 voteu none none none none none none none
5 u_guard none 11 5 none none none 1 & sb
6 bra_jmp none 9 2 none none none 1 & sb
7 uldc_mma none 11 5 none none none 1 & sb
8 usetmaxreg none none none none none none none
@@ -0,0 +1,9 @@
war,coupled,udp,voteu,u_guard,bra_jmp,uldc_mma,usetmaxreg
coupled,none,none,none,none,none,none,none
udp,1,1,none,1,1,1,none
voteu,2,2,none,1,1,1,none
u_guard,none,none,none,none,none,none,none
bra_jmp,none,none,none,none,none,none,none
uldc_mma,none,none,none,none,none,none,none
usetmaxreg,1,1,none,1,1,1,none
1 war coupled udp voteu u_guard bra_jmp uldc_mma usetmaxreg
2 coupled none none none none none none none
3 udp 1 1 none 1 1 1 none
4 voteu 2 2 none 1 1 1 none
5 u_guard none none none none none none none
6 bra_jmp none none none none none none none
7 uldc_mma none none none none none none none
8 usetmaxreg 1 1 none 1 1 1 none
@@ -0,0 +1,8 @@
waw,coupled,udp,voteu,u_guard,bra_jmp,uldc_mma,usetmaxreg
coupled,none,none,none,none,none,none,none
udp,none,1,1,none,none,none,1 & sb
voteu,none,7,1,none,none,none,1 & sb
u_guard,none,none,none,none,none,none,none
bra_jmp,none,none,none,none,none,none,none
uldc_mma,none,none,none,none,none,none,none
usetmaxreg,none,8+2,8,none,none,none,1 & sb
1 waw coupled udp voteu u_guard bra_jmp uldc_mma usetmaxreg
2 coupled none none none none none none none
3 udp none 1 1 none none none 1 & sb
4 voteu none 7 1 none none none 1 & sb
5 u_guard none none none none none none none
6 bra_jmp none none none none none none none
7 uldc_mma none none none none none none none
8 usetmaxreg none 8+2 8 none none none 1 & sb
@@ -0,0 +1,18 @@
raw,coupled,coupled_mma,decoupled,branch,coupled_bindless,decoupled_bindless,hfma2_mma,to_ur,tex,tma,rpcmov_64,udp,uldc,umov,elect,r2ur,voteu
coupled,none,none,none,none,none,none,none,1 & sb,none,none,none,6,2,2,2,13,2
coupled_mma,none,none,none,none,none,none,none,1 & sb,none,none,none,6,2,2,2,13,2
decoupled,none,none,none,none,none,none,none,1 & sb,none,none,none,9,2,2,2,13,2
branch,none,none,none,none,none,none,none,1 & sb,none,none,none,10,3,3,3,13,3
coupled_bindless,none,none,none,none,none,none,none,1 & sb,none,none,none,12,5,5,5,15,5
decoupled_bindless,none,none,none,none,none,none,none,1 & sb,none,none,none,12,5,5,5,15,5
hfma2_mma,none,none,none,none,none,none,none,1 & sb,none,none,none,9,2,2,2,13,2
to_ur,none,none,none,none,none,none,none,1 & sb,none,none,none,12,5,5,5,13,5
tex,none,none,none,none,none,none,none,1 & sb,none,none,none,9,2,2,2,13,2
tma,none,none,none,none,none,none,none,1 & sb,none,none,none,9,2,2,2,13,2
rpcmov_64,none,none,none,none,none,none,none,1 & sb,none,none,none,9,2,2,2,13,2
udp,none,none,none,none,none,none,none,1 & sb,none,none,none,4,2,2,2,13,2
uldc,none,none,none,none,none,none,none,1 & sb,none,none,none,12,5,5,5,15,5
umov,none,none,none,none,none,none,none,1 & sb,none,none,none,7,2,2,2,13,2
elect,none,none,none,none,none,none,none,1 & sb,none,none,none,8,2,2,2,13,2
r2ur,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
voteu,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
1 raw coupled coupled_mma decoupled branch coupled_bindless decoupled_bindless hfma2_mma to_ur tex tma rpcmov_64 udp uldc umov elect r2ur voteu
2 coupled none none none none none none none 1 & sb none none none 6 2 2 2 13 2
3 coupled_mma none none none none none none none 1 & sb none none none 6 2 2 2 13 2
4 decoupled none none none none none none none 1 & sb none none none 9 2 2 2 13 2
5 branch none none none none none none none 1 & sb none none none 10 3 3 3 13 3
6 coupled_bindless none none none none none none none 1 & sb none none none 12 5 5 5 15 5
7 decoupled_bindless none none none none none none none 1 & sb none none none 12 5 5 5 15 5
8 hfma2_mma none none none none none none none 1 & sb none none none 9 2 2 2 13 2
9 to_ur none none none none none none none 1 & sb none none none 12 5 5 5 13 5
10 tex none none none none none none none 1 & sb none none none 9 2 2 2 13 2
11 tma none none none none none none none 1 & sb none none none 9 2 2 2 13 2
12 rpcmov_64 none none none none none none none 1 & sb none none none 9 2 2 2 13 2
13 udp none none none none none none none 1 & sb none none none 4 2 2 2 13 2
14 uldc none none none none none none none 1 & sb none none none 12 5 5 5 15 5
15 umov none none none none none none none 1 & sb none none none 7 2 2 2 13 2
16 elect none none none none none none none 1 & sb none none none 8 2 2 2 13 2
17 r2ur none none none none none none none none none none none none none none none none none
18 voteu none none none none none none none none none none none none none none none none none
@@ -0,0 +1,18 @@
war,coupled,coupled_mma,decoupled,branch,coupled_bindless,decoupled_bindless,hfma2_mma,to_ur,tex,tma,rpcmov_64,udp,uldc,umov,elect,r2ur,voteu
coupled,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
coupled_mma,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
decoupled,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
branch,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
coupled_bindless,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
decoupled_bindless,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
hfma2_mma,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
to_ur,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,1,1,1,1,none,none
tex,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
tma,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
rpcmov_64,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
udp,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,1,1,1,1,none,none
uldc,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,3,1,1,1,none,none
umov,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,3,1,1,1,none,none
elect,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,3,1,1,1,none,none
r2ur,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,1,1,1,1,none,none
voteu,1,1,1,1,1,1,1,1 & sb,1,1 & sb,1,3,1,1,1,none,none
1 war coupled coupled_mma decoupled branch coupled_bindless decoupled_bindless hfma2_mma to_ur tex tma rpcmov_64 udp uldc umov elect r2ur voteu
2 coupled none none none none none none none none none none none none none none none none none
3 coupled_mma none none none none none none none none none none none none none none none none none
4 decoupled none none none none none none none none none none none none none none none none none
5 branch none none none none none none none none none none none none none none none none none
6 coupled_bindless none none none none none none none none none none none none none none none none none
7 decoupled_bindless none none none none none none none none none none none none none none none none none
8 hfma2_mma none none none none none none none none none none none none none none none none none
9 to_ur 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 1 1 1 1 none none
10 tex none none none none none none none none none none none none none none none none none
11 tma none none none none none none none none none none none none none none none none none
12 rpcmov_64 none none none none none none none none none none none none none none none none none
13 udp 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 1 1 1 1 none none
14 uldc 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 3 1 1 1 none none
15 umov 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 3 1 1 1 none none
16 elect 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 3 1 1 1 none none
17 r2ur 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 1 1 1 1 none none
18 voteu 1 1 1 1 1 1 1 1 & sb 1 1 & sb 1 3 1 1 1 none none
@@ -0,0 +1,19 @@
waw,coupled,coupled_mma,decoupled,branch,coupled_bindless,decoupled_bindless,hfma2_mma,to_ur,tex,tma,rpcmov_64,udp,uldc,umov,elect,r2ur,voteu
coupled,none,none,none,none,none,none,none,1 & sb,none,none,none,none,none,none,none,none,none
coupled_mma,none,none,none,none,none,none,none,1 & sb,none,none,none,none,none,none,none,none,none
decoupled,none,none,none,none,none,none,none,1 & sb,none,none,none,none,none,none,none,none,none
branch,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
coupled_bindless,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
decoupled_bindless,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
hfma2_mma,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
to_ur,none,none,none,none,none,none,none,1 & sb,none,none,none,4+7,4,4,4,4+10,4
tex,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
tma,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
rpcmov_64,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none,none
udp,none,none,none,none,none,none,none,1 & sb,none,none,none,1,1,1,1,4+5,1
uldc,none,none,none,none,none,none,none,1 & sb,none,none,none,7,1,1,1,10+1,1
umov,none,none,none,none,none,none,none,1 & sb,none,none,none,7,1,1,1,10+1,1
elect,none,none,none,none,none,none,none,1 & sb,none,none,none,7,1,1,1,10+1,1
r2ur,none,none,none,none,none,none,none,1 & sb,none,none,none,7,1,1,1,1,1
voteu,none,none,none,none,none,none,none,1 & sb,none,none,none,7,1,1,1,10+1,1
1 waw coupled coupled_mma decoupled branch coupled_bindless decoupled_bindless hfma2_mma to_ur tex tma rpcmov_64 udp uldc umov elect r2ur voteu
2 coupled none none none none none none none 1 & sb none none none none none none none none none
3 coupled_mma none none none none none none none 1 & sb none none none none none none none none none
4 decoupled none none none none none none none 1 & sb none none none none none none none none none
5 branch none none none none none none none none none none none none none none none none none
6 coupled_bindless none none none none none none none none none none none none none none none none none
7 decoupled_bindless none none none none none none none none none none none none none none none none none
8 hfma2_mma none none none none none none none none none none none none none none none none none
9 to_ur none none none none none none none 1 & sb none none none 4+7 4 4 4 4+10 4
10 tex none none none none none none none none none none none none none none none none none
11 tma none none none none none none none none none none none none none none none none none
12 rpcmov_64 none none none none none none none none none none none none none none none none none
13 udp none none none none none none none 1 & sb none none none 1 1 1 1 4+5 1
14 uldc none none none none none none none 1 & sb none none none 7 1 1 1 10+1 1
15 umov none none none none none none none 1 & sb none none none 7 1 1 1 10+1 1
16 elect none none none none none none none 1 & sb none none none 7 1 1 1 10+1 1
17 r2ur none none none none none none none 1 & sb none none none 7 1 1 1 1 1
18 voteu none none none none none none none 1 & sb none none none 7 1 1 1 10+1 1
+184
View File
@@ -0,0 +1,184 @@
# Copyright © 2022 Collabora, Ltd.
# SPDX-License-Identifier: MIT
subdir('latencies')
dep_paste = dependency('paste',
version : '>= 1.0.14',
fallback : ['paste-1-rs', 'dep_paste'],
required : true,
)
dep_rustc_hash = dependency('rustc-hash',
version: '>= 2.0',
fallback: ['rustc-hash-2-rs', 'dep_rustc_hash'],
required: true,
)
libnak_c_files = files(
'nak.h',
'nak_nir.c',
'nak_nir_lower_cf.c',
'nak_nir_lower_cmat.c',
'nak_nir_lower_fs_inputs.c',
'nak_nir_lower_gs_intrinsics.c',
'nak_nir_lower_image_addrs.c',
'nak_nir_lower_mesh_intrinsics.c',
'nak_nir_lower_non_uniform_ldcx.c',
'nak_nir_lower_scan_reduce.c',
'nak_nir_lower_shared_atomics.c',
'nak_nir_lower_tex.c',
'nak_nir_lower_vtg_io.c',
'nak_nir_mark_lcssa_invariants.c',
'nak_nir_rematerialize_load_const.c',
)
_libacorn_rs = static_library(
'acorn',
files('acorn/lib.rs'),
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
rust_args : nouveau_rust_args,
)
idep_acorn_rs = declare_dependency(
link_with : _libacorn_rs,
)
libnak_deps = [
idep_mesautil,
idep_nir_headers,
idep_nvidia_headers,
idep_nil_h,
]
_nak_bindings_rs = rust.bindgen(
input : ['nak_bindings.h'],
output : 'nak_bindings.rs',
c_args : [
pre_args,
],
args : [
bindgen_output_args,
compiler_rs_bindgen_blocklist,
'--raw-line', 'use compiler::bindings::*;',
'--allowlist-type', 'drm.*',
'--allowlist-type', 'nak_.*',
'--allowlist-type', 'nouveau_ws_.*',
'--allowlist-var', 'DRM_.*',
'--allowlist-var', 'NAK_.*',
'--allowlist-var', 'NVIDIA_VENDOR_ID',
'--allowlist-function', 'drm.*',
'--allowlist-function', 'nak_.*',
'--allowlist-function', 'nouveau_ws_.*',
# provided through compiler::bindings::*
'--blocklist-type', 'glsl_.*',
'--no-prepend-enum-name',
'--with-derive-default',
],
dependencies : [
dep_libdrm,
idep_nouveau_ws,
libnak_deps,
],
)
_libnak_bindings_rs = static_library(
'nak_bindings',
_nak_bindings_rs,
gnu_symbol_visibility : 'hidden',
dependencies : [
idep_compiler_rs,
],
rust_abi : 'rust',
)
_libnak_ir_proc_rs = rust.proc_macro(
'nak_ir_proc',
files('nak/ir_proc.rs'),
dependencies : [idep_compiler_proc_rs],
rust_args : rust_global_args,
)
_libnak_rs = static_library(
'nak_rs',
files('nak/lib.rs'),
gnu_symbol_visibility : 'hidden',
rust_abi : 'c',
rust_args : [
nouveau_rust_args,
# Otherwise, rustc trips up on -pthread
'-Clink-arg=-Wno-unused-command-line-argument',
],
dependencies : [
dep_paste,
dep_rustc_hash,
idep_bitview_rs,
idep_compiler_rs,
idep_nvidia_headers_rs,
],
link_with : [
_libnak_bindings_rs,
_libnak_ir_proc_rs,
libnak_latencies_rs,
],
)
# TODO: Linking Rust executables (such as unit tests) doesn't play nicely
# with the sanitizers because meson doesn't know to pass -fsanitize to the
# Rust linker. See also https://github.com/mesonbuild/meson/issues/11741
#
# Also, don't build on Android because we do cross-builds and, unlike most
# tests, these depend on libdrm and that confuses the build system.
if with_tests and get_option('b_sanitize') == 'none' and not with_platform_android
rust.test(
'nak',
_libnak_rs,
args : [
# Don't run HW or nvdisasm tests by default
'--skip', 'hw_tests::',
'--skip', 'nvdisasm_tests::',
],
suite : ['nouveau'],
dependencies : [
dep_libdrm,
idep_nouveau_ws,
idep_compiler.partial_dependency(link_args : true, links : true),
idep_mesautil.partial_dependency(link_args : true, links : true),
idep_nv_push_rs,
],
# This is needed to ensure we link against glibc
# See also https://gitlab.freedesktop.org/mesa/mesa/-/issues/11632
rust_args: ['-C', 'default-linker-libraries'],
link_with: [
_libacorn_rs,
],
)
endif
nak_nir_algebraic_c = custom_target(
'nak_nir_algebraic.c',
input : 'nak_nir_algebraic.py',
output : 'nak_nir_algebraic.c',
command : [
prog_python, '@INPUT@',
'-p', dir_compiler_nir,
'--out', '@OUTPUT@',
],
depend_files : nir_algebraic_depends,
)
_libnak = static_library(
'nak',
[libnak_c_files, nak_nir_algebraic_c],
include_directories : [inc_include, inc_src],
dependencies : libnak_deps,
link_with : [_libnak_rs],
c_args : [no_override_init_args],
gnu_symbol_visibility : 'hidden',
)
idep_nak = declare_dependency(
include_directories : include_directories('.'),
link_with : _libnak,
)
+339
View File
@@ -0,0 +1,339 @@
/*
* Copyright © 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef NAK_H
#define NAK_H
#include "compiler/shader_enums.h"
#include "nir_defines.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define NAK_SUBGROUP_SIZE 32
struct nak_compiler;
struct nv_device_info;
struct nak_compiler *nak_compiler_create(const struct nv_device_info *dev);
void nak_compiler_destroy(struct nak_compiler *nak);
uint64_t nak_debug_flags(const struct nak_compiler *nak);
const struct nir_shader_compiler_options *
nak_nir_options(const struct nak_compiler *nak);
void nak_preprocess_nir(nir_shader *nir, const struct nak_compiler *nak);
bool nak_nir_lower_image_addrs(nir_shader *nir, const struct nak_compiler *nak);
struct nak_sample_location {
uint8_t x_u4 : 4;
uint8_t y_u4 : 4;
};
static_assert(sizeof(struct nak_sample_location) == 1,
"This struct has no holes");
struct nak_sample_mask {
uint16_t sample_mask;
};
static_assert(sizeof(struct nak_sample_mask) == 2,
"This struct has no holes");
PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct nak_fs_key {
bool zs_self_dep;
/** True if sample shading is forced on via an API knob such as
* VkPipelineMultisampleStateCreateInfo::minSampleShading
*/
bool force_sample_shading;
bool uses_underestimate;
uint8_t pad;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct nak_fs_key) == 4, "This struct has no holes");
struct nak_constant_offset_info {
/**
* The constant buffer index and offset at which the sample locations and
* pass sample masks tables lives.
*/
uint8_t sample_info_cb;
/**
* The offset into sample_info_cb at which the sample locations live. The
* sample locations table is an array of nak_sample_location where each
* sample location is two 4-bit unorm values packed into an 8-bit value
* with the bottom 4 bits for x and the top 4 bits for y.
*/
uint32_t sample_locations_offset;
/**
* The offset into sample_info_cb at which the sample masks table lives.
* The sample masks table is an array of nak_sample_mask where each entry
* represents the set of samples covered by that pass corresponding to that
* sample in a multi-pass fragment shader invocaiton.
*/
uint32_t sample_masks_offset;
/**
* The constant buffer index at which the printf buffer pointer lives.
*/
uint8_t printf_cb;
/**
* The offset into printf_cb for the printf buffer pointer.
*/
uint32_t printf_buffer_offset;
};
const extern struct nak_constant_offset_info nak_const_offsets_base;
const extern struct nak_constant_offset_info nak_const_offsets_turing_graphics;
#define NAK_PRINTF_BUFFER_SIZE 0x40000
#ifdef NDEBUG
#define NAK_CAN_PRINTF false
#else
#define NAK_CAN_PRINTF true
#endif
void nak_postprocess_nir(nir_shader *nir, const struct nak_compiler *nak,
nir_variable_mode robust2_modes,
const struct nak_fs_key *fs_key,
bool has_task_shader);
enum ENUM_PACKED nak_ts_domain {
NAK_TS_DOMAIN_ISOLINE = 0,
NAK_TS_DOMAIN_TRIANGLE = 1,
NAK_TS_DOMAIN_QUAD = 2,
};
enum ENUM_PACKED nak_ts_spacing {
NAK_TS_SPACING_INTEGER = 0,
NAK_TS_SPACING_FRACT_ODD = 1,
NAK_TS_SPACING_FRACT_EVEN = 2,
};
enum PACKED nak_mesh_topology {
NAK_MESH_TOPOLOGY_POINTS = 0,
NAK_MESH_TOPOLOGY_LINES = 1,
NAK_MESH_TOPOLOGY_TRIANGLES = 4,
};
struct nak_xfb_info {
uint32_t stride[4];
uint8_t stream[4];
uint8_t attr_count[4];
uint8_t attr_index[4][128];
};
/* This struct MUST have explicit padding fields to ensure that all padding is
* zeroed and the zeros get properly copied, even across API boundaries.
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wpadded"
struct nak_shader_info {
mesa_shader_stage stage;
/** Shader model */
uint8_t sm;
/** Number of GPRs used */
uint8_t num_gprs;
/**
* Number of control barriers used
*
* These are barriers in the sense of glsl barrier(), not reconvergence
* barriers. In CUDA, these barriers have an index, but we currently
* only use index zero for vulkan, which means this will be at most 1.
*/
uint8_t num_control_barriers;
uint8_t _pad0;
/** Maximum number of warps per SM based on static information */
uint32_t max_warps_per_sm;
/** Number of instructions used */
uint32_t num_instrs;
/** Number of cycles used by fixed-latency instructions */
uint64_t num_static_cycles;
/** Number of spills from GPRs to Memory */
uint32_t num_spills_to_mem;
/** Number of fills from Memory to GPRs */
uint32_t num_fills_from_mem;
/** Number of spills between register files */
uint32_t num_spills_to_reg;
/** Number of fills between register files */
uint32_t num_fills_from_reg;
/** Size of shader local (scratch) memory */
uint32_t slm_size;
/** Size of call/return stack in bytes/warp */
uint32_t crs_size;
union {
struct {
/* Local workgroup size */
uint16_t local_size[3];
/* Shared memory size */
uint16_t smem_size;
uint8_t _pad[132];
} cs;
struct {
bool writes_depth;
bool reads_sample_mask;
bool post_depth_coverage;
bool uses_sample_shading;
bool early_fragment_tests;
uint8_t _pad[135];
} fs;
struct {
enum nak_ts_domain domain;
enum nak_ts_spacing spacing;
bool ccw;
bool point_mode;
uint8_t _pad[136];
} ts;
struct {
uint32_t gs_hdr[32];
uint16_t max_primitives;
uint16_t max_vertices;
uint16_t local_size;
uint16_t smem_size;
enum nak_mesh_topology topology;
/** Shader header for GS stage when per primitive outputs are used */
bool has_gs_sph;
bool has_task_shader;
uint8_t _pad[1];
} mesh;
struct {
uint16_t local_size;
uint16_t payload_smem_size;
uint16_t smem_size;
uint8_t _pad[130];
} task;
/* Used to initialize the union for other stages */
uint8_t _pad[140];
};
struct {
bool writes_layer;
bool writes_point_size;
bool writes_vprs_table_index;
uint8_t clip_enable;
uint8_t cull_enable;
uint8_t _pad[3];
struct nak_xfb_info xfb;
} vtg;
uint8_t _pad1[4];
/** Shader header for 3D stages */
uint32_t hdr[32];
};
#pragma GCC diagnostic pop
struct nak_shader_bin {
struct nak_shader_info info;
uint32_t code_size;
const void *code;
const char *asm_str;
};
void nak_shader_bin_destroy(struct nak_shader_bin *bin);
struct nak_shader_bin *
nak_compile_shader(nir_shader *nir, bool dump_asm,
const struct nak_compiler *nak,
nir_variable_mode robust2_modes,
const struct nak_fs_key *fs_key,
bool has_task_shader);
struct nak_qmd_cbuf {
uint32_t index;
uint32_t size;
uint64_t addr;
};
struct nak_qmd_info {
uint64_t addr;
uint32_t smem_size;
uint32_t global_size[3];
uint32_t num_cbufs;
struct nak_qmd_cbuf cbufs[8];
};
#define NAK_QMD_ALIGN_B 256
#define NAK_MAX_QMD_SIZE_B 384
#define NAK_MAX_QMD_DWORDS (NAK_MAX_QMD_SIZE_B / 4)
uint32_t nak_qmd_size_B(const struct nv_device_info *dev);
void nak_fill_qmd(const struct nv_device_info *dev,
const struct nak_shader_info *info,
const struct nak_qmd_info *qmd_info,
void *qmd_out, size_t qmd_size);
struct nak_qmd_dispatch_size_layout {
uint16_t x_start, x_end;
uint16_t y_start, y_end;
uint16_t z_start, z_end;
uint16_t local_x_start, local_x_end;
uint16_t local_y_start, local_y_end;
uint16_t local_z_start, local_z_end;
};
struct nak_qmd_dispatch_size_layout
nak_get_qmd_dispatch_size_layout(const struct nv_device_info *dev);
struct nak_qmd_cbuf_desc_layout {
uint16_t addr_shift;
uint16_t addr_lo_start, addr_lo_end;
uint16_t addr_hi_start, addr_hi_end;
};
struct nak_qmd_cbuf_desc_layout
nak_get_qmd_cbuf_desc_layout(const struct nv_device_info *dev, uint8_t idx);
#ifdef __cplusplus
}
#endif
#endif /* NAK_H */
+567
View File
@@ -0,0 +1,567 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::from_nir::*;
use crate::ir::{
ShaderInfo, ShaderIoInfo, ShaderModel, ShaderModelInfo, ShaderStageInfo,
};
use crate::sph;
use compiler::bindings::*;
use nak_bindings::*;
use std::cmp::max;
use std::env;
use std::ffi::{CStr, CString};
use std::fmt::Write;
use std::os::raw::c_void;
use std::panic;
use std::sync::OnceLock;
#[repr(u8)]
enum DebugFlags {
Panic,
Print,
Serial,
Spill,
Annotate,
NoUgpr,
Cycles,
}
pub struct Debug {
flags: u32,
}
impl Debug {
fn new() -> Debug {
let debug_var = "NAK_DEBUG";
let debug_str = match env::var(debug_var) {
Ok(s) => s,
Err(_) => {
return Debug { flags: 0 };
}
};
let mut flags = 0;
for flag in debug_str.split(',') {
match flag.trim() {
"panic" => flags |= 1 << DebugFlags::Panic as u8,
"print" => flags |= 1 << DebugFlags::Print as u8,
"serial" => flags |= 1 << DebugFlags::Serial as u8,
"spill" => flags |= 1 << DebugFlags::Spill as u8,
"annotate" => flags |= 1 << DebugFlags::Annotate as u8,
"nougpr" => flags |= 1 << DebugFlags::NoUgpr as u8,
"cycles" => flags |= 1 << DebugFlags::Cycles as u8,
unk => eprintln!("Unknown NAK_DEBUG flag \"{}\"", unk),
}
}
Debug { flags: flags }
}
}
pub trait GetDebugFlags {
fn debug_flags(&self) -> u32;
fn panic(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Panic as u8) != 0
}
fn print(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Print as u8) != 0
}
fn serial(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Serial as u8) != 0
}
fn spill(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Spill as u8) != 0
}
fn annotate(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Annotate as u8) != 0
}
fn no_ugpr(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::NoUgpr as u8) != 0
}
fn cycles(&self) -> bool {
self.debug_flags() & (1 << DebugFlags::Cycles as u8) != 0
}
}
pub static DEBUG: OnceLock<Debug> = OnceLock::new();
impl GetDebugFlags for OnceLock<Debug> {
fn debug_flags(&self) -> u32 {
self.get_or_init(Debug::new).flags
}
}
#[no_mangle]
pub extern "C" fn nak_should_print_nir() -> bool {
DEBUG.print()
}
#[no_mangle]
pub extern "C" fn nak_debug_no_ugpr() -> bool {
DEBUG.no_ugpr()
}
fn nir_options(dev: &nv_device_info) -> nir_shader_compiler_options {
nir_shader_compiler_options {
lower_fdiv: true,
float_mul_add16: nir_float_muladd_support_has_ffma
| nir_float_muladd_support_fuse,
float_mul_add32: nir_float_muladd_support_has_ffma
| nir_float_muladd_support_fuse,
float_mul_add64: nir_float_muladd_support_has_ffma
| nir_float_muladd_support_fuse,
lower_flrp16: true,
lower_flrp32: true,
lower_flrp64: true,
lower_fsqrt: dev.sm < 52,
lower_bitfield_extract: false,
lower_bitfield_extract8: true,
lower_bitfield_extract16: true,
lower_bitfield_insert: true,
lower_pack_half_2x16: true,
lower_pack_unorm_2x16: true,
lower_pack_snorm_2x16: true,
lower_pack_unorm_4x8: true,
lower_pack_snorm_4x8: true,
lower_unpack_half_2x16: true,
lower_unpack_unorm_2x16: true,
lower_unpack_snorm_2x16: true,
lower_unpack_unorm_4x8: true,
lower_unpack_snorm_4x8: true,
lower_insert_byte: true,
lower_insert_word: true,
lower_device_index_to_zero: true,
lower_isign: true,
lower_uadd_sat: dev.sm < 70,
lower_usub_sat: dev.sm < 70,
lower_iadd_sat: true, // TODO
lower_doubles_options: nir_lower_drcp
| nir_lower_dsqrt
| nir_lower_drsq
| nir_lower_dtrunc
| nir_lower_dfloor
| nir_lower_dceil
| nir_lower_dfract
| nir_lower_dround_even
| nir_lower_dsat
| if dev.sm >= 70 { nir_lower_dminmax } else { 0 },
lower_int64_options: !(nir_lower_icmp64
| nir_lower_iadd64
| nir_lower_ineg64
| nir_lower_shift64
| nir_lower_imul_2x32_64
| nir_lower_vote_ieq64
| nir_lower_conv64)
| if dev.sm < 70 { nir_lower_vote_ieq64 } else { 0 }
| if dev.sm < 32 { nir_lower_shift64 } else { 0 },
lower_fmod: true,
lower_ffract: true,
lower_fpow: true,
lower_scmp: true,
lower_uadd_carry: true,
lower_usub_borrow: true,
has_rotate32: dev.sm >= 32,
has_iadd3: dev.sm >= 70,
has_imad32: dev.sm >= 70,
has_sdot_4x8: dev.sm >= 70,
has_udot_4x8: dev.sm >= 70,
has_sudot_4x8: dev.sm >= 70,
// We set .ftz on f32 by default so we can support fmulz whenever the client
// doesn't explicitly request denorms.
has_fmulz_no_denorms: true,
has_ffmaz_no_denorms: true,
has_find_msb_rev: true,
has_pack_half_2x16_rtz: true,
has_bfm: dev.sm >= 70,
discard_is_demote: true,
has_load_global_bounded: dev.sm >= 73,
vectorize_vec2_16bit: true,
max_unroll_iterations: 32,
max_samples: 8,
scalarize_ddx: true,
..Default::default()
}
}
#[no_mangle]
pub extern "C" fn nak_compiler_create(
dev: *const nv_device_info,
) -> *mut nak_compiler {
assert!(!dev.is_null());
let dev = unsafe { &*dev };
let nak = Box::new(nak_compiler {
sm: dev.sm,
warps_per_sm: dev.max_warps_per_mp,
nir_options: nir_options(dev),
});
Box::into_raw(nak)
}
#[no_mangle]
pub extern "C" fn nak_compiler_destroy(nak: *mut nak_compiler) {
unsafe { drop(Box::from_raw(nak)) };
}
#[no_mangle]
pub extern "C" fn nak_debug_flags(_nak: *const nak_compiler) -> u64 {
DEBUG.debug_flags().into()
}
#[no_mangle]
pub extern "C" fn nak_nir_options(
nak: *const nak_compiler,
) -> *const nir_shader_compiler_options {
assert!(!nak.is_null());
let nak = unsafe { &*nak };
&nak.nir_options
}
#[repr(C)]
pub struct ShaderBin {
pub bin: nak_shader_bin,
code: Vec<u32>,
asm: CString,
}
impl ShaderBin {
pub fn new(
sm: &ShaderModelInfo,
info: &ShaderInfo,
fs_key: Option<&nak_fs_key>,
code: Vec<u32>,
asm: &str,
) -> ShaderBin {
let asm = CString::new(asm)
.expect("NAK assembly has unexpected null characters");
let c_info = nak_shader_info {
stage: match info.stage {
ShaderStageInfo::Compute(_) => MESA_SHADER_COMPUTE,
ShaderStageInfo::Vertex(_) => MESA_SHADER_VERTEX,
ShaderStageInfo::Fragment(_) => MESA_SHADER_FRAGMENT,
ShaderStageInfo::Geometry(_) => MESA_SHADER_GEOMETRY,
ShaderStageInfo::TessellationInit(_) => MESA_SHADER_TESS_CTRL,
ShaderStageInfo::Tessellation(_) => MESA_SHADER_TESS_EVAL,
ShaderStageInfo::Task(_) => MESA_SHADER_TASK,
ShaderStageInfo::Mesh(_) => MESA_SHADER_MESH,
},
sm: sm.sm(),
num_gprs: {
max(4, info.num_gprs as u32 + sm.hw_reserved_gprs())
.try_into()
.unwrap()
},
num_control_barriers: info.num_control_barriers,
_pad0: Default::default(),
_pad1: Default::default(),
max_warps_per_sm: info.max_warps_per_sm,
num_instrs: info.num_instrs,
num_static_cycles: info.num_static_cycles,
num_spills_to_mem: info.num_spills_to_mem,
num_fills_from_mem: info.num_fills_from_mem,
num_spills_to_reg: info.num_spills_to_reg,
num_fills_from_reg: info.num_fills_from_reg,
slm_size: info.slm_size,
crs_size: sm.crs_size(info.max_crs_depth),
__bindgen_anon_1: match &info.stage {
ShaderStageInfo::Compute(cs_info) => {
nak_shader_info__bindgen_ty_1 {
cs: nak_shader_info__bindgen_ty_1__bindgen_ty_1 {
local_size: [
cs_info.local_size[0],
cs_info.local_size[1],
cs_info.local_size[2],
],
smem_size: cs_info.smem_size,
_pad: [0; 132],
},
}
}
ShaderStageInfo::Fragment(fs_info) => {
let fs_io_info = match &info.io {
ShaderIoInfo::Fragment(io) => io,
_ => unreachable!(),
};
nak_shader_info__bindgen_ty_1 {
fs: nak_shader_info__bindgen_ty_1__bindgen_ty_2 {
writes_depth: fs_io_info.writes_depth,
reads_sample_mask: fs_io_info.reads_sample_mask,
post_depth_coverage: fs_info.post_depth_coverage,
uses_sample_shading: fs_info.uses_sample_shading,
early_fragment_tests: fs_info.early_fragment_tests,
_pad: [0; 135],
},
}
}
ShaderStageInfo::TessellationInit(ts_info) => {
nak_shader_info__bindgen_ty_1 {
ts: nak_shader_info__bindgen_ty_1__bindgen_ty_3 {
domain: 0,
spacing: ts_info
.common
.spacing
.map_or(0, |x| x as u8),
ccw: ts_info.common.ccw,
point_mode: ts_info.common.point_mode,
_pad: [0; 136],
},
}
}
ShaderStageInfo::Tessellation(ts_info) => {
nak_shader_info__bindgen_ty_1 {
ts: nak_shader_info__bindgen_ty_1__bindgen_ty_3 {
domain: ts_info.domain as u8,
spacing: ts_info
.common
.spacing
.map_or(0, |x| x as u8),
ccw: ts_info.common.ccw,
point_mode: ts_info.common.point_mode,
_pad: [0; 136],
},
}
}
ShaderStageInfo::Task(task_info) => {
nak_shader_info__bindgen_ty_1 {
task: nak_shader_info__bindgen_ty_1__bindgen_ty_5 {
local_size: task_info.local_size,
payload_smem_size: task_info.payload_smem_size,
smem_size: task_info.smem_size,
_pad: [0; 130],
},
}
}
ShaderStageInfo::Mesh(mesh_info) => {
nak_shader_info__bindgen_ty_1 {
mesh: nak_shader_info__bindgen_ty_1__bindgen_ty_4 {
gs_hdr: sph::encode_gs_mesh_header(
sm.sm(),
mesh_info,
),
max_primitives: mesh_info.max_primitives,
max_vertices: mesh_info.max_vertices,
local_size: mesh_info.local_size,
smem_size: mesh_info.smem_size,
topology: mesh_info.output_topology,
has_gs_sph: mesh_info.has_gs_sph,
has_task_shader: mesh_info.has_task_shader,
_pad: [0; 1],
},
}
}
_ => nak_shader_info__bindgen_ty_1 { _pad: [0; 140] },
},
vtg: match &info.io {
ShaderIoInfo::Vtg(io) => {
let writes_layer;
let writes_vprs_table_index;
if let ShaderStageInfo::Mesh(mesh) = &info.stage {
writes_layer = mesh
.primitive_io
.attr_written(NAK_ATTR_RT_ARRAY_INDEX);
writes_vprs_table_index = mesh
.primitive_io
.attr_written(NAK_ATTR_VPRS_TABLE_INDEX);
} else {
writes_layer = io.attr_written(NAK_ATTR_RT_ARRAY_INDEX);
writes_vprs_table_index =
io.attr_written(NAK_ATTR_VPRS_TABLE_INDEX);
}
nak_shader_info__bindgen_ty_2 {
writes_layer,
writes_point_size: io.attr_written(NAK_ATTR_POINT_SIZE),
writes_vprs_table_index,
clip_enable: io.clip_enable,
cull_enable: io.cull_enable,
xfb: if let Some(xfb) = &io.xfb {
**xfb
} else {
Default::default()
},
_pad: Default::default(),
}
}
_ => Default::default(),
},
hdr: sph::encode_header(sm, info, fs_key),
};
if DEBUG.print() {
let stage_name = unsafe {
let c_name = _mesa_shader_stage_to_string(c_info.stage as u32);
CStr::from_ptr(c_name).to_str().expect("Invalid UTF-8")
};
eprintln!("Stage: {}", stage_name);
eprintln!("Instruction count: {}", c_info.num_instrs);
eprintln!("Static cycle count: {}", c_info.num_static_cycles);
eprintln!("Max warps/SM: {}", c_info.max_warps_per_sm);
eprintln!("Spills to mem: {}", c_info.num_spills_to_mem);
eprintln!("Spills to reg: {}", c_info.num_spills_to_reg);
eprintln!("Fills from mem: {}", c_info.num_fills_from_mem);
eprintln!("Fills from reg: {}", c_info.num_fills_from_reg);
eprintln!("Num GPRs: {}", c_info.num_gprs);
eprintln!("SLM size: {}", c_info.slm_size);
if c_info.stage != MESA_SHADER_COMPUTE {
eprint_hex("Header", &c_info.hdr);
}
eprint_hex("Encoded shader", &code);
}
let bin = nak_shader_bin {
info: c_info,
code_size: (code.len() * 4).try_into().unwrap(),
code: code.as_ptr() as *const c_void,
asm_str: if asm.is_empty() {
std::ptr::null()
} else {
asm.as_ptr()
},
};
ShaderBin {
bin: bin,
code: code,
asm: asm,
}
}
}
impl std::ops::Deref for ShaderBin {
type Target = nak_shader_bin;
fn deref(&self) -> &nak_shader_bin {
&self.bin
}
}
#[no_mangle]
pub extern "C" fn nak_shader_bin_destroy(bin: *mut nak_shader_bin) {
unsafe {
_ = Box::from_raw(bin as *mut ShaderBin);
};
}
fn eprint_hex(label: &str, data: &[u32]) {
eprint!("{}:", label);
for i in 0..data.len() {
if (i % 8) == 0 {
eprintln!();
eprint!(" ");
}
eprint!(" {:08x}", data[i]);
}
eprintln!();
}
macro_rules! pass {
($s: expr, $pass: ident) => {
$s.$pass();
if DEBUG.print() {
eprintln!("NAK IR after {}:\n{}", stringify!($pass), $s);
}
};
}
fn nak_compile_shader_internal(
nir: *mut nir_shader,
dump_asm: bool,
nak: *const nak_compiler,
robust2_modes: nir_variable_mode,
fs_key: *const nak_fs_key,
has_task_shader: bool,
) -> *mut nak_shader_bin {
unsafe {
nak_postprocess_nir(nir, nak, robust2_modes, fs_key, has_task_shader)
};
let nak = unsafe { &*nak };
let nir = unsafe { &*nir };
let fs_key = if fs_key.is_null() {
None
} else {
Some(unsafe { &*fs_key })
};
let sm = ShaderModelInfo::new(nak.sm, nak.warps_per_sm);
let mut s = nak_shader_from_nir(nak, nir, &sm, has_task_shader);
if DEBUG.print() {
eprintln!("NAK IR:\n{}", &s);
}
pass!(s, opt_bar_prop);
pass!(s, opt_uniform_instrs);
pass!(s, opt_copy_prop);
pass!(s, opt_prmt);
pass!(s, opt_lop);
pass!(s, opt_copy_prop);
pass!(s, opt_dce);
pass!(s, opt_out);
pass!(s, legalize);
pass!(s, opt_dce);
pass!(s, opt_instr_sched_prepass);
pass!(s, assign_regs);
pass!(s, lower_par_copies);
pass!(s, lower_copy_swap);
if nak.sm >= 70 {
pass!(s, opt_jump_thread);
} else {
pass!(s, opt_crs);
}
s.remove_annotations();
pass!(s, opt_instr_sched_postpass);
pass!(s, calc_instr_deps);
s.gather_info();
let mut asm = String::new();
if dump_asm {
write!(asm, "{}", s).expect("Failed to dump assembly");
}
let code = sm.encode_shader(&s);
let bin = Box::new(ShaderBin::new(&sm, &s.info, fs_key, code, &asm));
Box::into_raw(bin) as *mut nak_shader_bin
}
#[no_mangle]
pub extern "C" fn nak_compile_shader(
nir: *mut nir_shader,
dump_asm: bool,
nak: *const nak_compiler,
robust2_modes: nir_variable_mode,
fs_key: *const nak_fs_key,
has_task_shader: bool,
) -> *mut nak_shader_bin {
let compile = || {
nak_compile_shader_internal(
nir,
dump_asm,
nak,
robust2_modes,
fs_key,
has_task_shader,
)
};
if DEBUG.panic() {
compile()
} else {
panic::catch_unwind(compile).unwrap_or(std::ptr::null_mut())
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+59
View File
@@ -0,0 +1,59 @@
// Copyright © 2025 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use rustc_hash::FxHashMap;
pub struct ConstTracker {
map: FxHashMap<SSAValue, SrcRef>,
}
/// A tracker struct for finding re-materializable constants
///
/// Anything which is an immediate, Zero, or a bound cbuf can trivially be
/// re-materialized anywhere in the shader and it's probably cheaper to do so
/// than to try and keep them around in GPRs forever. This is just a helper
/// struct for implementing this logic in compiler passes.
impl ConstTracker {
pub fn new() -> Self {
ConstTracker {
map: Default::default(),
}
}
/// Registers a copy instruction
///
/// If the source of the copy is a constant, the destination SSA value and
/// the constant value get stored as a key/value pair.
pub fn add_copy(&mut self, op: &OpCopy) {
let Some(dst) = op.dst.as_ssa() else {
return;
};
debug_assert!(dst.comps() == 1);
let dst = dst[0];
debug_assert!(op.src.is_unmodified());
let is_const = match &op.src.src_ref {
SrcRef::Zero | SrcRef::True | SrcRef::False | SrcRef::Imm32(_) => {
true
}
SrcRef::CBuf(cb) => matches!(cb.buf, CBuf::Binding(_)),
_ => false,
};
if is_const {
self.map.insert(dst, op.src.src_ref.clone());
}
}
/// Tests if the ConstTracker contains the given SSAValue
pub fn contains(&self, ssa: &SSAValue) -> bool {
self.map.contains_key(ssa)
}
/// Returns the SrcRef associated with this SSAValue, if any
pub fn get(&self, ssa: &SSAValue) -> Option<&SrcRef> {
self.map.get(ssa)
}
}
File diff suppressed because it is too large. Load diff
+624
View File
@@ -0,0 +1,624 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use compiler::bindings::*;
use nak_bindings::*;
use nv_push_rs::Push as NvPush;
use nvidia_headers::classes::cla0c0::mthd as cla0c0;
use nvidia_headers::classes::clb1c0::mthd as clb1c0;
use nvidia_headers::classes::clb1c0::MAXWELL_COMPUTE_B;
use nvidia_headers::classes::clc3c0::mthd as clc3c0;
use nvidia_headers::classes::clc3c0::VOLTA_COMPUTE_A;
use nvidia_headers::classes::clc6c0::mthd as clc6c0;
use nvidia_headers::classes::clc6c0::AMPERE_COMPUTE_A;
use nvidia_headers::classes::clcbc0::HOPPER_COMPUTE_A;
use std::io;
use std::ops::Deref;
use std::ptr;
use std::ptr::NonNull;
use std::slice;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
unsafe fn is_nvidia_device(dev: drmDevicePtr) -> bool {
unsafe {
match (*dev).bustype as u32 {
DRM_BUS_PCI => {
let pci = (*dev).deviceinfo.pci.as_ref().unwrap();
pci.vendor_id == (NVIDIA_VENDOR_ID as u16)
}
_ => false,
}
}
}
#[repr(C)]
pub struct CB0 {
pub data_addr_lo: u32,
pub data_addr_hi: u32,
pub data_stride: u32,
pub invocations: u32,
}
struct DrmDevices {
num_devices: usize,
devices: [drmDevicePtr; 16],
}
impl DrmDevices {
fn get() -> io::Result<Self> {
unsafe {
let mut devices: [drmDevicePtr; 16] = [std::ptr::null_mut(); 16];
let num_devices = drmGetDevices(
devices.as_mut_ptr(),
devices.len().try_into().unwrap(),
);
if num_devices < 0 {
return Err(io::Error::last_os_error());
}
Ok(DrmDevices {
num_devices: num_devices.try_into().unwrap(),
devices,
})
}
}
fn iter(&self) -> slice::Iter<'_, drmDevicePtr> {
self.devices[..self.num_devices].iter()
}
}
impl Deref for DrmDevices {
type Target = [drmDevicePtr];
fn deref(&self) -> &[drmDevicePtr] {
&self.devices[..self.num_devices]
}
}
impl Drop for DrmDevices {
fn drop(&mut self) {
unsafe {
drmFreeDevices(
self.devices.as_mut_ptr(),
self.num_devices.try_into().unwrap(),
);
}
}
}
pub struct Device {
dev: NonNull<nouveau_ws_device>,
next_addr: AtomicU64,
}
impl Device {
pub fn new(dev_id: Option<usize>) -> io::Result<Arc<Self>> {
let drm_devices = DrmDevices::get()?;
unsafe {
let drm_dev = if let Some(dev_id) = dev_id {
if dev_id >= drm_devices.len() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"Unknown device {dev_id}",
));
}
drm_devices[dev_id]
} else {
if let Some(dev) =
drm_devices.iter().find(|dev| is_nvidia_device(**dev))
{
*dev
} else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"Failed to find an NVIDIA device",
));
}
};
let dev = nouveau_ws_device_new(drm_dev);
let Some(dev) = NonNull::new(dev) else {
return Err(io::Error::last_os_error());
};
Ok(Arc::new(Device {
dev,
next_addr: AtomicU64::new(1 << 16),
}))
}
}
pub fn dev_info(&self) -> &nv_device_info {
unsafe { &self.dev.as_ref().info }
}
fn fd(&self) -> i32 {
unsafe { self.dev.as_ref().fd }
}
fn ws_dev(&self) -> *mut nouveau_ws_device {
self.dev.as_ptr()
}
}
impl Drop for Device {
fn drop(&mut self) {
unsafe { nouveau_ws_device_destroy(self.ws_dev()) }
}
}
pub struct Context {
dev: Arc<Device>,
ctx: NonNull<nouveau_ws_context>,
syncobj: u32,
sync_value: Mutex<u64>,
}
impl Context {
pub fn new(dev: Arc<Device>) -> io::Result<Self> {
unsafe {
let mut ctx: *mut nouveau_ws_context = std::ptr::null_mut();
let err = nouveau_ws_context_create(
dev.ws_dev(),
NOUVEAU_WS_ENGINE_COMPUTE,
&mut ctx,
);
if err != 0 {
return Err(io::Error::last_os_error());
}
let ctx = NonNull::new(ctx).unwrap();
let mut syncobj = 0_u32;
let err = drmSyncobjCreate(dev.fd(), 0, &mut syncobj);
if err != 0 {
nouveau_ws_context_destroy(ctx.as_ptr());
return Err(io::Error::last_os_error());
}
Ok(Context {
dev,
ctx,
syncobj,
sync_value: Mutex::new(0),
})
}
}
pub fn exec(&self, addr: u64, len: u16) -> io::Result<()> {
let sync_value = unsafe {
let mut sync_value = self.sync_value.lock().unwrap();
*sync_value += 1;
let push = drm_nouveau_exec_push {
va: addr,
va_len: len.into(),
flags: 0,
};
let sig = drm_nouveau_sync {
flags: DRM_NOUVEAU_SYNC_TIMELINE_SYNCOBJ,
handle: self.syncobj,
timeline_value: *sync_value,
};
let exec = drm_nouveau_exec {
channel: self.ctx.as_ref().channel as u32,
wait_count: 0,
wait_ptr: 0,
push_count: 1,
push_ptr: &push as *const _ as u64,
sig_count: 1,
sig_ptr: &sig as *const _ as u64,
};
let err = drmIoctl(
self.dev.fd(),
DRM_RS_IOCTL_NOUVEAU_EXEC.into(),
&exec as *const _ as *mut std::os::raw::c_void,
);
if err != 0 {
return Err(io::Error::last_os_error());
}
*sync_value
};
// The close of this unsafe { } drops the lock
unsafe {
let err = drmSyncobjTimelineWait(
self.dev.fd(),
&self.syncobj as *const _ as *mut _,
&sync_value as *const _ as *mut _,
1, // num_handles
i64::MAX, // timeout_nsec
DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT,
std::ptr::null_mut(),
);
if err != 0 {
return Err(io::Error::last_os_error());
}
// Exec again to check for errors
let mut exec = drm_nouveau_exec {
channel: self.ctx.as_ref().channel as u32,
wait_count: 0,
wait_ptr: 0,
push_count: 0,
push_ptr: 0,
sig_count: 0,
sig_ptr: 0,
};
let err = drmIoctl(
self.dev.fd(),
DRM_RS_IOCTL_NOUVEAU_EXEC.into(),
ptr::from_mut(&mut exec).cast(),
);
if err != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
drmSyncobjDestroy(self.dev.fd(), self.syncobj);
nouveau_ws_context_destroy(self.ctx.as_ptr());
}
}
}
pub struct BO {
dev: Arc<Device>,
bo: NonNull<nouveau_ws_bo>,
pub addr: u64,
pub map: *mut std::os::raw::c_void,
}
impl BO {
pub fn new(dev: Arc<Device>, size: u64) -> io::Result<BO> {
let size = size.next_multiple_of(4096);
let mut map: *mut std::os::raw::c_void = std::ptr::null_mut();
let bo = unsafe {
nouveau_ws_bo_new_mapped(
dev.ws_dev(),
size,
0, // align
NOUVEAU_WS_BO_GART,
NOUVEAU_WS_BO_RDWR,
ptr::from_mut(&mut map),
)
};
let Some(bo) = NonNull::new(bo) else {
return Err(io::Error::last_os_error());
};
assert!(!map.is_null());
let addr = dev.next_addr.fetch_add(size, Ordering::Relaxed);
assert!(addr % 4096 == 0);
unsafe {
nouveau_ws_bo_bind_vma(
dev.ws_dev(),
bo.as_ptr(),
addr,
size,
0, // bo_offset
0, // pte_kind
);
}
Ok(BO { dev, bo, addr, map })
}
}
impl Drop for BO {
fn drop(&mut self) {
unsafe {
nouveau_ws_bo_unbind_vma(
self.dev.dev.as_ptr(),
self.addr,
self.bo.as_ref().size,
);
nouveau_ws_bo_destroy(self.bo.as_ptr());
}
}
}
struct QMDHeapImpl {
dev: Arc<Device>,
bos: Vec<BO>,
last_offset: u32,
free: Vec<(u64, *mut std::os::raw::c_void)>,
}
struct QMDHeap {
pub qmd_size_B: u32,
imp: Mutex<QMDHeapImpl>,
}
struct QMD<'a> {
heap: &'a QMDHeap,
pub addr: u64,
pub map: *mut std::os::raw::c_void,
}
impl Drop for QMD<'_> {
fn drop(&mut self) {
let mut heap = self.heap.imp.lock().unwrap();
heap.free.push((self.addr, self.map));
}
}
impl QMDHeap {
const BO_SIZE: u32 = 1 << 16;
fn new(dev: Arc<Device>) -> Self {
let qmd_size_B = unsafe { nak_qmd_size_B(dev.dev_info()) };
Self {
qmd_size_B,
imp: Mutex::new(QMDHeapImpl {
dev,
bos: Vec::new(),
last_offset: Self::BO_SIZE,
free: Vec::new(),
}),
}
}
fn alloc_qmd<'a>(&'a self) -> io::Result<QMD<'a>> {
let mut imp = self.imp.lock().unwrap();
if let Some((addr, map)) = imp.free.pop() {
return Ok(QMD {
heap: self,
addr,
map,
});
}
if imp.last_offset >= Self::BO_SIZE {
let dev = imp.dev.clone();
imp.bos.push(BO::new(dev, Self::BO_SIZE.into())?);
imp.last_offset = 0;
}
let bo = imp.bos.last().unwrap();
let addr = bo.addr + u64::from(imp.last_offset);
let map =
unsafe { bo.map.byte_offset(imp.last_offset.try_into().unwrap()) };
imp.last_offset += self.qmd_size_B.next_multiple_of(NAK_QMD_ALIGN_B);
Ok(QMD {
heap: self,
addr,
map,
})
}
}
pub struct Runner {
dev: Arc<Device>,
ctx: Context,
qmd_heap: QMDHeap,
}
impl Runner {
pub fn new(dev_id: Option<usize>) -> Runner {
let dev = Device::new(dev_id).expect("Failed to create nouveau device");
let ctx = Context::new(dev.clone()).expect("Failed to create context");
let qmd_heap = QMDHeap::new(dev.clone());
Runner { dev, ctx, qmd_heap }
}
pub fn device(&self) -> Arc<Device> {
self.dev.clone()
}
pub fn dev_info(&self) -> &nv_device_info {
self.dev.dev_info()
}
pub unsafe fn run_raw(
&self,
shader: &nak_shader_bin,
invocations: u32,
data_stride: u32,
data: *mut std::os::raw::c_void,
data_size: usize,
) -> io::Result<()> {
let cs_info = unsafe {
assert!(shader.info.stage == MESA_SHADER_COMPUTE);
&shader.info.__bindgen_anon_1.cs
};
assert!(cs_info.local_size[1] == 1 && cs_info.local_size[2] == 1);
let local_size = cs_info.local_size[0];
// Compute the needed size of the buffer
let mut size = 0_usize;
const MAX_PUSH_DW: usize = 256;
let push_offset = size;
size = push_offset + 4 * MAX_PUSH_DW;
let shader_offset = size.next_multiple_of(0x80);
size = shader_offset + usize::try_from(shader.code_size).unwrap();
let cb0_offset = size.next_multiple_of(256);
size = cb0_offset + size_of::<CB0>();
let data_offset = size.next_multiple_of(256);
size = data_offset + data_size;
let bo = BO::new(self.dev.clone(), size.try_into().unwrap())?;
// Copy the data from the caller into our BO
let data_addr = bo.addr + u64::try_from(data_offset).unwrap();
unsafe {
let data_map = bo.map.byte_offset(data_offset.try_into().unwrap());
if data_size > 0 {
std::ptr::copy(data, data_map, data_size);
}
}
// Fill out cb0
let cb0_addr = bo.addr + u64::try_from(cb0_offset).unwrap();
unsafe {
let cb0_map = bo.map.byte_offset(cb0_offset.try_into().unwrap());
cb0_map.cast::<CB0>().write(CB0 {
data_addr_lo: data_addr as u32,
data_addr_hi: (data_addr >> 32) as u32,
data_stride,
invocations,
});
}
// Upload the shader
let shader_addr = bo.addr + u64::try_from(shader_offset).unwrap();
unsafe {
let shader_map =
bo.map.byte_offset(shader_offset.try_into().unwrap());
std::ptr::copy(
shader.code,
shader_map,
shader.code_size.try_into().unwrap(),
);
}
// Populate and upload the QMD
let mut qmd_cbufs: [nak_qmd_cbuf; 8] = Default::default();
qmd_cbufs[0] = nak_qmd_cbuf {
index: 0,
size: size_of::<CB0>().next_multiple_of(256).try_into().unwrap(),
addr: cb0_addr,
};
let qmd_info = nak_qmd_info {
// Pre-Volta, we set the program region to the start of the bo
addr: if self.dev_info().cls_compute < VOLTA_COMPUTE_A {
shader_offset.try_into().unwrap()
} else {
shader_addr
},
smem_size: unsafe { shader.info.__bindgen_anon_1.cs }
.smem_size
.into(),
global_size: [invocations.div_ceil(local_size.into()), 1, 1],
num_cbufs: 1,
cbufs: qmd_cbufs,
};
let qmd = self.qmd_heap.alloc_qmd()?;
unsafe {
nak_fill_qmd(
self.dev_info(),
&shader.info,
&qmd_info,
qmd.map,
self.qmd_heap.qmd_size_B.try_into().unwrap(),
);
}
// Fill out the pushbuf
let mut p = NvPush::new();
p.push_mthd(cla0c0::SetObject {
class_id: self.dev_info().cls_compute.into(),
engine_id: 0,
});
if self.dev_info().cls_compute < VOLTA_COMPUTE_A {
p.push_mthd(cla0c0::SetProgramRegionA {
address_upper: (bo.addr >> 32) as u32,
});
p.push_mthd(cla0c0::SetProgramRegionB {
address_lower: bo.addr as u32,
});
}
let mut smem_base_addr = 0xfe000000_u64;
if self.dev_info().cls_compute >= HOPPER_COMPUTE_A {
// This needs to be 4GB aligned on Hopper+
smem_base_addr <<= 8;
}
let lmem_base_addr = 0xff000000_u32;
if self.dev_info().cls_compute >= VOLTA_COMPUTE_A {
p.push_mthd(clc3c0::SetShaderSharedMemoryWindowA {
base_address_upper: (smem_base_addr >> 32) as u32,
});
p.push_mthd(clc3c0::SetShaderSharedMemoryWindowB {
base_address: smem_base_addr as u32,
});
p.push_mthd(clc3c0::SetShaderLocalMemoryWindowA {
base_address_upper: 0,
});
p.push_mthd(clc3c0::SetShaderLocalMemoryWindowB {
base_address: lmem_base_addr,
});
} else {
p.push_mthd(cla0c0::SetShaderSharedMemoryWindow {
base_address: smem_base_addr as u32,
});
p.push_mthd(cla0c0::SetShaderLocalMemoryWindow {
base_address: lmem_base_addr,
});
}
if self.dev_info().cls_compute >= MAXWELL_COMPUTE_B {
p.push_mthd(clb1c0::InvalidateSkedCaches { v: 0 });
}
p.push_mthd(cla0c0::SendPcasA {
qmd_address_shifted8: (qmd.addr >> 8) as u32,
});
if self.dev_info().cls_compute >= AMPERE_COMPUTE_A {
p.push_mthd(clc6c0::SendSignalingPcas2B {
pcas_action: clc6c0::SendSignalingPcas2BPcasAction::InvalidateCopySchedule,
});
} else {
p.push_mthd(cla0c0::SendSignalingPcasB {
invalidate: true,
schedule: true,
});
}
let push_addr = bo.addr + u64::try_from(push_offset).unwrap();
unsafe {
let push_map = bo.map.byte_offset(push_offset.try_into().unwrap());
std::ptr::copy(p.as_ptr(), push_map.cast(), p.len());
}
let res = self.ctx.exec(push_addr, (p.len() * 4).try_into().unwrap());
// Always copy the data back to the caller, even if exec fails
unsafe {
let data_map = bo.map.byte_offset(data_offset.try_into().unwrap());
if data_size > 0 {
std::ptr::copy(data_map, data, data_size);
}
}
res
}
pub fn run<T>(
&self,
shader: &nak_shader_bin,
data: &mut [T],
) -> io::Result<()> {
unsafe {
let stride = size_of::<T>();
self.run_raw(
shader,
data.len().try_into().unwrap(),
stride.try_into().unwrap(),
data.as_mut_ptr().cast(),
size_of_val(data),
)
}
}
}
unsafe impl Sync for Runner {}
unsafe impl Send for Runner {}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+156
View File
@@ -0,0 +1,156 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
extern crate syn;
use compiler_proc::as_slice::*;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::*;
#[proc_macro_derive(SrcsAsSlice, attributes(src_type))]
pub fn derive_srcs_as_slice(input: TokenStream) -> TokenStream {
let input2 = input.clone();
let DeriveInput { ident, data, .. } = parse_macro_input!(input2);
if let Data::Enum(e) = data {
let mut as_slice_cases = TokenStream2::new();
let mut as_mut_slice_cases = TokenStream2::new();
let mut types_cases = TokenStream2::new();
for v in e.variants {
let case = v.ident;
as_slice_cases.extend(quote! {
#ident::#case(x) => x.srcs_as_slice(),
});
as_mut_slice_cases.extend(quote! {
#ident::#case(x) => x.srcs_as_mut_slice(),
});
types_cases.extend(quote! {
#ident::#case(x) => x.src_types(),
});
}
quote! {
impl SrcsAsSlice for #ident {
fn srcs_as_slice(&self) -> &[Src] {
match self {
#as_slice_cases
}
}
fn srcs_as_mut_slice(&mut self) -> &mut [Src] {
match self {
#as_mut_slice_cases
}
}
fn src_types(&self) -> SrcTypeList {
match self {
#types_cases
}
}
}
}
.into()
} else {
derive_as_slice(input, "Src", "src_type", "SrcType")
}
}
#[proc_macro_derive(DstsAsSlice, attributes(dst_type))]
pub fn derive_dsts_as_slice(input: TokenStream) -> TokenStream {
let input2 = input.clone();
let DeriveInput { ident, data, .. } = parse_macro_input!(input2);
if let Data::Enum(e) = data {
let mut as_slice_cases = TokenStream2::new();
let mut as_mut_slice_cases = TokenStream2::new();
let mut types_cases = TokenStream2::new();
for v in e.variants {
let case = v.ident;
as_slice_cases.extend(quote! {
#ident::#case(x) => x.dsts_as_slice(),
});
as_mut_slice_cases.extend(quote! {
#ident::#case(x) => x.dsts_as_mut_slice(),
});
types_cases.extend(quote! {
#ident::#case(x) => x.dst_types(),
});
}
quote! {
impl DstsAsSlice for #ident {
fn dsts_as_slice(&self) -> &[Dst] {
match self {
#as_slice_cases
}
}
fn dsts_as_mut_slice(&mut self) -> &mut [Dst] {
match self {
#as_mut_slice_cases
}
}
fn dst_types(&self) -> DstTypeList {
match self {
#types_cases
}
}
}
}
.into()
} else {
derive_as_slice(input, "Dst", "dst_type", "DstType")
}
}
#[proc_macro_derive(DisplayOp)]
pub fn enum_derive_display_op(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
if let Data::Enum(e) = data {
let mut fmt_dsts_cases = TokenStream2::new();
let mut fmt_op_cases = TokenStream2::new();
for v in e.variants {
let case = v.ident;
fmt_dsts_cases.extend(quote! {
#ident::#case(x) => x.fmt_dsts(f),
});
fmt_op_cases.extend(quote! {
#ident::#case(x) => x.fmt_op(f),
});
}
quote! {
impl DisplayOp for #ident {
fn fmt_dsts(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#fmt_dsts_cases
}
}
fn fmt_op(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#fmt_op_cases
}
}
}
}
.into()
} else {
panic!("Not an enum type");
}
}
#[proc_macro_derive(FromVariants)]
pub fn derive_from_variants(input: TokenStream) -> TokenStream {
compiler_proc::from_variants::derive_from_variants(input)
}
#[proc_macro_derive(EnumAsU8)]
pub fn derive_enum_as_u8(input: TokenStream) -> TokenStream {
compiler_proc::enum_as_u8::derive_enum_as_u8(input)
}
+620
View File
@@ -0,0 +1,620 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::api::{GetDebugFlags, DEBUG};
use crate::const_tracker::ConstTracker;
use crate::ir::*;
use crate::liveness::{BlockLiveness, Liveness, SimpleLiveness};
use rustc_hash::{FxHashMap, FxHashSet};
pub fn src_is_upred_reg(src: &Src) -> bool {
match &src.src_ref {
SrcRef::True | SrcRef::False => false,
SrcRef::SSA(ssa) => {
assert!(ssa.comps() == 1);
match ssa[0].file() {
RegFile::Pred => false,
RegFile::UPred => true,
_ => panic!("Not a predicate source"),
}
}
SrcRef::Reg(_) => panic!("Not in SSA form"),
_ => panic!("Not a predicate source"),
}
}
pub fn src_is_reg(src: &Src, reg_file: RegFile) -> bool {
match &src.src_ref {
SrcRef::Zero | SrcRef::True | SrcRef::False => true,
SrcRef::SSA(ssa) => ssa.file() == reg_file,
SrcRef::Imm32(_) | SrcRef::CBuf(_) => false,
SrcRef::Reg(_) => panic!("Not in SSA form"),
}
}
pub fn swap_srcs_if_not_reg(
x: &mut Src,
y: &mut Src,
reg_file: RegFile,
) -> bool {
if !src_is_reg(x, reg_file) && src_is_reg(y, reg_file) {
std::mem::swap(x, y);
true
} else {
false
}
}
fn src_is_imm(src: &Src) -> bool {
matches!(src.src_ref, SrcRef::Imm32(_))
}
pub enum PadValue {
Zero,
#[allow(dead_code)]
Undefined,
}
pub trait LegalizeBuildHelpers: SSABuilder {
fn copy_ssa(&mut self, ssa: &mut SSAValue, reg_file: RegFile) {
let tmp = self.alloc_ssa(reg_file);
self.copy_to(tmp.into(), (*ssa).into());
*ssa = tmp;
}
fn copy_ssa_ref(&mut self, vec: &mut SSARef, reg_file: RegFile) {
for ssa in &mut vec[..] {
self.copy_ssa(ssa, reg_file);
}
}
fn copy_pred_ssa_if_uniform(&mut self, ssa: &mut SSAValue) {
match ssa.file() {
RegFile::Pred => (),
RegFile::UPred => self.copy_ssa(ssa, RegFile::Pred),
_ => panic!("Not a predicate value"),
}
}
fn copy_pred_if_upred(&mut self, pred: &mut Pred) {
match &mut pred.pred_ref {
PredRef::None => (),
PredRef::SSA(ssa) => {
self.copy_pred_ssa_if_uniform(ssa);
}
PredRef::Reg(_) => panic!("Not in SSA form"),
}
}
fn copy_src_if_upred(&mut self, src: &mut Src) {
match &mut src.src_ref {
SrcRef::True | SrcRef::False => (),
SrcRef::SSA(ssa) => {
assert!(ssa.comps() == 1);
self.copy_pred_ssa_if_uniform(&mut ssa[0]);
}
SrcRef::Reg(_) => panic!("Not in SSA form"),
_ => panic!("Not a predicate source"),
}
}
fn copy_src_if_not_same_file(&mut self, src: &mut Src) {
let SrcRef::SSA(vec) = &mut src.src_ref else {
return;
};
if vec.comps() == 1 {
return;
}
let mut all_same = true;
let file = vec[0].file();
for i in 1..vec.comps() {
let c_file = vec[usize::from(i)].file();
if c_file != file {
debug_assert!(c_file.to_warp() == file.to_warp());
all_same = false;
}
}
if !all_same {
self.copy_ssa_ref(vec, file.to_warp());
}
}
fn align_reg(
&mut self,
src: &mut Src,
n_comps: usize,
pad_value: PadValue,
) {
debug_assert!(!matches!(src.src_ref, SrcRef::Reg(_)));
let SrcRef::SSA(ref old_val) = src.src_ref else {
return;
};
assert!(old_val.len() <= n_comps);
assert!(src.is_unmodified());
let pad_fn = || {
Some(match pad_value {
PadValue::Zero => self.copy(0.into()),
PadValue::Undefined => self.undef(),
})
};
// Pad the given ssa_ref with either undefined or zero
let ssa_vals: Vec<_> = old_val
.iter()
.copied()
.chain(std::iter::from_fn(pad_fn))
.take(n_comps)
.collect();
// Collect it in a new ssa_ref and replace it with the original.
let val = SSARef::try_from(ssa_vals).expect("Cannot create SSARef");
src.src_ref = val.into();
}
fn copy_alu_src(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
let val = match src_type {
SrcType::GPR
| SrcType::ALU
| SrcType::F32
| SrcType::F16
| SrcType::F16v2
| SrcType::I32
| SrcType::B32 => self.alloc_ssa_vec(reg_file, 1),
SrcType::F64 => self.alloc_ssa_vec(reg_file, 2),
SrcType::Pred => self.alloc_ssa_vec(reg_file, 1),
_ => panic!("Unknown source type"),
};
if DEBUG.annotate() {
self.push_instr(Instr::new(OpAnnotate {
annotation: "copy generated by legalizer".into(),
}));
}
let old_src_ref =
std::mem::replace(&mut src.src_ref, val.clone().into());
if val.comps() == 1 {
self.copy_to(val[0].into(), old_src_ref.into());
} else {
match old_src_ref {
SrcRef::Imm32(u) => {
// Immediates go in the top bits
self.copy_to(val[0].into(), 0.into());
self.copy_to(val[1].into(), u.into());
}
SrcRef::CBuf(cb) => {
// CBufs load 8B
self.copy_to(val[0].into(), cb.clone().into());
self.copy_to(val[1].into(), cb.offset(4).into());
}
SrcRef::SSA(vec) => {
assert!(vec.comps() == 2);
self.copy_to(val[0].into(), vec[0].into());
self.copy_to(val[1].into(), vec[1].into());
}
_ => panic!("Invalid 64-bit SrcRef"),
}
}
}
fn copy_alu_src_if_not_reg(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if !src_is_reg(src, reg_file) {
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_alu_src_if_not_reg_or_imm(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if !src_is_reg(src, reg_file)
&& !matches!(&src.src_ref, SrcRef::Imm32(_))
{
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_alu_src_if_imm(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if src_is_imm(src) {
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_alu_src_if_ineg_imm(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
assert!(src_type == SrcType::I32);
if src_is_imm(src) && src.src_mod.is_ineg() {
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_alu_src_if_both_not_reg(
&mut self,
src1: &Src,
src2: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if !src_is_reg(src1, reg_file) && !src_is_reg(src2, reg_file) {
self.copy_alu_src(src2, reg_file, src_type);
}
}
fn copy_alu_src_and_lower_fmod(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
match src_type {
SrcType::F16 | SrcType::F16v2 => {
let val = self.alloc_ssa(reg_file);
let old_src = std::mem::replace(src, val.into());
self.push_op(OpHAdd2 {
dst: val.into(),
srcs: [Src::ZERO.fneg(), old_src],
saturate: false,
ftz: false,
f32: false,
});
}
SrcType::F32 => {
let val = self.alloc_ssa(reg_file);
let old_src = std::mem::replace(src, val.into());
self.push_op(OpFAdd {
dst: val.into(),
srcs: [Src::ZERO.fneg(), old_src],
saturate: false,
rnd_mode: FRndMode::NearestEven,
ftz: false,
});
}
SrcType::F64 => {
let val = self.alloc_ssa_vec(reg_file, 2);
let old_src = std::mem::replace(src, val.clone().into());
self.push_op(OpDAdd {
dst: val.into(),
srcs: [Src::ZERO.fneg(), old_src],
rnd_mode: FRndMode::NearestEven,
});
}
_ => panic!("Invalid ffabs srouce type"),
}
}
fn copy_alu_src_and_lower_ineg(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
assert!(src_type == SrcType::I32);
let val = self.alloc_ssa(reg_file);
let old_src = std::mem::replace(src, val.into());
if self.sm() >= 70 {
self.push_op(OpIAdd3 {
srcs: [Src::ZERO, old_src, Src::ZERO],
overflow: [Dst::None, Dst::None],
dst: val.into(),
});
} else {
self.push_op(OpIAdd2 {
dst: val.into(),
carry_out: Dst::None,
srcs: [Src::ZERO, old_src],
});
}
}
fn copy_alu_src_if_fabs(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if src.src_mod.has_fabs() {
self.copy_alu_src_and_lower_fmod(src, reg_file, src_type);
}
}
fn copy_alu_src_if_i20_overflow(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if src.as_imm_not_i20().is_some() {
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_alu_src_if_f20_overflow(
&mut self,
src: &mut Src,
reg_file: RegFile,
src_type: SrcType,
) {
if src.as_imm_not_f20().is_some() {
self.copy_alu_src(src, reg_file, src_type);
}
}
fn copy_ssa_ref_if_uniform(&mut self, ssa_ref: &mut SSARef) {
for ssa in &mut ssa_ref[..] {
if ssa.is_uniform() {
let warp = self.alloc_ssa(ssa.file().to_warp());
self.copy_to(warp.into(), (*ssa).into());
*ssa = warp;
}
}
}
fn copy_src_if_uniform(&mut self, src: &mut Src) {
match &mut src.src_ref {
SrcRef::SSA(ssa) => self.copy_ssa_ref_if_uniform(ssa),
SrcRef::Imm32(_) | SrcRef::False | SrcRef::True | SrcRef::Zero => {}
_ => panic!("Unsupported source reference"),
}
}
}
pub struct LegalizeBuilder<'a> {
b: SSAInstrBuilder<'a>,
const_tracker: &'a mut ConstTracker,
}
impl<'a> LegalizeBuilder<'a> {
fn new(
sm: &'a ShaderModelInfo,
alloc: &'a mut SSAValueAllocator,
const_tracker: &'a mut ConstTracker,
) -> Self {
LegalizeBuilder {
b: SSAInstrBuilder::new(sm, alloc),
const_tracker,
}
}
pub fn into_vec(self) -> Vec<Instr> {
self.b.into_vec()
}
#[allow(dead_code)]
pub fn into_mapped_instrs(self) -> MappedInstrs {
self.b.into_mapped_instrs()
}
}
impl<'a> Builder for LegalizeBuilder<'a> {
fn push_instr(&mut self, instr: Instr) -> &mut Instr {
self.b.push_instr(instr)
}
fn sm(&self) -> u8 {
self.b.sm()
}
fn copy_to(&mut self, dst: Dst, mut src: Src) {
if let Some(ssa_ref) = src.as_ssa() {
if let &[ssa_value] = &ssa_ref[..] {
if let Some(new_src) = self.const_tracker.get(&ssa_value) {
src = new_src.clone().into();
}
}
};
self.b.copy_to(dst, src);
}
}
impl<'a> SSABuilder for LegalizeBuilder<'a> {
fn alloc_ssa(&mut self, file: RegFile) -> SSAValue {
self.b.alloc_ssa(file)
}
fn alloc_ssa_vec(&mut self, file: RegFile, comps: u8) -> SSARef {
self.b.alloc_ssa_vec(file, comps)
}
}
impl LegalizeBuildHelpers for LegalizeBuilder<'_> {}
fn legalize_instr(
sm: &ShaderModelInfo,
b: &mut LegalizeBuilder,
bl: &impl BlockLiveness,
block_uniform: bool,
pinned: &FxHashSet<SSARef>,
ip: usize,
instr: &mut Instr,
) {
// Handle a few no-op cases up-front
match &instr.op {
Op::Annotate(_) => {
// OpAnnotate does nothing. There's nothing to legalize.
return;
}
Op::Undef(_)
| Op::PhiSrcs(_)
| Op::PhiDsts(_)
| Op::Pin(_)
| Op::Unpin(_)
| Op::RegOut(_) => {
// These are implemented by RA and can take pretty much anything
// you can throw at them.
debug_assert!(instr.pred.is_true());
return;
}
Op::Copy(_) => {
// OpCopy is implemented in a lowering pass and can handle anything
return;
}
Op::SrcBar(_) => {
// This is turned into a nop by calc_instr_deps
return;
}
Op::Swap(_) | Op::ParCopy(_) => {
// These are generated by RA and should not exist yet
panic!("Unsupported instruction");
}
_ => (),
}
if !instr.is_uniform() {
b.copy_pred_if_upred(&mut instr.pred);
}
let src_types = instr.src_types();
for (i, src) in instr.srcs_mut().iter_mut().enumerate() {
if matches!(src.src_ref, SrcRef::Imm32(_)) {
// Fold modifiers on Imm32 sources whenever possible. Not all
// instructions suppport modifiers and immediates at the same time.
// But leave Zero sources alone as we don't want to make things
// immediates that could just be rZ.
if let Some(u) = src.as_u32(src_types[i]) {
*src = u.into();
}
}
b.copy_src_if_not_same_file(src);
if !block_uniform {
// In non-uniform control-flow, we can't collect uniform vectors so
// we need to insert copies to warp regs which we can collect.
match &mut src.src_ref {
SrcRef::SSA(vec) => {
if vec.is_uniform()
&& vec.comps() > 1
&& !pinned.contains(vec)
{
b.copy_ssa_ref(vec, vec.file().to_warp());
}
}
SrcRef::CBuf(CBufRef {
buf: CBuf::BindlessSSA(handle),
..
}) => assert!(pinned.contains(&SSARef::new(handle))),
_ => (),
}
}
}
// OpBreak and OpBSsy impose additional RA constraints
let mut legalize_break_bssy = |bar_in: &mut Src, bar_out: &mut Dst| {
let bar_in_ssa = bar_in.src_ref.as_ssa().unwrap();
if !bar_out.is_none() && bl.is_live_after_ip(&bar_in_ssa[0], ip) {
let gpr = b.bmov_to_gpr(bar_in.clone());
let tmp = b.bmov_to_bar(gpr.into());
*bar_in = tmp.into();
}
};
match &mut instr.op {
Op::Break(op) => legalize_break_bssy(&mut op.bar_in, &mut op.bar_out),
Op::BSSy(op) => legalize_break_bssy(&mut op.bar_in, &mut op.bar_out),
_ => (),
}
sm.legalize_op(b, &mut instr.op);
let mut vec_src_map: FxHashMap<SSARef, SSARef> = Default::default();
let mut vec_comps: FxHashSet<_> = Default::default();
for src in instr.srcs_mut() {
if let SrcRef::SSA(vec) = &src.src_ref {
if vec.comps() == 1 {
continue;
}
// If the same vector shows up twice in one instruction, that's
// okay. Just make it look the same as the previous source we
// fixed up.
if let Some(new_vec) = vec_src_map.get(vec) {
src.src_ref = new_vec.clone().into();
continue;
}
let mut new_vec = vec.clone();
for c in 0..vec.comps() {
let ssa = vec[usize::from(c)];
// If the same SSA value shows up in multiple non-identical
// vector sources or as multiple components in the same
// source, we need to make a copy so it can get assigned to
// multiple different registers.
if vec_comps.contains(&ssa) {
let copy = b.alloc_ssa(ssa.file());
b.copy_to(copy.into(), ssa.into());
new_vec[usize::from(c)] = copy;
} else {
vec_comps.insert(ssa);
}
}
vec_src_map.insert(vec.clone(), new_vec.clone());
src.src_ref = new_vec.into();
}
}
}
impl Shader<'_> {
pub fn legalize(&mut self) {
let sm = self.sm;
for f in &mut self.functions {
let live = SimpleLiveness::for_function(f);
let mut pinned: FxHashSet<_> = Default::default();
let mut const_tracker = ConstTracker::new();
for (bi, b) in f.blocks.iter_mut().enumerate() {
let bl = live.block_live(bi);
let bu = b.uniform;
let mut instrs = Vec::new();
for (ip, mut instr) in b.instrs.drain(..).enumerate() {
match &instr.op {
Op::Pin(pin) => {
if let Dst::SSA(ssa) = &pin.dst {
pinned.insert(ssa.clone());
}
}
Op::Copy(copy) => {
const_tracker.add_copy(copy);
}
_ => (),
}
let mut b = LegalizeBuilder::new(
sm,
&mut f.ssa_alloc,
&mut const_tracker,
);
legalize_instr(sm, &mut b, bl, bu, &pinned, ip, &mut instr);
b.push_instr(instr);
instrs.append(&mut b.into_vec());
}
b.instrs = instrs;
}
}
}
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
mod api;
mod assign_regs;
mod builder;
mod calc_instr_deps;
mod const_tracker;
mod from_nir;
mod ir;
mod legalize;
mod liveness;
mod lower_copy_swap;
mod lower_par_copies;
mod opt_bar_prop;
mod opt_copy_prop;
mod opt_crs;
mod opt_dce;
mod opt_instr_sched_common;
mod opt_instr_sched_postpass;
mod opt_instr_sched_prepass;
mod opt_jump_thread;
mod opt_lop;
mod opt_out;
mod opt_prmt;
mod opt_uniform_instrs;
mod qmd;
mod reg_tracker;
mod repair_ssa;
mod sm120_instr_latencies;
mod sm20;
mod sm30_instr_latencies;
mod sm32;
mod sm50;
mod sm70;
mod sm70_encode;
mod sm75_instr_latencies;
mod sm80_instr_latencies;
mod sph;
mod spill_values;
mod ssa_value;
mod to_cssa;
mod union_find;
#[cfg(test)]
mod hw_tests;
#[cfg(test)]
mod hw_runner;
#[cfg(test)]
mod nvdisasm_tests;
+606
View File
@@ -0,0 +1,606 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use compiler::bitset::BitSet;
use compiler::dataflow::BackwardDataflow;
use rustc_hash::{FxHashMap, FxHashSet};
use std::cmp::{max, min, Ord, Ordering};
#[derive(Clone, Default)]
pub struct LiveSet {
live: PerRegFile<u32>,
set: FxHashSet<SSAValue>,
}
impl LiveSet {
pub fn new() -> LiveSet {
Default::default()
}
pub fn clear(&mut self) {
self.live = Default::default();
self.set.clear();
}
pub fn contains(&self, ssa: &SSAValue) -> bool {
self.set.contains(ssa)
}
pub fn count(&self, file: RegFile) -> u32 {
self.live[file]
}
pub fn insert(&mut self, ssa: SSAValue) -> bool {
if self.set.insert(ssa) {
self.live[ssa.file()] += 1;
true
} else {
false
}
}
pub fn iter(&self) -> impl Iterator<Item = &SSAValue> {
self.set.iter()
}
pub fn remove(&mut self, ssa: &SSAValue) -> bool {
if self.set.remove(ssa) {
self.live[ssa.file()] -= 1;
true
} else {
false
}
}
pub fn insert_instr_top_down<L: BlockLiveness>(
&mut self,
ip: usize,
instr: &Instr,
bl: &L,
) -> PerRegFile<u32> {
// Vector destinations go live before sources are killed. Even
// in the case where the destination is immediately killed, it
// still may contribute to pressure temporarily.
for dst in instr.dsts() {
if let Dst::SSA(vec) = dst {
if vec.comps() > 1 {
for ssa in vec.iter() {
self.insert(*ssa);
}
}
}
}
let after_dsts_live = self.live;
instr.for_each_ssa_use(|ssa| {
if !bl.is_live_after_ip(ssa, ip) {
self.remove(ssa);
}
});
// Scalar destinations are allocated last
for dst in instr.dsts() {
if let Dst::SSA(vec) = dst {
if vec.comps() == 1 {
self.insert(vec[0]);
}
}
}
let max_live = PerRegFile::new_with(|file| {
max(self.live[file], after_dsts_live[file])
});
// It's possible (but unlikely) that a destination is immediately
// killed. Remove any which are killed by this instruction.
instr.for_each_ssa_def(|ssa| {
debug_assert!(self.contains(ssa));
if !bl.is_live_after_ip(ssa, ip) {
self.remove(ssa);
}
});
max_live
}
}
impl FromIterator<SSAValue> for LiveSet {
fn from_iter<T: IntoIterator<Item = SSAValue>>(iter: T) -> Self {
let mut set = LiveSet::new();
let iter = iter.into_iter();
set.set.reserve(iter.size_hint().0);
for ssa in iter {
set.insert(ssa);
}
set
}
}
impl Extend<SSAValue> for LiveSet {
fn extend<T: IntoIterator<Item = SSAValue>>(&mut self, iter: T) {
let iter = iter.into_iter();
self.set.reserve(iter.size_hint().0);
for ssa in iter {
self.insert(ssa);
}
}
}
pub trait BlockLiveness {
/// Returns true if @val is still live after @ip
fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool;
/// Returns true if @val is live-in to this block
fn is_live_in(&self, val: &SSAValue) -> bool;
/// Returns true if @val is live-out of this block
fn is_live_out(&self, val: &SSAValue) -> bool;
fn get_instr_pressure(&self, ip: usize, instr: &Instr) -> PerRegFile<u8> {
let mut live = PerRegFile::new_with(|_| 0_i8);
// Vector destinations go live before sources are killed.
for dst in instr.dsts() {
if let Dst::SSA(vec) = dst {
if vec.comps() > 1 {
for ssa in vec.iter() {
live[ssa.file()] += 1;
}
}
}
}
// This is the first high point
let vec_dst_live = live;
// Use a hash set because sources may occur more than once
let mut killed: FxHashSet<_> = Default::default();
instr.for_each_ssa_use(|ssa| {
if !self.is_live_after_ip(ssa, ip) {
killed.insert(*ssa);
}
});
for ssa in killed.drain() {
live[ssa.file()] -= 1;
}
// Scalar destinations are allocated last
for dst in instr.dsts() {
if let Dst::SSA(vec) = dst {
if vec.comps() == 1 {
live[vec[0].file()] += 1;
}
}
}
PerRegFile::new_with(|file| {
max(0, max(vec_dst_live[file], live[file]))
.try_into()
.unwrap()
})
}
}
pub trait Liveness {
type PerBlock: BlockLiveness;
fn block_live(&self, idx: usize) -> &Self::PerBlock;
fn calc_max_live(&self, f: &Function) -> PerRegFile<u32> {
let mut max_live: PerRegFile<u32> = Default::default();
let mut block_live_out: Vec<LiveSet> = Vec::new();
for (bb_idx, bb) in f.blocks.iter().enumerate() {
let bl = self.block_live(bb_idx);
let mut live = LiveSet::new();
// Predecessors are added block order so we can just grab the first
// one (if any) and it will be a block we've processed.
if let Some(pred_idx) = f.blocks.pred_indices(bb_idx).first() {
let pred_out = &block_live_out[*pred_idx];
for ssa in pred_out.iter() {
if bl.is_live_in(ssa) {
live.insert(*ssa);
}
}
}
for (ip, instr) in bb.instrs.iter().enumerate() {
let live_at_instr = live.insert_instr_top_down(ip, instr, bl);
max_live = PerRegFile::new_with(|file| {
max(max_live[file], live_at_instr[file])
});
if let Op::RegOut(reg_out) = &instr.op {
// This should be the last instruction. Everything should
// be dead once we've processed it.
debug_assert!(live.count(RegFile::GPR) == 0);
let num_gprs_out = reg_out.srcs.len().try_into().unwrap();
max_live[RegFile::GPR] =
max(max_live[RegFile::GPR], num_gprs_out);
}
}
assert!(block_live_out.len() == bb_idx);
block_live_out.push(live);
}
max_live
}
}
#[derive(Default)]
pub struct SimpleBlockLiveness {
defs: BitSet<SSAValue>,
uses: BitSet<SSAValue>,
last_use: FxHashMap<SSAValue, usize>,
live_in: BitSet<SSAValue>,
live_out: BitSet<SSAValue>,
}
impl SimpleBlockLiveness {
fn new() -> Self {
Default::default()
}
fn add_def(&mut self, ssa: SSAValue) {
self.defs.insert(ssa);
}
fn add_use(&mut self, ssa: SSAValue, ip: usize) {
self.uses.insert(ssa);
self.last_use.insert(ssa, ip);
}
}
impl BlockLiveness for SimpleBlockLiveness {
fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool {
if self.live_out.contains(*val) {
true
} else if let Some(last_use_ip) = self.last_use.get(val) {
*last_use_ip > ip
} else {
false
}
}
fn is_live_in(&self, val: &SSAValue) -> bool {
self.live_in.contains(*val)
}
fn is_live_out(&self, val: &SSAValue) -> bool {
self.live_out.contains(*val)
}
}
pub struct SimpleLiveness {
ssa_block_ip: FxHashMap<SSAValue, (usize, usize)>,
blocks: Vec<SimpleBlockLiveness>,
}
impl SimpleLiveness {
pub fn for_function(func: &Function) -> SimpleLiveness {
let mut l = SimpleLiveness {
ssa_block_ip: Default::default(),
blocks: Vec::new(),
};
for (bi, b) in func.blocks.iter().enumerate() {
let mut bl = SimpleBlockLiveness::new();
for (ip, instr) in b.instrs.iter().enumerate() {
instr.for_each_ssa_use(|ssa| {
bl.add_use(*ssa, ip);
});
instr.for_each_ssa_def(|ssa| {
l.ssa_block_ip.insert(*ssa, (bi, ip));
bl.add_def(*ssa);
});
}
l.blocks.push(bl);
}
assert!(l.blocks.len() == func.blocks.len());
let mut live_in: Vec<BitSet<SSAValue>> =
(0..func.blocks.len()).map(|_| Default::default()).collect();
let mut live_out: Vec<BitSet<SSAValue>> =
(0..func.blocks.len()).map(|_| Default::default()).collect();
BackwardDataflow {
cfg: &func.blocks,
block_in: &mut live_in[..],
block_out: &mut live_out[..],
transfer: |block_idx, _, live_in, live_out| {
let bl = &l.blocks[block_idx];
live_in.union_with(
(live_out.s(..) | bl.uses.s(..)) - bl.defs.s(..),
)
},
join: |live_out, succ_live_in| {
*live_out |= succ_live_in.s(..);
},
}
.solve();
for ((bl, b_live_in), b_live_out) in l
.blocks
.iter_mut()
.zip(live_in.into_iter())
.zip(live_out.into_iter())
{
bl.live_in = b_live_in;
bl.live_out = b_live_out;
}
l
}
}
impl SimpleLiveness {
pub fn def_block_ip(&self, ssa: &SSAValue) -> (usize, usize) {
*self.ssa_block_ip.get(ssa).unwrap()
}
pub fn interferes(&self, a: &SSAValue, b: &SSAValue) -> bool {
let (ab, ai) = self.def_block_ip(a);
let (bb, bi) = self.def_block_ip(b);
match ab.cmp(&bb).then(ai.cmp(&bi)) {
Ordering::Equal => true,
Ordering::Less => self.block_live(bb).is_live_after_ip(a, bi),
Ordering::Greater => self.block_live(ab).is_live_after_ip(b, ai),
}
}
}
impl Liveness for SimpleLiveness {
type PerBlock = SimpleBlockLiveness;
fn block_live(&self, idx: usize) -> &SimpleBlockLiveness {
&self.blocks[idx]
}
}
struct SSAUseDef {
defined: bool,
uses: Vec<usize>,
}
impl SSAUseDef {
fn add_def(&mut self) {
self.defined = true;
}
fn add_in_block_use(&mut self, use_ip: usize) {
self.uses.push(use_ip);
}
fn add_successor_use(
&mut self,
num_block_instrs: usize,
use_ip: usize,
) -> bool {
// IPs are relative to the start of their block
let use_ip = num_block_instrs + use_ip;
if let Some(last_use_ip) = self.uses.last_mut() {
if *last_use_ip < num_block_instrs {
// We've never seen a successor use before
self.uses.push(use_ip);
true
} else if *last_use_ip > use_ip {
// Otherwise, we want the minimum next use
*last_use_ip = use_ip;
true
} else {
false
}
} else {
self.uses.push(use_ip);
true
}
}
}
pub struct NextUseBlockLiveness {
num_instrs: usize,
ssa_map: FxHashMap<SSAValue, SSAUseDef>,
}
impl NextUseBlockLiveness {
fn new(num_instrs: usize) -> Self {
Self {
num_instrs: num_instrs,
ssa_map: Default::default(),
}
}
fn entry_mut(&mut self, ssa: SSAValue) -> &mut SSAUseDef {
self.ssa_map.entry(ssa).or_insert_with(|| SSAUseDef {
defined: false,
uses: Vec::new(),
})
}
fn add_def(&mut self, ssa: SSAValue) {
self.entry_mut(ssa).add_def();
}
fn add_use(&mut self, ssa: SSAValue, ip: usize) {
self.entry_mut(ssa).add_in_block_use(ip);
}
/// Returns an iterator over all the values which are live-in to this block
pub fn iter_live_in(&self) -> impl Iterator<Item = &SSAValue> {
self.ssa_map.iter().filter_map(|(ssa, entry)| {
if entry.defined || entry.uses.is_empty() {
None
} else {
Some(ssa)
}
})
}
/// Returns the IP of the first use of @val
///
/// The returned IP is relative to the start of this block. If the next use
/// is in some successor block, the returned IP is relative to the start of
/// this block. If @val is not used in this block and is not live-out, None
/// is returned.
pub fn first_use(&self, val: &SSAValue) -> Option<usize> {
if let Some(entry) = self.ssa_map.get(val) {
entry.uses.first().cloned()
} else {
None
}
}
/// Returns the IP of the first use of @val which is greater than or equal
/// to @ip
///
/// All IPs are relative to the start of the block. If the next use is some
/// successor block, the returned IP is relative to the start of this block.
pub fn next_use_after_or_at_ip(
&self,
val: &SSAValue,
ip: usize,
) -> Option<usize> {
if let Some(entry) = self.ssa_map.get(val) {
let i = entry.uses.partition_point(|u| *u < ip);
if i < entry.uses.len() {
Some(entry.uses[i])
} else {
None
}
} else {
None
}
}
}
impl BlockLiveness for NextUseBlockLiveness {
fn is_live_after_ip(&self, val: &SSAValue, ip: usize) -> bool {
if let Some(entry) = self.ssa_map.get(val) {
if let Some(last_use_ip) = entry.uses.last() {
*last_use_ip > ip
} else {
false
}
} else {
false
}
}
fn is_live_in(&self, val: &SSAValue) -> bool {
if let Some(entry) = self.ssa_map.get(val) {
!entry.defined && !entry.uses.is_empty()
} else {
false
}
}
fn is_live_out(&self, val: &SSAValue) -> bool {
if let Some(entry) = self.ssa_map.get(val) {
if let Some(last_use_ip) = entry.uses.last() {
*last_use_ip >= self.num_instrs
} else {
false
}
} else {
false
}
}
}
/// An implementation of Liveness that tracks next-use IPs for each SSAValue
///
/// Along with the usual liveness information, this tracks next-use IPs for each
/// SSAValue. Cross-block next-use IPs computed are as per the global next-use
/// distance algorithm described in "Register Spilling and Live-Range Splitting
/// for SSA-Form Programs" by Braun and Hack.
pub struct NextUseLiveness {
blocks: Vec<NextUseBlockLiveness>,
}
impl NextUseLiveness {
pub fn for_function(
func: &Function,
files: &RegFileSet,
) -> NextUseLiveness {
let mut blocks = Vec::new();
for (bi, b) in func.blocks.iter().enumerate() {
let mut bl = NextUseBlockLiveness::new(b.instrs.len());
for (ip, instr) in b.instrs.iter().enumerate() {
instr.for_each_ssa_use(|ssa| {
if files.contains(ssa.file()) {
bl.add_use(*ssa, ip);
}
});
instr.for_each_ssa_def(|ssa| {
if files.contains(ssa.file()) {
bl.add_def(*ssa);
}
});
}
debug_assert!(bi == blocks.len());
blocks.push(bl);
}
let mut live_out: Vec<FxHashMap<SSAValue, usize>> =
(0..func.blocks.len()).map(|_| Default::default()).collect();
BackwardDataflow {
cfg: &func.blocks,
block_in: &mut blocks[..],
block_out: &mut live_out[..],
transfer: |_block_idx, _block, live_in, live_out| {
let num_instrs = live_in.num_instrs;
let mut changed = false;
for (&ssa, &first_use_ip) in live_out.iter() {
changed |= live_in
.entry_mut(ssa)
.add_successor_use(num_instrs, first_use_ip);
}
changed
},
join: |live_out, succ_live_in| {
if live_out.capacity() == 0 {
live_out.reserve(succ_live_in.ssa_map.len());
}
for (&ssa, entry) in succ_live_in.ssa_map.iter() {
if entry.defined {
continue;
}
let Some(&first_use_ip) = entry.uses.first() else {
continue;
};
live_out
.entry(ssa)
.and_modify(|val| *val = min(*val, first_use_ip))
.or_insert(first_use_ip);
}
},
}
.solve();
NextUseLiveness { blocks }
}
}
impl Liveness for NextUseLiveness {
type PerBlock = NextUseBlockLiveness;
fn block_live(&self, idx: usize) -> &NextUseBlockLiveness {
&self.blocks[idx]
}
}
+307
View File
@@ -0,0 +1,307 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::{
api::{GetDebugFlags, DEBUG},
ir::*,
};
use std::cmp::max;
struct LowerCopySwap {
slm_start: u32,
slm_size: u32,
}
impl LowerCopySwap {
fn new(slm_size: u32) -> Self {
Self {
slm_start: slm_size,
slm_size: slm_size,
}
}
fn lower_copy(&mut self, b: &mut impl Builder, copy: OpCopy) {
let dst_reg = copy.dst.as_reg().unwrap();
assert!(dst_reg.comps() == 1);
assert!(copy.src.is_unmodified());
assert!(copy.src.is_uniform() || !dst_reg.is_uniform());
match dst_reg.file() {
RegFile::GPR | RegFile::UGPR => match copy.src.src_ref {
SrcRef::Zero | SrcRef::Imm32(_) => {
b.push_op(OpMov {
dst: copy.dst,
src: copy.src,
quad_lanes: 0xf,
});
}
SrcRef::CBuf(_) => match dst_reg.file() {
RegFile::GPR => {
if b.sm() >= 100 {
b.push_op(OpLdc {
dst: copy.dst,
cb: copy.src,
offset: 0.into(),
mode: LdcMode::Indexed,
mem_type: MemType::B32,
});
} else {
b.push_op(OpMov {
dst: copy.dst,
src: copy.src,
quad_lanes: 0xf,
});
}
}
RegFile::UGPR => {
b.push_op(OpLdc {
dst: copy.dst,
cb: copy.src,
offset: 0.into(),
mode: LdcMode::Indexed,
mem_type: MemType::B32,
});
}
_ => panic!("Invalid cbuf destination"),
},
SrcRef::True | SrcRef::False => {
panic!("Cannot copy to GPR");
}
SrcRef::Reg(src_reg) => match src_reg.file() {
RegFile::GPR | RegFile::UGPR => {
b.push_op(OpMov {
dst: copy.dst,
src: copy.src,
quad_lanes: 0xf,
});
}
RegFile::Bar => {
b.push_op(OpBMov {
dst: copy.dst,
src: copy.src,
clear: false,
});
}
RegFile::Mem => {
let access = MemAccess {
mem_type: MemType::B32,
space: MemSpace::Local,
order: MemOrder::Strong(MemScope::CTA),
eviction_priority: MemEvictionPriority::Normal,
};
let addr = self.slm_start + src_reg.base_idx() * 4;
self.slm_size = max(self.slm_size, addr + 4);
b.push_op(OpLd {
dst: copy.dst,
addr: Src::ZERO,
uniform_addr: Src::ZERO,
pred: true.into(),
offset: addr.try_into().unwrap(),
stride: OffsetStride::X1,
access: access,
});
}
_ => panic!("Cannot copy to GPR"),
},
SrcRef::SSA(_) => panic!("Should be run after RA"),
},
RegFile::Pred | RegFile::UPred => match copy.src.src_ref {
SrcRef::Zero | SrcRef::Imm32(_) | SrcRef::CBuf(_) => {
panic!("Cannot copy to Pred");
}
SrcRef::True => {
b.lop2_to(
copy.dst,
LogicOp2::PassB,
Src::new_imm_bool(true),
Src::new_imm_bool(true),
);
}
SrcRef::False => {
b.lop2_to(
copy.dst,
LogicOp2::PassB,
Src::new_imm_bool(true),
Src::new_imm_bool(false),
);
}
SrcRef::Reg(src_reg) => match src_reg.file() {
RegFile::Pred => {
b.lop2_to(
copy.dst,
LogicOp2::PassB,
Src::new_imm_bool(true),
copy.src,
);
}
RegFile::UPred => {
// PLOP3 supports a UPred in src[2]
b.push_op(OpPLop3 {
dsts: [copy.dst, Dst::None],
srcs: [true.into(), true.into(), copy.src],
ops: [
LogicOp3::new_lut(&|_, _, z| z),
LogicOp3::new_const(false),
],
});
}
_ => panic!("Cannot copy to Pred"),
},
SrcRef::SSA(_) => panic!("Should be run after RA"),
},
RegFile::Bar => match copy.src.src_ref {
SrcRef::Reg(src_reg) => match src_reg.file() {
RegFile::GPR | RegFile::UGPR => {
b.push_op(OpBMov {
dst: copy.dst,
src: copy.src,
clear: false,
});
}
_ => panic!("Cannot copy to Bar"),
},
_ => panic!("Cannot copy to Bar"),
},
RegFile::Mem => match copy.src.src_ref {
SrcRef::Reg(src_reg) => match src_reg.file() {
RegFile::GPR => {
let access = MemAccess {
mem_type: MemType::B32,
space: MemSpace::Local,
order: MemOrder::Strong(MemScope::CTA),
eviction_priority: MemEvictionPriority::Normal,
};
let addr = self.slm_start + dst_reg.base_idx() * 4;
self.slm_size = max(self.slm_size, addr + 4);
b.push_op(OpSt {
addr: Src::ZERO,
uniform_addr: Src::ZERO,
data: copy.src,
offset: addr.try_into().unwrap(),
stride: OffsetStride::X1,
access: access,
});
}
_ => panic!("Cannot copy to Mem"),
},
_ => panic!("Cannot copy to Mem"),
},
_ => panic!("Unhandled register file"),
}
}
fn lower_r2ur(&mut self, b: &mut impl Builder, r2ur: OpR2UR) {
assert!(r2ur.src.is_unmodified());
if r2ur.src.is_uniform() {
let copy = OpCopy {
dst: r2ur.dst,
src: r2ur.src,
};
self.lower_copy(b, copy);
} else {
let src_file = r2ur.src.src_ref.as_reg().unwrap().file();
let dst_file = r2ur.dst.as_reg().unwrap().file();
match src_file {
RegFile::GPR => {
assert!(dst_file == RegFile::UGPR);
b.push_op(r2ur);
}
RegFile::Pred => {
assert!(dst_file == RegFile::UPred);
// It doesn't matter what channel we take
b.push_op(OpVote {
op: VoteOp::Any,
ballot: Dst::None,
vote: r2ur.dst,
pred: r2ur.src,
});
}
_ => panic!("No matching uniform register file"),
}
}
}
fn lower_swap(&mut self, b: &mut impl Builder, swap: OpSwap) {
let x = *swap.dsts[0].as_reg().unwrap();
let y = *swap.dsts[1].as_reg().unwrap();
assert!(x.file() == y.file());
assert!(x.file() != RegFile::Mem);
assert!(x.comps() == 1 && y.comps() == 1);
assert!(swap.srcs[0].is_unmodified());
assert!(*swap.srcs[0].src_ref.as_reg().unwrap() == y);
assert!(swap.srcs[1].is_unmodified());
assert!(*swap.srcs[1].src_ref.as_reg().unwrap() == x);
if x == y {
// Nothing to do
} else if x.is_predicate() && b.sm() >= 70 {
b.push_op(OpPLop3 {
dsts: [x.into(), y.into()],
srcs: [x.into(), y.into(), Src::new_imm_bool(true)],
ops: [
LogicOp3::new_lut(&|_, y, _| y),
LogicOp3::new_lut(&|x, _, _| x),
],
});
} else {
b.lop2_to(x.into(), LogicOp2::Xor, x.into(), y.into());
b.lop2_to(y.into(), LogicOp2::Xor, x.into(), y.into());
b.lop2_to(x.into(), LogicOp2::Xor, x.into(), y.into());
}
}
fn run(&mut self, s: &mut Shader) {
let sm = s.sm;
s.map_instrs(|instr: Instr, _| -> MappedInstrs {
match instr.op {
Op::R2UR(r2ur) => {
debug_assert!(instr.pred.is_true());
let mut b = InstrBuilder::new(sm);
if DEBUG.annotate() {
b.push_instr(Instr::new(OpAnnotate {
annotation: "r2ur lowered by lower_copy_swap"
.into(),
}));
}
self.lower_r2ur(&mut b, *r2ur);
b.into_mapped_instrs()
}
Op::Copy(copy) => {
debug_assert!(instr.pred.is_true());
let mut b = InstrBuilder::new(sm);
if DEBUG.annotate() {
b.push_instr(Instr::new(OpAnnotate {
annotation: "copy lowered by lower_copy_swap"
.into(),
}));
}
self.lower_copy(&mut b, *copy);
b.into_mapped_instrs()
}
Op::Swap(swap) => {
debug_assert!(instr.pred.is_true());
let mut b = InstrBuilder::new(sm);
if DEBUG.annotate() {
b.push_instr(Instr::new(OpAnnotate {
annotation: "swap lowered by lower_copy_swap"
.into(),
}));
}
self.lower_swap(&mut b, *swap);
b.into_mapped_instrs()
}
_ => [instr].into(),
}
});
}
}
impl Shader<'_> {
pub fn lower_copy_swap(&mut self) {
let mut pass = LowerCopySwap::new(self.info.slm_size);
pass.run(self);
self.info.slm_size = pass.slm_size;
}
}
@@ -0,0 +1,275 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::{
api::{GetDebugFlags, DEBUG},
ir::*,
};
use rustc_hash::FxHashMap;
struct CopyNode {
num_reads: usize,
src: isize,
}
struct CopyGraph {
nodes: Vec<CopyNode>,
}
impl CopyGraph {
pub fn new() -> CopyGraph {
CopyGraph { nodes: Vec::new() }
}
fn add_node(&mut self) -> usize {
let node_idx = self.nodes.len();
self.nodes.push(CopyNode {
num_reads: 0,
src: -1,
});
node_idx
}
fn num_reads(&self, node_idx: usize) -> usize {
self.nodes[node_idx].num_reads
}
fn src(&self, node_idx: usize) -> Option<usize> {
if self.nodes[node_idx].src < 0 {
None
} else {
Some(self.nodes[node_idx].src.try_into().unwrap())
}
}
fn add_edge(&mut self, dst_idx: usize, src_idx: usize) {
// Disallow self-loops
assert!(dst_idx != src_idx);
// Each node has in-degree at most 1
assert!(self.nodes[dst_idx].src == -1);
self.nodes[dst_idx].src = src_idx.try_into().unwrap();
self.nodes[src_idx].num_reads += 1;
}
fn del_edge(&mut self, dst_idx: usize, src_idx: usize) -> bool {
assert!(self.nodes[dst_idx].src >= 0);
self.nodes[dst_idx].src = -1;
self.nodes[src_idx].num_reads -= 1;
self.nodes[src_idx].num_reads == 0
}
}
fn copy_needs_tmp(dst: &RegRef, src: &SrcRef) -> bool {
if let Some(src_reg) = src.as_reg() {
(dst.file() == RegFile::Mem && src_reg.file() != RegFile::GPR)
|| (dst.file() == RegFile::Bar && src_reg.file() == RegFile::Bar)
} else {
// Non-GPR to Mem copies need a temporary
dst.file() == RegFile::Mem
}
}
fn cycle_use_swap(pc: &OpParCopy, file: RegFile) -> bool {
match file {
RegFile::GPR => {
if let Some(tmp) = &pc.tmp {
debug_assert!(tmp.file() == RegFile::GPR);
false
} else {
true
}
}
RegFile::Bar | RegFile::Mem => {
let tmp = &pc.tmp.expect("This copy needs a temporary");
assert!(tmp.comps() >= 2, "Memory cycles need 2 temporaries");
false
}
_ => true,
}
}
fn lower_par_copy(pc: OpParCopy, sm: &ShaderModelInfo) -> MappedInstrs {
let mut graph = CopyGraph::new();
let mut vals = Vec::new();
let mut reg_to_idx = FxHashMap::default();
for (i, (dst, _)) in pc.dsts_srcs.iter().enumerate() {
// Destinations must be pairwise unique
let reg = dst.as_reg().unwrap();
assert!(!reg_to_idx.contains_key(reg));
// Everything must be scalar
assert!(reg.comps() == 1);
let node_idx = graph.add_node();
assert!(node_idx == i && vals.len() == i);
vals.push(SrcRef::from(*reg));
reg_to_idx.insert(*reg, i);
}
for (dst_idx, (_, src)) in pc.dsts_srcs.iter().enumerate() {
assert!(src.is_unmodified());
let src = &src.src_ref;
let src_idx = if let SrcRef::Reg(reg) = src {
// Everything must be scalar
assert!(reg.comps() == 1);
*reg_to_idx.entry(*reg).or_insert_with(|| {
let node_idx = graph.add_node();
assert!(node_idx == vals.len());
vals.push(src.clone());
node_idx
})
} else {
// We can't have bindless CBufs because we can't resolve cycles
// containing one.
assert!(src.get_reg().is_none());
let node_idx = graph.add_node();
assert!(node_idx == vals.len());
vals.push(src.clone());
node_idx
};
if dst_idx != src_idx {
graph.add_edge(dst_idx, src_idx);
}
}
let mut b = InstrBuilder::new(sm);
let mut ready = Vec::new();
for i in 0..pc.dsts_srcs.len() {
if graph.num_reads(i) == 0 {
ready.push(i);
}
}
while let Some(dst_idx) = ready.pop() {
if let Some(src_idx) = graph.src(dst_idx) {
let dst = *vals[dst_idx].as_reg().unwrap();
let src = vals[src_idx].clone();
if copy_needs_tmp(&dst, &src) {
let tmp = pc.tmp.expect("This copy needs a temporary").comp(0);
b.copy_to(tmp.into(), src.into());
b.copy_to(dst.into(), tmp.into());
} else {
b.copy_to(dst.into(), src.into());
}
if graph.del_edge(dst_idx, src_idx) {
ready.push(src_idx);
}
}
}
// At this point, all we are left with in the graph are isolated nodes
// (no edges) and cycles.
//
// Proof:
//
// Without loss of generality, we can assume that there are no isolated
// nodes in the graph. By construction, no node has in-degree more than 1
// (that would indicate a duplicate destination). The loop above ensures
// that there are no nodes with an in-degree of 1 and an out-degree of 0.
//
// Suppose that there were a node with an in-degree of 0. Then, because no
// node has an in-degree greater than 1, the number of edges must be less
// than the number of nodes. This implies that there is some node N with
// with out-degree of 0. If N has an in-degree of 0 then it is isolated,
// which is a contradiction. If N has an in-degree of 1 then it is a node
// with in-degree of 1 and out-degree of 0 which is also a contradiction.
// Therefore, there are no nodes with in-degree of 0 and all nodes have an
// in-degree of 1.
//
// Since all nodes have an in-degree of 1, no node has an out-degree of 0
// and, because the sum of all in-degrees equals the sum of all out-degrees
// (they both equal the number of edges), every node must also have an
// out-degree of 1. Therefore, the graph only contains cycles.
//
// QED
for i in 0..pc.dsts_srcs.len() {
if graph.src(i).is_none() {
debug_assert!(graph.num_reads(i) == 0);
continue;
}
let file = vals[i].as_reg().unwrap().file();
if cycle_use_swap(&pc, file) {
loop {
let j = graph.src(i).unwrap();
// We're part of a cycle so j also has a source
let k = graph.src(j).unwrap();
let j_reg = vals[j].as_reg().unwrap();
let k_reg = vals[k].as_reg().unwrap();
b.swap(*j_reg, *k_reg);
graph.del_edge(i, j);
graph.del_edge(j, k);
if i == k {
// This was our last swap
break;
} else {
graph.add_edge(i, k);
}
}
} else {
let pc_tmp = pc.tmp.expect("This copy needs a temporary");
let tmp = pc_tmp.comp(0);
let mut p = i;
let mut p_reg = *vals[p].as_reg().unwrap();
b.copy_to(tmp.into(), p_reg.into());
loop {
let j = graph.src(p).unwrap();
if j == i {
break;
}
let j_reg = *vals[j].as_reg().unwrap();
debug_assert!(j_reg.file() == file);
if file == RegFile::Bar || file == RegFile::Mem {
let copy_tmp = pc_tmp.comp(1);
b.copy_to(copy_tmp.into(), j_reg.into());
b.copy_to(p_reg.into(), copy_tmp.into());
} else {
b.copy_to(p_reg.into(), j_reg.into());
}
graph.del_edge(p, j);
p = j;
p_reg = j_reg;
}
b.copy_to(p_reg.into(), tmp.into());
graph.del_edge(p, i);
}
}
b.into_mapped_instrs()
}
impl Shader<'_> {
pub fn lower_par_copies(&mut self) {
let sm = self.sm;
self.map_instrs(|instr, _| -> MappedInstrs {
match instr.op {
Op::ParCopy(pc) => {
assert!(instr.pred.is_true());
let mut instrs = MappedInstrs::new();
if DEBUG.annotate() {
instrs.push(Instr::new(OpAnnotate {
annotation: "par_copy lowered by lower_par_copy"
.into(),
}));
}
instrs.extend(lower_par_copy(*pc, sm));
instrs
}
_ => [instr].into(),
}
});
}
}
File diff suppressed because it is too large. Load diff
+275
View File
@@ -0,0 +1,275 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::api::{GetDebugFlags, DEBUG};
use crate::ir::*;
use compiler::bitset::BitSet;
use rustc_hash::FxHashMap;
struct PhiMap {
phi_ssa: FxHashMap<Phi, Vec<SSAValue>>,
ssa_phi: FxHashMap<SSAValue, Phi>,
}
impl PhiMap {
pub fn new() -> PhiMap {
PhiMap {
ssa_phi: Default::default(),
phi_ssa: Default::default(),
}
}
fn add_phi_srcs(&mut self, op: &OpPhiSrcs) {
for (phi, src) in op.srcs.iter() {
if let SrcRef::SSA(ssa) = &src.src_ref {
assert!(ssa.comps() == 1);
let phi_srcs = self.phi_ssa.entry(*phi).or_default();
phi_srcs.push(ssa[0]);
}
}
}
fn add_phi_dsts(&mut self, op: &OpPhiDsts) {
for (phi, dst) in op.dsts.iter() {
if let Dst::SSA(ssa) = dst {
assert!(ssa.comps() == 1);
self.ssa_phi.insert(ssa[0], *phi);
}
}
}
fn get_phi(&self, ssa: &SSAValue) -> Option<&Phi> {
self.ssa_phi.get(ssa)
}
fn phi_srcs(&self, idx: &Phi) -> &[SSAValue] {
self.phi_ssa.get(idx).unwrap()
}
}
#[derive(Default)]
struct BarPropPass {
ssa_map: FxHashMap<SSAValue, SSAValue>,
phi_is_bar: BitSet<Phi>,
phi_is_not_bar: BitSet<Phi>,
}
impl BarPropPass {
pub fn new() -> BarPropPass {
Default::default()
}
fn add_copy(&mut self, dst: SSAValue, src: SSAValue) {
assert!(dst.file() == RegFile::Bar || src.file() == RegFile::Bar);
self.ssa_map.insert(dst, src);
}
fn is_bar(&self, ssa: &SSAValue) -> bool {
ssa.file() == RegFile::Bar || self.ssa_map.contains_key(ssa)
}
fn map_bar<'a>(&'a self, ssa: &'a SSAValue) -> Option<&'a SSAValue> {
let mut ssa = ssa;
let mut last_bar = None;
loop {
let Some(mapped) = self.ssa_map.get(ssa) else {
break;
};
if mapped.file() == RegFile::Bar {
last_bar = Some(mapped);
}
ssa = mapped;
}
last_bar
}
fn phi_can_be_bar_recur(
&mut self,
phi_map: &PhiMap,
seen: &mut BitSet<Phi>,
phi: Phi,
) -> bool {
if self.phi_is_not_bar.contains(phi) {
return false;
}
if seen.contains(phi) {
// If we've hit a cycle, that's not a fail
return true;
}
seen.insert(phi);
for src_ssa in phi_map.phi_srcs(&phi) {
if self.is_bar(src_ssa) {
continue;
}
if let Some(src_phi) = phi_map.get_phi(src_ssa) {
if self.phi_can_be_bar_recur(phi_map, seen, *src_phi) {
continue;
}
}
self.phi_is_not_bar.insert(phi);
return false;
}
true
}
fn add_phi_recur(
&mut self,
ssa_alloc: &mut SSAValueAllocator,
phi_map: &PhiMap,
needs_bar: &mut BitSet<Phi>,
phi: Phi,
ssa: SSAValue,
) {
if !needs_bar.contains(phi) {
return;
}
let bar = ssa_alloc.alloc(RegFile::Bar);
self.ssa_map.insert(ssa, bar);
self.phi_is_bar.insert(phi);
needs_bar.remove(phi);
for src_ssa in phi_map.phi_srcs(&phi) {
if let Some(src_phi) = phi_map.get_phi(src_ssa) {
self.add_phi_recur(
ssa_alloc, phi_map, needs_bar, *src_phi, *src_ssa,
);
}
}
}
fn try_add_phi(
&mut self,
ssa_alloc: &mut SSAValueAllocator,
phi_map: &PhiMap,
phi: Phi,
ssa: SSAValue,
) {
if self.phi_is_bar.contains(phi) {
return;
}
let mut seen = BitSet::new();
if self.phi_can_be_bar_recur(phi_map, &mut seen, phi) {
self.add_phi_recur(ssa_alloc, phi_map, &mut seen, phi, ssa);
assert!(seen.is_empty());
}
}
fn run(&mut self, f: &mut Function) {
let mut phi_map = PhiMap::new();
let mut phis_want_bar = Vec::new();
for b in &f.blocks {
for instr in &b.instrs {
match &instr.op {
Op::PhiSrcs(op) => {
phi_map.add_phi_srcs(op);
}
Op::PhiDsts(op) => {
phi_map.add_phi_dsts(op);
}
Op::BMov(op) => {
assert!(!op.clear);
assert!(op.src.is_unmodified());
let dst = op.dst.as_ssa().unwrap();
let src = op.src.as_ssa().unwrap();
assert!(dst.comps() == 1 && src.comps() == 1);
self.add_copy(dst[0], src[0]);
if let Some(phi) = phi_map.get_phi(&src[0]) {
phis_want_bar.push((*phi, src[0]));
}
}
_ => (),
}
}
}
for (phi, ssa) in phis_want_bar.drain(..) {
self.try_add_phi(&mut f.ssa_alloc, &phi_map, phi, ssa);
}
f.map_instrs(|mut instr, _| {
match &mut instr.op {
Op::PhiSrcs(op) => {
for (idx, src) in op.srcs.iter_mut() {
if self.phi_is_bar.contains(*idx) {
// Barrier immediates don't exist
let ssa = src.as_ssa().unwrap();
let bar = *self.map_bar(&ssa[0]).unwrap();
*src = bar.into();
}
}
[instr].into()
}
Op::PhiDsts(op) => {
let mut bmovs = Vec::new();
for (idx, dst) in op.dsts.iter_mut() {
if self.phi_is_bar.contains(*idx) {
let ssa = dst.as_ssa().unwrap().clone();
let bar = *self.ssa_map.get(&ssa[0]).unwrap();
*dst = bar.into();
// On the off chance that someone still wants the
// GPR version of this barrier, insert an OpBMov to
// copy into the GPR. DCE will clean it up if it's
// never used.
bmovs.push(Instr::new(OpBMov {
dst: ssa.into(),
src: bar.into(),
clear: false,
}));
}
}
if bmovs.is_empty() {
[instr].into()
} else {
if DEBUG.annotate() {
bmovs.insert(
0,
Instr::new(OpAnnotate {
annotation: "generated by opt_bar_prop"
.into(),
}),
);
}
bmovs.insert(1, instr);
bmovs.into()
}
}
_ => {
let src_types = instr.src_types();
for (i, src) in instr.srcs_mut().iter_mut().enumerate() {
if src_types[i] != SrcType::Bar {
continue;
}
if let SrcRef::SSA(ssa) = &src.src_ref {
if let Some(bar) = self.map_bar(&ssa[0]) {
*src = (*bar).into();
}
};
}
[instr].into()
}
}
});
}
}
impl Shader<'_> {
pub fn opt_bar_prop(&mut self) {
for f in &mut self.functions {
BarPropPass::new().run(f);
}
}
}
+878
View File
@@ -0,0 +1,878 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use rustc_hash::FxHashMap;
enum CBufRule {
Yes,
No,
BindlessRequiresBlock(usize),
}
impl CBufRule {
fn allows_src(&self, src_bi: usize, src: &Src) -> bool {
let SrcRef::CBuf(cb) = &src.src_ref else {
return true;
};
match self {
CBufRule::Yes => true,
CBufRule::No => false,
CBufRule::BindlessRequiresBlock(bi) => match cb.buf {
CBuf::Binding(_) => true,
CBuf::BindlessSSA(_) => src_bi == *bi,
CBuf::BindlessUGPR(_) => panic!("Not in SSA form"),
},
}
}
}
struct CopyEntry {
bi: usize,
src_type: SrcType,
src: Src,
ftz: bool,
}
struct PrmtEntry {
bi: usize,
sel: PrmtSel,
srcs: [Src; 2],
}
/// This entry tracks b2i conversions
struct ConvBoolToInt {
src: Src,
}
enum CopyPropEntry {
Copy(CopyEntry),
Prmt(PrmtEntry),
ConvBoolToInt(ConvBoolToInt),
}
struct CopyPropPass<'a> {
sm: &'a ShaderModelInfo,
ssa_map: FxHashMap<SSAValue, CopyPropEntry>,
}
impl<'a> CopyPropPass<'a> {
pub fn new(sm: &'a ShaderModelInfo) -> Self {
CopyPropPass {
sm: sm,
ssa_map: Default::default(),
}
}
fn add_copy(
&mut self,
bi: usize,
dst: SSAValue,
src_type: SrcType,
src: Src,
) {
self.add_copy_float(bi, dst, src_type, src, false);
}
fn add_copy_float(
&mut self,
bi: usize,
dst: SSAValue,
src_type: SrcType,
src: Src,
ftz: bool,
) {
assert!(src.src_ref.get_reg().is_none());
self.ssa_map.insert(
dst,
CopyPropEntry::Copy(CopyEntry {
bi,
src_type,
src,
ftz,
}),
);
}
fn add_b2i(&mut self, _bi: usize, dst: SSAValue, src: Src) {
assert!(src.src_ref.get_reg().is_none());
assert!(src.is_predicate());
assert!(dst.is_gpr());
self.ssa_map
.insert(dst, CopyPropEntry::ConvBoolToInt(ConvBoolToInt { src }));
}
fn add_i2b(&mut self, bi: usize, dst: SSAValue, src: Src, inverted: bool) {
assert!(src.src_ref.get_reg().is_none());
assert!(dst.is_predicate());
// Fold i2b(b2i(x))
// if we find i2b(b2i(x)) replace that with a copy
let parent = src.as_ssa().and_then(|x| self.get_copy(&x[0]));
let Some(CopyPropEntry::ConvBoolToInt(par_entry)) = parent else {
return;
};
let mut copy_src = par_entry.src.clone().modify(src.src_mod);
if inverted {
copy_src = copy_src.bnot();
}
self.add_copy(bi, dst, SrcType::Pred, copy_src);
}
fn add_prmt(
&mut self,
bi: usize,
dst: SSAValue,
sel: PrmtSel,
srcs: [Src; 2],
) {
assert!(
srcs[0].src_ref.get_reg().is_none()
&& srcs[1].src_ref.get_reg().is_none()
);
self.ssa_map
.insert(dst, CopyPropEntry::Prmt(PrmtEntry { bi, sel, srcs }));
}
fn add_fp64_copy(&mut self, bi: usize, dst: &SSARef, src: Src) {
assert!(dst.comps() == 2);
match src.src_ref {
SrcRef::Zero | SrcRef::Imm32(_) => {
self.add_copy(bi, dst[0], SrcType::ALU, Src::ZERO);
self.add_copy(bi, dst[1], SrcType::F64, src);
}
SrcRef::CBuf(cb) => {
let lo32 = Src::from(SrcRef::CBuf(cb.clone()));
let hi32 = Src {
src_ref: SrcRef::CBuf(cb.offset(4)),
src_mod: src.src_mod,
src_swizzle: src.src_swizzle,
};
self.add_copy(bi, dst[0], SrcType::ALU, lo32);
self.add_copy(bi, dst[1], SrcType::F64, hi32);
}
SrcRef::SSA(ssa) => {
assert!(ssa.comps() == 2);
let lo32 = Src::from(ssa[0]);
let hi32 = Src {
src_ref: ssa[1].into(),
src_mod: src.src_mod,
src_swizzle: src.src_swizzle,
};
self.add_copy(bi, dst[0], SrcType::ALU, lo32);
self.add_copy(bi, dst[1], SrcType::F64, hi32);
}
_ => (),
}
}
fn get_copy(&self, dst: &SSAValue) -> Option<&CopyPropEntry> {
self.ssa_map.get(dst)
}
fn prop_to_pred(&self, pred: &mut Pred) {
loop {
let src_ssa = match &pred.pred_ref {
PredRef::SSA(ssa) => ssa,
_ => return,
};
let Some(CopyPropEntry::Copy(entry)) = self.get_copy(src_ssa)
else {
return;
};
match &entry.src.src_ref {
SrcRef::True => {
pred.pred_ref = PredRef::None;
}
SrcRef::False => {
pred.pred_ref = PredRef::None;
pred.pred_inv = !pred.pred_inv;
}
SrcRef::SSA(ssa) => {
assert!(ssa.comps() == 1);
pred.pred_ref = PredRef::SSA(ssa[0]);
}
_ => return,
}
match entry.src.src_mod {
SrcMod::None => (),
SrcMod::BNot => {
pred.pred_inv = !pred.pred_inv;
}
_ => panic!("Invalid predicate modifier"),
}
}
}
fn prop_to_ssa_values(
&self,
src_ssa: &mut [SSAValue],
same_file: bool,
) -> bool {
let mut progress = false;
for c_ssa in src_ssa {
let Some(CopyPropEntry::Copy(entry)) = self.get_copy(c_ssa) else {
continue;
};
if entry.ftz {
continue;
}
if entry.src.is_unmodified() {
if let SrcRef::SSA(entry_ssa) = &entry.src.src_ref {
assert!(entry_ssa.comps() == 1);
if same_file && (c_ssa.file() != entry_ssa[0].file()) {
continue;
}
*c_ssa = entry_ssa[0];
progress = true;
}
}
}
progress
}
fn prop_to_ssa_ref(&self, src_ssa: &mut SSARef) -> bool {
self.prop_to_ssa_values(&mut src_ssa[..], false)
}
fn prop_to_cbuf_ref(&self, cbuf: &mut CBufRef) {
match cbuf.buf {
CBuf::BindlessSSA(ref mut ssa_values) => loop {
if !self.prop_to_ssa_values(&mut ssa_values[..], true) {
break;
}
},
_ => (),
}
}
fn prop_to_ssa_src(&self, src: &mut Src) {
assert!(src.is_unmodified());
if let SrcRef::SSA(src_ssa) = &mut src.src_ref {
loop {
if !self.prop_to_ssa_ref(src_ssa) {
break;
}
}
}
}
fn prop_to_gpr_src(&self, src: &mut Src) {
loop {
let src_ssa = match &mut src.src_ref {
SrcRef::SSA(ssa) => {
// First, try to propagate SSA components
if self.prop_to_ssa_ref(ssa) {
continue;
}
ssa
}
_ => return,
};
for c in 0..usize::from(src_ssa.comps()) {
let Some(CopyPropEntry::Copy(entry)) =
self.get_copy(&src_ssa[c])
else {
return;
};
match entry.src.src_ref {
SrcRef::Zero | SrcRef::Imm32(0) => (),
_ => return,
}
}
// If we got here, all the components are zero
src.src_ref = SrcRef::Zero;
}
}
fn prop_to_scalar_src(
&self,
src_type: SrcType,
cbuf_rule: &CBufRule,
src: &mut Src,
instr_ftz: bool,
) {
loop {
let src_ssa = match &src.src_ref {
SrcRef::SSA(ssa) => ssa,
_ => return,
};
assert!(src_ssa.comps() == 1);
let entry = match self.get_copy(&src_ssa[0]) {
Some(e) => e,
None => return,
};
match entry {
CopyPropEntry::Copy(entry) => {
// If the original op flushes denorms, but not the
// consumer, we skip the propagation
if entry.ftz && !instr_ftz {
return;
}
if !cbuf_rule.allows_src(entry.bi, &entry.src) {
return;
}
// If there are modifiers or ftz enabled, the source types
// have to match
if (!entry.src.is_unmodified() || entry.ftz)
&& !entry.src_type.eq_ftz_mod(src_type)
{
return;
}
src.src_ref = entry.src.src_ref.clone();
src.src_mod = entry.src.src_mod.modify(src.src_mod);
}
CopyPropEntry::Prmt(entry) => {
// Turn the swizzle into a permute. For F16, we use Xx to
// indicate that it only takes the bottom 16 bits.
let swizzle_prmt: [u8; 4] = match src_type {
SrcType::F16 => match src.src_swizzle {
SrcSwizzle::None | SrcSwizzle::Xx => [0, 1, 0, 1],
SrcSwizzle::Yy => [2, 3, 2, 3],
},
SrcType::F16v2 => match src.src_swizzle {
SrcSwizzle::None => [0, 1, 2, 3],
SrcSwizzle::Xx => [0, 1, 0, 1],
SrcSwizzle::Yy => [2, 3, 2, 3],
},
_ => [0, 1, 2, 3],
};
let mut entry_src_idx = None;
let mut combined = [0_u8; 4];
for i in 0..4 {
let prmt_byte = entry.sel.get(swizzle_prmt[i].into());
// If we have a sign extension, we cannot simplify it.
if prmt_byte.msb() {
return;
}
// Ensure we are using the same source, we cannot
// combine multiple sources.
if entry_src_idx.is_none() {
entry_src_idx = Some(prmt_byte.src());
} else if entry_src_idx != Some(prmt_byte.src()) {
return;
}
combined[i] = prmt_byte.byte().try_into().unwrap();
}
let entry_src_idx = entry_src_idx.unwrap();
let entry_src = &entry.srcs[entry_src_idx];
if !cbuf_rule.allows_src(entry.bi, entry_src) {
return;
}
// See if that permute is a valid swizzle
let new_swizzle = match src_type {
SrcType::F16 => match combined {
[0, 1, _, _] => SrcSwizzle::None,
[2, 3, _, _] => SrcSwizzle::Yy,
_ => return,
},
SrcType::F16v2 => match combined {
[0, 1, 2, 3] => SrcSwizzle::None,
[0, 1, 0, 1] => SrcSwizzle::Xx,
[2, 3, 2, 3] => SrcSwizzle::Yy,
_ => return,
},
_ => {
if combined != [0, 1, 2, 3] {
return;
}
SrcSwizzle::None
}
};
src.src_ref = entry_src.src_ref.clone();
src.src_mod = entry_src.src_mod.modify(src.src_mod);
src.src_swizzle = new_swizzle;
}
CopyPropEntry::ConvBoolToInt(_) => {
// b2i(i2b(x)) can't be easily optimized
return;
}
}
}
}
fn prop_to_f64_src(&self, cbuf_rule: &CBufRule, src: &mut Src) {
loop {
let src_ssa = match &mut src.src_ref {
SrcRef::SSA(ssa) => ssa,
_ => return,
};
assert!(src_ssa.comps() == 2);
// First, try to propagate the two halves individually. Source
// modifiers only apply to the high 32 bits so we have to reject
// any copies with source modifiers in the low bits and apply
// source modifiers as needed when propagating the high bits.
let lo_entry_or_none = self.get_copy(&src_ssa[0]);
if let Some(CopyPropEntry::Copy(lo_entry)) = lo_entry_or_none {
if lo_entry.src.is_unmodified() {
if let SrcRef::SSA(lo_entry_ssa) = &lo_entry.src.src_ref {
src_ssa[0] = lo_entry_ssa[0];
continue;
}
}
}
let hi_entry_or_none = self.get_copy(&src_ssa[1]);
if let Some(CopyPropEntry::Copy(hi_entry)) = hi_entry_or_none {
if hi_entry.src.is_unmodified()
|| hi_entry.src_type == SrcType::F64
{
if let SrcRef::SSA(hi_entry_ssa) = &hi_entry.src.src_ref {
src_ssa[1] = hi_entry_ssa[0];
src.src_mod = hi_entry.src.src_mod.modify(src.src_mod);
continue;
}
}
}
let Some(CopyPropEntry::Copy(lo_entry)) = lo_entry_or_none else {
return;
};
let Some(CopyPropEntry::Copy(hi_entry)) = hi_entry_or_none else {
return;
};
if !lo_entry.src.is_unmodified() {
return;
}
if !hi_entry.src.is_unmodified()
&& hi_entry.src_type != SrcType::F64
{
return;
}
if !cbuf_rule.allows_src(hi_entry.bi, &hi_entry.src)
|| !cbuf_rule.allows_src(lo_entry.bi, &lo_entry.src)
{
return;
}
let new_src_ref = match &hi_entry.src.src_ref {
SrcRef::Zero => match &lo_entry.src.src_ref {
SrcRef::Zero | SrcRef::Imm32(0) => SrcRef::Zero,
_ => return,
},
SrcRef::Imm32(i) => {
// 32-bit immediates for f64 srouces are the top 32 bits
// with zero in the lower 32.
match lo_entry.src.src_ref {
SrcRef::Zero | SrcRef::Imm32(0) => SrcRef::Imm32(*i),
_ => return,
}
}
SrcRef::CBuf(hi_cb) => match &lo_entry.src.src_ref {
SrcRef::CBuf(lo_cb) => {
if hi_cb.buf != lo_cb.buf {
return;
}
if lo_cb.offset % 8 != 0 {
return;
}
if hi_cb.offset != lo_cb.offset + 4 {
return;
}
SrcRef::CBuf(lo_cb.clone())
}
_ => return,
},
// SrcRef::SSA is already handled above
_ => return,
};
src.src_ref = new_src_ref;
src.src_mod = hi_entry.src.src_mod.modify(src.src_mod);
}
}
fn prop_to_src(
&self,
src_type: SrcType,
cbuf_rule: &CBufRule,
src: &mut Src,
instr_ftz: bool,
) {
match src_type {
SrcType::SSA => {
self.prop_to_ssa_src(src);
}
SrcType::GPR => {
self.prop_to_gpr_src(src);
}
SrcType::ALU
| SrcType::F16
| SrcType::F16v2
| SrcType::F32
| SrcType::I32
| SrcType::B32
| SrcType::Pred => {
self.prop_to_scalar_src(src_type, cbuf_rule, src, instr_ftz);
}
SrcType::F64 => {
self.prop_to_f64_src(cbuf_rule, src);
}
SrcType::Carry | SrcType::Bar => (),
}
match &mut src.src_ref {
SrcRef::CBuf(cbuf) => {
self.prop_to_cbuf_ref(cbuf);
}
_ => (),
}
}
fn try_add_instr(&mut self, bi: usize, instr: &Instr) {
match &instr.op {
Op::HAdd2(add) => {
let dst = add.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
if !add.saturate {
if add.srcs[0].is_fneg_zero(SrcType::F16v2) {
self.add_copy_float(
bi,
dst,
SrcType::F16v2,
add.srcs[1].clone(),
add.ftz,
);
} else if add.srcs[1].is_fneg_zero(SrcType::F16v2) {
self.add_copy_float(
bi,
dst,
SrcType::F16v2,
add.srcs[0].clone(),
add.ftz,
);
}
}
}
Op::FAdd(add) => {
let dst = add.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
if !add.saturate {
if add.srcs[0].is_fneg_zero(SrcType::F32) {
self.add_copy_float(
bi,
dst,
SrcType::F32,
add.srcs[1].clone(),
add.ftz,
);
} else if add.srcs[1].is_fneg_zero(SrcType::F32) {
self.add_copy_float(
bi,
dst,
SrcType::F32,
add.srcs[0].clone(),
add.ftz,
);
}
}
}
Op::DAdd(add) => {
let dst = add.dst.as_ssa().unwrap();
if add.srcs[0].is_fneg_zero(SrcType::F64) {
self.add_fp64_copy(bi, dst, add.srcs[1].clone());
} else if add.srcs[1].is_fneg_zero(SrcType::F64) {
self.add_fp64_copy(bi, dst, add.srcs[0].clone());
}
}
Op::Lop3(lop) => {
let dst = lop.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
let op = lop.op;
if op.lut == 0 {
self.add_copy(bi, dst, SrcType::ALU, SrcRef::Zero.into());
} else if op.lut == !0 {
self.add_copy(
bi,
dst,
SrcType::ALU,
SrcRef::Imm32(u32::MAX).into(),
);
} else {
for s in 0..3 {
if op.lut == LogicOp3::SRC_MASKS[s] {
self.add_copy(
bi,
dst,
SrcType::ALU,
lop.srcs[s].clone(),
);
}
}
}
}
Op::PLop3(lop) => {
for i in 0..2 {
let dst = match &lop.dsts[i] {
Dst::SSA(vec) => {
assert!(vec.comps() == 1);
vec[0]
}
_ => continue,
};
let op = lop.ops[i];
if op.lut == 0 {
self.add_copy(
bi,
dst,
SrcType::Pred,
SrcRef::False.into(),
);
} else if op.lut == !0 {
self.add_copy(
bi,
dst,
SrcType::Pred,
SrcRef::True.into(),
);
} else {
for s in 0..3 {
if op.lut == LogicOp3::SRC_MASKS[s] {
self.add_copy(
bi,
dst,
SrcType::Pred,
lop.srcs[s].clone(),
);
} else if op.lut == !LogicOp3::SRC_MASKS[s] {
self.add_copy(
bi,
dst,
SrcType::Pred,
lop.srcs[s].clone().bnot(),
);
}
}
}
}
}
Op::Sel(sel) => {
let dst = sel.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
let src = match &sel.srcs {
[z, u] if z.is_zero() && u.is_nonzero() => {
sel.cond.clone().bnot()
}
[u, z] if z.is_zero() && u.is_nonzero() => sel.cond.clone(),
_ => return,
};
self.add_b2i(bi, dst, src);
}
Op::ISetP(isetp) if isetp.set_op.is_trivial(&isetp.accum) => {
let dst = isetp.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
let src = match (&isetp.srcs[0], &isetp.srcs[1]) {
(z, x) | (x, z) if z.is_zero() => x,
_ => return,
};
// -0 = 0
// -x != 0 => x != 0
if !matches!(src.src_mod, SrcMod::None | SrcMod::INeg) {
return;
}
// x op 0
let inverted = match isetp.cmp_op {
IntCmpOp::Eq => true,
IntCmpOp::Ne => false,
_ => return,
};
self.add_i2b(bi, dst, src.clone(), inverted);
}
Op::IAdd2(add) => {
let dst = add.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
if add.srcs[0].is_zero() {
self.add_copy(bi, dst, SrcType::I32, add.srcs[1].clone());
} else if add.srcs[1].is_zero() {
self.add_copy(bi, dst, SrcType::I32, add.srcs[0].clone());
}
}
Op::IAdd3(add) => {
let dst = add.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
let dst = dst[0];
if add.srcs[0].is_zero() {
if add.srcs[1].is_zero() {
self.add_copy(
bi,
dst,
SrcType::I32,
add.srcs[2].clone(),
);
} else if add.srcs[2].is_zero() {
self.add_copy(
bi,
dst,
SrcType::I32,
add.srcs[1].clone(),
);
}
} else if add.srcs[1].is_zero() && add.srcs[2].is_zero() {
self.add_copy(bi, dst, SrcType::I32, add.srcs[0].clone());
}
}
Op::Prmt(prmt) => {
let dst = prmt.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
if let Some(sel) = prmt.get_sel() {
if let Some(imm) = prmt.as_u32() {
self.add_copy(bi, dst[0], SrcType::GPR, imm.into());
} else if sel == PrmtSel(0x3210) {
self.add_copy(
bi,
dst[0],
SrcType::GPR,
prmt.srcs[0].clone(),
);
} else if sel == PrmtSel(0x7654) {
self.add_copy(
bi,
dst[0],
SrcType::GPR,
prmt.srcs[1].clone(),
);
} else {
self.add_prmt(bi, dst[0], sel, prmt.srcs.clone());
}
}
}
Op::R2UR(r2ur) => {
assert!(r2ur.src.is_unmodified());
if r2ur.src.is_uniform() {
let dst = r2ur.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
self.add_copy(bi, dst[0], SrcType::GPR, r2ur.src.clone());
}
}
Op::Copy(copy) => {
let dst = copy.dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
self.add_copy(bi, dst[0], SrcType::GPR, copy.src.clone());
}
Op::ParCopy(pcopy) => {
for (dst, src) in pcopy.dsts_srcs.iter() {
let dst = dst.as_ssa().unwrap();
assert!(dst.comps() == 1);
self.add_copy(bi, dst[0], SrcType::GPR, src.clone());
}
}
_ => (),
}
}
pub fn run(&mut self, f: &mut Function) {
for (bi, b) in f.blocks.iter_mut().enumerate() {
let b_uniform = b.uniform;
for instr in &mut b.instrs {
self.try_add_instr(bi, instr);
self.prop_to_pred(&mut instr.pred);
let cbuf_rule = if self.sm.sm() >= 100 {
// Blackwell+ doesn't allow cbufs directly in instruction
// sources anymore and instead have to be explicitly loaded
// with OpLdc.
CBufRule::No
} else if instr.is_uniform() {
CBufRule::No
} else if !b_uniform {
CBufRule::BindlessRequiresBlock(bi)
} else {
CBufRule::Yes
};
// Carry-out and overflow interact funny with SrcMod::INeg so we
// can only propagate with modifiers if no carry/overflow is
// written.
let force_alu_src_type = match &instr.op {
Op::IAdd2(add) => !add.carry_out.is_none(),
Op::IAdd2X(add) => !add.carry_out.is_none(),
Op::IAdd3(add) => {
!add.overflow[0].is_none() || !add.overflow[1].is_none()
}
Op::IAdd3X(add) => {
!add.overflow[0].is_none() || !add.overflow[1].is_none()
}
Op::Lea(lea) => !lea.overflow.is_none(),
Op::LeaX(lea) => !lea.overflow.is_none(),
_ => false,
};
let src_types = instr.src_types();
let ftz = instr.ftz();
for (i, src) in instr.srcs_mut().iter_mut().enumerate() {
let mut src_type = src_types[i];
if force_alu_src_type {
src_type = match src_type {
SrcType::ALU | SrcType::B32 | SrcType::I32 => {
SrcType::ALU
}
SrcType::Carry | SrcType::Pred => src_type,
_ => panic!("Unhandled src_type"),
};
};
self.prop_to_src(src_type, &cbuf_rule, src, ftz);
}
}
}
}
}
impl Shader<'_> {
pub fn opt_copy_prop(&mut self) {
for f in &mut self.functions {
CopyPropPass::new(self.sm).run(f);
}
}
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use rustc_hash::FxHashSet;
fn opt_crs(f: &mut Function) {
let mut live_targets: FxHashSet<Label> = Default::default();
for b in f.blocks.iter() {
let Some(instr) = b.instrs.last() else {
continue;
};
match &instr.op {
Op::Sync(OpSync { target })
| Op::Brk(OpBrk { target })
| Op::Cont(OpCont { target }) => {
live_targets.insert(*target);
}
_ => (),
}
}
f.map_instrs(|instr, _| match &instr.op {
Op::SSy(OpSSy { target })
| Op::PBk(OpPBk { target })
| Op::PCnt(OpPCnt { target }) => {
if live_targets.contains(target) {
[instr].into()
} else {
[].into()
}
}
_ => [instr].into(),
});
}
impl Shader<'_> {
pub fn opt_crs(&mut self) {
for f in &mut self.functions {
opt_crs(f);
}
}
}
+191
View File
@@ -0,0 +1,191 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::{
api::{GetDebugFlags, DEBUG},
ir::*,
};
use compiler::bitset::BitSet;
struct DeadCodePass {
any_dead: bool,
new_live: bool,
live_ssa: BitSet<SSAValue>,
live_phi: BitSet<Phi>,
}
impl DeadCodePass {
pub fn new() -> DeadCodePass {
DeadCodePass {
any_dead: false,
new_live: false,
live_ssa: Default::default(),
live_phi: Default::default(),
}
}
fn mark_ssa_live(&mut self, ssa: &SSAValue) {
self.new_live |= self.live_ssa.insert(*ssa);
}
fn mark_src_live(&mut self, src: &Src) {
for ssa in src.iter_ssa() {
self.mark_ssa_live(ssa);
}
}
fn mark_phi_live(&mut self, phi: Phi) {
self.new_live |= self.live_phi.insert(phi);
}
fn is_dst_live(&self, dst: &Dst) -> bool {
match dst {
Dst::SSA(ssa) => {
for val in ssa.iter() {
if self.live_ssa.contains(*val) {
return true;
}
}
false
}
Dst::None => false,
_ => panic!("Invalid SSA destination"),
}
}
fn is_phi_live(&self, phi: Phi) -> bool {
self.live_phi.contains(phi)
}
fn is_instr_live(&self, instr: &Instr) -> bool {
if instr.pred.is_false() {
return false;
}
if !instr.can_eliminate() {
return true;
}
for dst in instr.dsts() {
if self.is_dst_live(dst) {
return true;
}
}
false
}
fn mark_instr(&mut self, instr: &Instr) {
match &instr.op {
Op::PhiSrcs(phi) => {
assert!(instr.pred.is_true());
for (phi, src) in phi.srcs.iter() {
if self.is_phi_live(*phi) {
self.mark_src_live(src);
} else {
self.any_dead = true;
}
}
}
Op::PhiDsts(phi) => {
assert!(instr.pred.is_true());
for (phi, dst) in phi.dsts.iter() {
if self.is_dst_live(dst) {
self.mark_phi_live(*phi);
} else {
self.any_dead = true;
}
}
}
Op::ParCopy(pcopy) => {
assert!(instr.pred.is_true());
for (dst, src) in pcopy.dsts_srcs.iter() {
if self.is_dst_live(dst) {
self.mark_src_live(src);
} else {
self.any_dead = true;
}
}
}
_ => {
if self.is_instr_live(instr) {
if let PredRef::SSA(ssa) = &instr.pred.pred_ref {
self.mark_ssa_live(ssa);
}
for src in instr.srcs() {
self.mark_src_live(src);
}
} else {
self.any_dead = true;
}
}
}
}
fn map_instr(&self, mut instr: Instr) -> MappedInstrs {
let is_live = match &mut instr.op {
Op::PhiSrcs(phi) => {
phi.srcs.retain(|phi, _| self.is_phi_live(*phi));
!phi.srcs.is_empty()
}
Op::PhiDsts(phi) => {
phi.dsts.retain(|_, dst| self.is_dst_live(dst));
!phi.dsts.is_empty()
}
Op::ParCopy(pcopy) => {
pcopy.dsts_srcs.retain(|dst, _| self.is_dst_live(dst));
!pcopy.dsts_srcs.is_empty()
}
_ => self.is_instr_live(&instr),
};
if is_live {
[instr].into()
} else {
if DEBUG.annotate() {
let ann = OpAnnotate {
annotation: "killed by dce".into(),
};
[Instr::new(ann)].into()
} else {
[].into()
}
}
}
pub fn run(&mut self, f: &mut Function) {
loop {
self.new_live = false;
self.any_dead = false;
for b in f.blocks.iter().rev() {
for instr in b.instrs.iter().rev() {
self.mark_instr(instr);
}
}
if !self.new_live {
break;
}
}
if self.any_dead {
f.map_instrs(|instr, _| self.map_instr(instr));
}
}
}
impl Function {
pub fn opt_dce(&mut self) {
DeadCodePass::new().run(self);
}
}
impl Shader<'_> {
pub fn opt_dce(&mut self) {
for f in &mut self.functions {
f.opt_dce();
}
}
}
@@ -0,0 +1,465 @@
// Copyright © 2024 Valve Corporation
// SPDX-License-Identifier: MIT
use crate::ir::*;
use compiler::cfg::CFG;
use std::cmp::max;
use std::cmp::Reverse;
pub mod graph {
#[derive(Clone)]
pub struct Edge<EdgeLabel> {
pub label: EdgeLabel,
pub head_idx: usize,
}
#[derive(Clone)]
pub struct Node<NodeLabel, EdgeLabel> {
pub label: NodeLabel,
pub outgoing_edges: Vec<Edge<EdgeLabel>>,
}
#[derive(Clone)]
pub struct Graph<NodeLabel, EdgeLabel> {
pub nodes: Vec<Node<NodeLabel, EdgeLabel>>,
}
impl<NodeLabel, EdgeLabel> Graph<NodeLabel, EdgeLabel> {
pub fn new(node_labels: impl Iterator<Item = NodeLabel>) -> Self {
let nodes = node_labels
.map(|label| Node {
label,
outgoing_edges: Vec::new(),
})
.collect();
Graph { nodes }
}
pub fn add_edge(
&mut self,
tail_idx: usize,
head_idx: usize,
label: EdgeLabel,
) {
assert!(head_idx < self.nodes.len());
self.nodes[tail_idx]
.outgoing_edges
.push(Edge { label, head_idx });
}
pub fn reverse(&mut self) {
let old_edges: Vec<_> = self
.nodes
.iter_mut()
.map(|node| std::mem::take(&mut node.outgoing_edges))
.collect();
for (tail_idx, edges) in old_edges.into_iter().enumerate() {
for e in edges.into_iter() {
self.add_edge(e.head_idx, tail_idx, e.label);
}
}
}
}
}
#[derive(Eq, PartialEq)]
pub enum SideEffect {
/// No side effect (ALU-like)
None,
/// Instruction reads or writes memory
///
/// This will be serialized with respect to other
/// SideEffect::Memory instructions
Memory,
/// This instcuction is a full code motion barrier
///
/// No other instruction will be re-ordered with respect to this one
Barrier,
}
pub fn side_effect_type(op: &Op) -> SideEffect {
match op {
// Float ALU
Op::F2FP(_)
| Op::FAdd(_)
| Op::FFma(_)
| Op::FMnMx(_)
| Op::FMul(_)
| Op::FSet(_)
| Op::FSetP(_)
| Op::HAdd2(_)
| Op::HFma2(_)
| Op::HMul2(_)
| Op::HSet2(_)
| Op::HSetP2(_)
| Op::HMnMx2(_)
| Op::FSwz(_)
| Op::FSwzAdd(_) => SideEffect::None,
// Multi-function unit
Op::Rro(_) | Op::MuFu(_) => SideEffect::None,
// Double-precision float ALU
Op::DAdd(_)
| Op::DFma(_)
| Op::DMnMx(_)
| Op::DMul(_)
| Op::DSetP(_) => SideEffect::None,
// Integer ALU
Op::BRev(_)
| Op::Flo(_)
| Op::PopC(_)
| Op::IMad(_)
| Op::IMul(_)
| Op::BMsk(_)
| Op::IAbs(_)
| Op::IAdd2(_)
| Op::IAdd2X(_)
| Op::IAdd3(_)
| Op::IAdd3X(_)
| Op::IDp4(_)
| Op::IMad64(_)
| Op::IMnMx(_)
| Op::ISetP(_)
| Op::Lea(_)
| Op::LeaX(_)
| Op::Lop2(_)
| Op::Lop3(_)
| Op::SuClamp(_)
| Op::SuBfm(_)
| Op::SuEau(_)
| Op::IMadSp(_)
| Op::Shf(_)
| Op::Shl(_)
| Op::Shr(_)
| Op::Bfe(_) => SideEffect::None,
// Conversions
Op::F2F(_) | Op::F2I(_) | Op::I2F(_) | Op::I2I(_) | Op::FRnd(_) => {
SideEffect::None
}
// Move ops
Op::Mov(_) | Op::Prmt(_) | Op::Sel(_) | Op::Sgxt(_) => SideEffect::None,
Op::Shfl(_) => SideEffect::None,
// Predicate ops
Op::PLop3(_) | Op::PSetP(_) => SideEffect::None,
// Uniform ops
Op::R2UR(_) | Op::Redux(_) => SideEffect::None,
// Texture ops
Op::Tex(_)
| Op::Tld(_)
| Op::Tld4(_)
| Op::Tmml(_)
| Op::Txd(_)
| Op::Txq(_) => SideEffect::Memory,
// Surface ops
Op::SuLd(_)
| Op::SuSt(_)
| Op::SuAtom(_)
| Op::SuLdGa(_)
| Op::SuStGa(_) => SideEffect::Memory,
// Memory ops
Op::Ipa(_) | Op::Ldc(_) => SideEffect::None,
Op::Ld(_)
| Op::Ldsm(_)
| Op::LdSharedLock(_)
| Op::St(_)
| Op::StSCheckUnlock(_)
| Op::Atom(_)
| Op::AL2P(_)
| Op::ALd(_)
| Op::ASt(_)
| Op::CCtl(_)
| Op::LdTram(_)
| Op::MemBar(_) => SideEffect::Memory,
// Matrix ops
Op::Imma(_) | Op::Hmma(_) | Op::Movm(_) => SideEffect::None,
// Control-flow ops
Op::BClear(_)
| Op::Break(_)
| Op::BSSy(_)
| Op::BSync(_)
| Op::SSy(_)
| Op::Sync(_)
| Op::Brk(_)
| Op::PBk(_)
| Op::Cont(_)
| Op::PCnt(_)
| Op::Bra(_)
| Op::Exit(_)
| Op::WarpSync(_) => SideEffect::Barrier,
// We don't model the barrier register yet, so serialize these
Op::BMov(_) => SideEffect::Memory,
// Geometry ops
Op::Out(_) | Op::OutFinal(_) => SideEffect::Barrier,
// Miscellaneous ops
Op::Bar(_)
| Op::TexDepBar(_)
| Op::CS2R(_)
| Op::Isberd(_)
| Op::Isbewr(_)
| Op::ViLd(_)
| Op::Kill(_)
| Op::S2R(_) => SideEffect::Barrier,
Op::PixLd(_) | Op::Vote(_) | Op::Match(_) => SideEffect::None,
Op::Nop(OpNop { label, .. }) => {
if label.is_none() {
SideEffect::None
} else {
SideEffect::Barrier
}
}
// Virtual ops
Op::Annotate(_)
| Op::ParCopy(_)
| Op::Swap(_)
| Op::Copy(_)
| Op::Undef(_) => SideEffect::None,
Op::SrcBar(_)
| Op::Pin(_)
| Op::Unpin(_)
| Op::PhiSrcs(_)
| Op::PhiDsts(_)
| Op::RegOut(_) => SideEffect::Barrier,
}
}
pub fn estimate_block_weight(cfg: &CFG<BasicBlock>, block_idx: usize) -> u64 {
let loop_depth = cfg.loop_depth(block_idx) as f32;
10_f32.powf((loop_depth + 1.0).log2()) as u64
}
/// Try to guess how many cycles a variable latency instruction will take
///
/// These values are based on the cycle estimates from ["Dissecting the NVidia
/// Turing T4 GPU via Microbenchmarking"](https://arxiv.org/pdf/1903.07486).
/// Memory instructions were copied from L1 data cache latencies.
/// For instructions not mentioned in the paper, I made up numbers.
/// This could probably be improved.
pub fn estimate_variable_latency(sm: &ShaderModelInfo, op: &Op) -> u32 {
if !sm.op_needs_scoreboard(op) {
return 0;
}
match op {
// Multi-function unit
Op::Rro(_) | Op::MuFu(_) => 15,
// Double-precision float ALU
Op::DFma(_) | Op::DSetP(_) => 54,
Op::DAdd(_) | Op::DMnMx(_) | Op::DMul(_) => 48,
// Integer ALU
Op::BRev(_) | Op::Flo(_) | Op::PopC(_) => 15,
Op::IMad(_) | Op::IMul(_) => {
assert!(sm.sm() < 70);
86
}
// Conversions
Op::F2F(_) | Op::F2I(_) | Op::I2F(_) | Op::I2I(_) | Op::FRnd(_) => 15,
// Move ops
Op::Shfl(_) => 15,
// Uniform ops
Op::Redux(_) | Op::R2UR(_) => 15,
// Texture ops
Op::Tex(_)
| Op::Tld(_)
| Op::Tld4(_)
| Op::Tmml(_)
| Op::Txd(_)
| Op::Txq(_) => 32,
// Surface ops
Op::SuLd(_)
| Op::SuSt(_)
| Op::SuAtom(_)
| Op::SuLdGa(_)
| Op::SuStGa(_) => 32,
// Memory ops
Op::Ldc(_) => 4,
Op::Ld(_)
| Op::Ldsm(_)
| Op::Movm(_)
| Op::LdSharedLock(_)
| Op::St(_)
| Op::StSCheckUnlock(_)
| Op::Atom(_)
| Op::AL2P(_)
| Op::ALd(_)
| Op::ASt(_)
| Op::Ipa(_)
| Op::CCtl(_)
| Op::LdTram(_)
| Op::MemBar(_) => 32,
// Control-flow ops
Op::WarpSync(_) => 16,
// Barrier
Op::BMov(_) => 16,
// Geometry ops
Op::Out(_) | Op::OutFinal(_) => 2,
// Miscellaneous ops
Op::Bar(_)
| Op::TexDepBar(_)
| Op::CS2R(_)
| Op::Isberd(_)
| Op::Isbewr(_)
| Op::ViLd(_)
| Op::Kill(_)
| Op::PixLd(_)
| Op::S2R(_)
| Op::Match(_) => 16,
Op::Hmma(_) | Op::Imma(_) => 22,
_ => panic!("Unknown variable latency op {op}"),
}
}
#[derive(Default, Clone)]
pub struct NodeLabel {
pub cycles_to_end: u32,
pub num_uses: u32,
/// The first cycle that the instruction can begin executing
pub ready_cycle: u32,
pub exec_latency: u32,
}
pub struct EdgeLabel {
pub latency: u32,
}
pub type DepGraph = graph::Graph<NodeLabel, EdgeLabel>;
pub fn calc_statistics(g: &mut DepGraph) -> Vec<usize> {
let mut initial_ready_list = Vec::new();
for i in (0..g.nodes.len()).rev() {
let node = &g.nodes[i];
let mut max_delay = 0;
for edge in &node.outgoing_edges {
assert!(edge.head_idx > i);
max_delay = max(
max_delay,
g.nodes[edge.head_idx].label.cycles_to_end + edge.label.latency,
);
}
let node = &mut g.nodes[i];
node.label.cycles_to_end = max_delay;
node.label.num_uses = node.outgoing_edges.len().try_into().unwrap();
if node.label.num_uses == 0 {
initial_ready_list.push(i);
}
}
initial_ready_list
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ReadyInstr {
cycles_to_end: u32,
// We use the original instruction order as a final tie-breaker, the idea
// being that the original schedule is often not too bad. Since we're
// iterating in reverse order, that means scheduling the largest instruciton
// index first.
pub index: usize,
}
impl ReadyInstr {
pub fn new<E>(g: &graph::Graph<NodeLabel, E>, i: usize) -> Self {
let label = &g.nodes[i].label;
// NodeLabel::cycles_to_end is cycles from the beginning of the
// instruction to the top of the block, but we want from the end of the
// instruction to the top of the block
let cycles_to_end = label.cycles_to_end + label.exec_latency;
ReadyInstr {
cycles_to_end,
index: i,
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FutureReadyInstr {
/// The first cycle that the instruction can end executing
pub ready_cycle: Reverse<u32>,
pub index: usize,
}
impl FutureReadyInstr {
pub fn new<E>(g: &graph::Graph<NodeLabel, E>, i: usize) -> Self {
let label = &g.nodes[i].label;
// NodeLabel::ready_cycle is the earliest beginning cycle for the
// instruction, but we need the earliest end cycle for the instruction.
let ready_cycle = label.ready_cycle.saturating_sub(label.exec_latency);
FutureReadyInstr {
ready_cycle: Reverse(ready_cycle),
index: i,
}
}
}
#[allow(dead_code)]
pub fn save_graphviz(
instrs: &[Box<Instr>],
g: &DepGraph,
) -> std::io::Result<()> {
// dot /tmp/instr_dep_graph.dot -Tsvg > /tmp/instr_dep_graph.svg
use std::fs::File;
use std::io::{BufWriter, Write};
let file = File::create("/tmp/instr_dep_graph.dot")?;
let mut w = BufWriter::new(file);
writeln!(w, "digraph {{")?;
for (i, instr) in instrs.iter().enumerate() {
let l = &g.nodes[i].label;
writeln!(
w,
" {i} [label=\"{}\\n{}, {}\"];",
instr, l.cycles_to_end, l.num_uses
)?;
}
for (i, node) in g.nodes.iter().enumerate() {
for j in &node.outgoing_edges {
writeln!(
w,
" {i} -> {} [label=\"{}\"];",
j.head_idx, j.label.latency
)?;
}
}
writeln!(w, "}}")?;
w.flush()?;
Ok(())
}
@@ -0,0 +1,256 @@
// Copyright © 2024 Valve Corporation
// SPDX-License-Identifier: MIT
use crate::ir::*;
use crate::opt_instr_sched_common::*;
use crate::reg_tracker::RegRefIterable;
use crate::reg_tracker::RegTracker;
use std::cmp::max;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
struct RegUse<T: Clone> {
reads: Vec<T>,
write: Option<T>,
}
impl<T: Clone> RegUse<T> {
pub fn new() -> Self {
RegUse {
reads: Vec::new(),
write: None,
}
}
pub fn add_read(&mut self, dep: T) {
self.reads.push(dep);
}
pub fn set_write(&mut self, dep: T) {
self.write = Some(dep);
self.reads.clear();
}
}
fn generate_dep_graph(sm: &ShaderModelInfo, instrs: &[Instr]) -> DepGraph {
let mut g = DepGraph::new((0..instrs.len()).map(|_| Default::default()));
// Maps registers to RegUse<ip, src_dst_idx>. Predicates are
// represented by src_idx = usize::MAX.
let mut uses: Box<RegTracker<RegUse<(usize, usize)>>> =
Box::new(RegTracker::new_with(&|| RegUse::new()));
let mut last_memory_op = None;
let mut last_barrier_op = None;
for ip in (0..instrs.len()).rev() {
let instr = &instrs[ip];
if let Some(bar_ip) = last_barrier_op {
g.add_edge(ip, bar_ip, EdgeLabel { latency: 0 });
}
match side_effect_type(&instr.op) {
SideEffect::None => (),
SideEffect::Barrier => {
let last_ip = last_barrier_op.unwrap_or(instrs.len());
for other_ip in (ip + 1)..last_ip {
g.add_edge(ip, other_ip, EdgeLabel { latency: 0 });
}
last_barrier_op = Some(ip);
}
SideEffect::Memory => {
if let Some(mem_ip) = last_memory_op {
g.add_edge(ip, mem_ip, EdgeLabel { latency: 0 });
}
last_memory_op = Some(ip);
}
}
uses.for_each_instr_dst_mut(instr, |i, u| {
if let Some((w_ip, w_dst_idx)) = u.write {
let mut latency = sm.waw_latency(
&instr.op,
i,
!instr.pred.pred_ref.is_none(),
&instrs[w_ip].op,
w_dst_idx,
);
if sm.op_needs_scoreboard(&instr.op) {
// Barriers take two cycles to become active
latency = max(latency, 2);
}
g.add_edge(ip, w_ip, EdgeLabel { latency });
}
for &(r_ip, r_src_idx) in &u.reads {
let mut latency = if r_src_idx == usize::MAX {
sm.paw_latency(&instr.op, i)
} else {
sm.raw_latency(&instr.op, i, &instrs[r_ip].op, r_src_idx)
};
latency =
max(latency, estimate_variable_latency(sm, &instr.op));
g.add_edge(ip, r_ip, EdgeLabel { latency });
}
});
uses.for_each_instr_src_mut(instr, |i, u| {
if let Some((w_ip, w_dst_idx)) = u.write {
let mut latency =
sm.war_latency(&instr.op, i, &instrs[w_ip].op, w_dst_idx);
if sm.op_needs_scoreboard(&instr.op) {
// Barriers take two cycles to become active
latency = max(latency, 2);
}
g.add_edge(ip, w_ip, EdgeLabel { latency });
}
});
// We're iterating in reverse, so writes are logically first
uses.for_each_instr_dst_mut(instr, |i, c| {
c.set_write((ip, i));
});
uses.for_each_instr_pred_mut(instr, |c| {
c.add_read((ip, usize::MAX));
});
uses.for_each_instr_src_mut(instr, |i, c| {
c.add_read((ip, i));
});
// Initialize this node's distance to the end
let mut ready_cycle = (0..instr.dsts().len())
.map(|i| sm.worst_latency(&instr.op, i))
.max()
.unwrap_or(0);
let var_latency = estimate_variable_latency(sm, &instr.op)
+ sm.exec_latency(&instrs[instrs.len() - 1].op);
ready_cycle = max(ready_cycle, var_latency);
let label = &mut g.nodes[ip].label;
label.exec_latency = sm.exec_latency(&instr.op);
label.ready_cycle = ready_cycle;
}
g
}
fn generate_order(
g: &mut DepGraph,
init_ready_list: Vec<usize>,
) -> (Vec<usize>, u64) {
let mut ready_instrs: BinaryHeap<ReadyInstr> = BinaryHeap::new();
let mut future_ready_instrs: BinaryHeap<FutureReadyInstr> = init_ready_list
.into_iter()
.map(|i| FutureReadyInstr::new(g, i))
.collect();
let mut current_cycle = 0u32;
let mut instr_order = Vec::with_capacity(g.nodes.len());
loop {
// Move ready instructions to the ready list
loop {
let Some(fri) = future_ready_instrs.peek() else {
break;
};
if current_cycle < fri.ready_cycle.0 {
break;
}
ready_instrs.push(ReadyInstr::new(g, fri.index));
future_ready_instrs.pop();
}
// Pick a ready instruction
let next_idx = match ready_instrs.pop() {
None => match future_ready_instrs.peek() {
None => break, // Both lists are empty. We're done!
Some(&FutureReadyInstr {
ready_cycle: Reverse(ready_cycle),
..
}) => {
// Fast-forward time to when the next instr is ready
assert!(ready_cycle > current_cycle);
current_cycle = ready_cycle;
continue;
}
},
Some(ReadyInstr { index, .. }) => index,
};
// Schedule the instuction
instr_order.push(next_idx);
current_cycle += g.nodes[next_idx].label.exec_latency;
let outgoing_edges =
std::mem::take(&mut g.nodes[next_idx].outgoing_edges);
for edge in outgoing_edges.into_iter() {
let dep_instr = &mut g.nodes[edge.head_idx].label;
dep_instr.ready_cycle =
max(dep_instr.ready_cycle, current_cycle + edge.label.latency);
dep_instr.num_uses -= 1;
if dep_instr.num_uses == 0 {
future_ready_instrs
.push(FutureReadyInstr::new(g, edge.head_idx));
}
}
}
(instr_order, u64::from(current_cycle))
}
fn sched_buffer(
sm: &ShaderModelInfo,
instrs: Vec<Instr>,
) -> (impl Iterator<Item = Instr> + use<>, u64) {
let mut g = generate_dep_graph(sm, &instrs);
let init_ready_list = calc_statistics(&mut g);
// save_graphviz(&instrs, &g).unwrap();
g.reverse();
let (new_order, cycle_count) = generate_order(&mut g, init_ready_list);
// Apply the new instruction order
let mut instrs: Vec<Option<Instr>> = instrs.into_iter().map(Some).collect();
let instrs = new_order.into_iter().rev().map(move |i| {
std::mem::take(&mut instrs[i]).expect("Instruction scheduled twice")
});
(instrs, cycle_count)
}
impl Function {
pub fn opt_instr_sched_postpass(&mut self, sm: &ShaderModelInfo) -> u64 {
let mut num_static_cycles = 0u64;
for i in 0..self.blocks.len() {
let block = &mut self.blocks[i];
let orig_instr_count = block.instrs.len();
let instrs = std::mem::take(&mut block.instrs);
let (instrs, cycle_count) = sched_buffer(sm, instrs);
block.instrs = instrs.collect();
assert_eq!(orig_instr_count, block.instrs.len());
let block_weight = estimate_block_weight(&self.blocks, i);
num_static_cycles = cycle_count
.checked_mul(block_weight)
.expect("Cycle count estimate overflow")
.checked_add(num_static_cycles)
.expect("Cycle count estimate overflow");
}
num_static_cycles
}
}
impl Shader<'_> {
/// Post-RA instruction scheduling
///
/// Uses the popular latency-weighted-depth heuristic.
/// See eg. Cooper & Torczon's "Engineering A Compiler", 3rd ed.
/// Chapter 12.3 "Local scheduling"
pub fn opt_instr_sched_postpass(&mut self) {
self.info.num_static_cycles = 0;
for f in &mut self.functions {
self.info.num_static_cycles += f.opt_instr_sched_postpass(self.sm);
}
}
}
File diff suppressed because it is too large. Load diff
+175
View File
@@ -0,0 +1,175 @@
// Copyright © 2023 Mel Henning
// SPDX-License-Identifier: MIT
use crate::ir::*;
use compiler::cfg::CFGBuilder;
use rustc_hash::{FxBuildHasher, FxHashMap};
fn clone_branch(op: &Op) -> Op {
match op {
Op::Bra(b) => {
assert!(b.cond.is_true());
Op::Bra(b.clone())
}
Op::Exit(e) => Op::Exit(e.clone()),
_ => unreachable!(),
}
}
fn jump_thread(func: &mut Function) -> bool {
// Let's call a basic block "trivial" if its only instruction is an
// unconditional branch. If a block is trivial, we can update all of its
// predecessors to jump to its sucessor.
//
// A single reverse pass over the basic blocks is enough to update all of
// the edges we're interested in. Roughly, if we assume that all loops in
// the shader can terminate, then loop heads are never trivial and we
// never replace a backward edge. Therefore, in each step we only need to
// make sure that later control flow has been replaced in order to update
// the current block as much as possible.
//
// We additionally try to update a branch-to-empty-block to point to the
// block's successor, which along with block dce/reordering can sometimes
// enable a later optimization that converts branches to fallthrough.
let mut progress = false;
// A branch to label can be replaced with Op
let mut replacements: FxHashMap<Label, Op> = Default::default();
// Invariant 1: At the end of each loop iteration,
// every trivial block with an index in [i, blocks.len())
// is represented in replacements.keys()
// Invariant 2: replacements.values() never contains
// a branch to a trivial block
for i in (0..func.blocks.len()).rev() {
// Replace the branch if possible
if let Some(instr) = func.blocks[i].instrs.last_mut() {
if let Op::Bra(bra) = &mut instr.op {
if let Some(replacement) = replacements.get(&bra.target) {
if bra.cond.is_true() {
// Unconditional branches can just be replaced
instr.op = clone_branch(replacement);
progress = true;
} else {
// OpExit has a form that takes an input predicate but it
// doesn't support upred so there's nothing we can do here.
// TODO: however we could do something for the case OpBra has a
// non uniform input predicate.
if let Op::Bra(replacement) = replacement {
bra.target = replacement.target;
progress = true;
}
}
}
// If the branch target was previously a trivial block then the
// branch was previously a forward edge (see above) and by
// invariants 1 and 2 we just updated the branch to target
// a nontrivial block
}
}
// Is this block trivial?
let block_label = func.blocks[i].label;
match &func.blocks[i].instrs[..] {
[instr] => {
if instr.is_branch_always_taken() {
// Upholds invariant 2 because we updated the branch above
replacements.insert(block_label, clone_branch(&instr.op));
}
}
[] => {
// Empty block - falls through
// Our successor might be trivial, so we need to
// apply the rewrite map to uphold invariant 2
let target_label = func.blocks[i + 1].label;
let replacement = replacements
.get(&target_label)
.map(clone_branch)
.unwrap_or_else(|| {
Op::Bra(
OpBra {
target: target_label,
cond: true.into(),
}
.into(),
)
});
replacements.insert(block_label, replacement);
}
_ => (),
}
}
if progress {
// We don't update the CFG above, so rewrite it if we made progress
rewrite_cfg(func);
}
progress
}
fn rewrite_cfg(func: &mut Function) {
// CFGBuilder takes care of removing dead blocks for us
// We use the basic block's label to identify it
let mut builder = CFGBuilder::<_, _, FxBuildHasher>::new();
for i in 0..func.blocks.len() {
let block = &func.blocks[i];
// Note: fall-though must be first edge
if block.falls_through() {
let next_block = &func.blocks[i + 1];
builder.add_edge(block.label, next_block.label);
}
if let Some(control_flow) = block.branch() {
match &control_flow.op {
Op::Bra(bra) => {
builder.add_edge(block.label, bra.target);
}
Op::Exit(_) => (),
_ => unreachable!(),
};
}
}
for block in func.blocks.drain() {
builder.add_node(block.label, block);
}
let _ = std::mem::replace(&mut func.blocks, builder.as_cfg());
}
/// Replace jumps to the following block with fall-through
fn opt_fall_through(func: &mut Function) {
for i in 0..func.blocks.len() - 1 {
let remove_last_instr = match func.blocks[i].branch() {
Some(b) => match &b.op {
Op::Bra(bra) => bra.target == func.blocks[i + 1].label,
_ => false,
},
None => false,
};
if remove_last_instr {
func.blocks[i].instrs.pop();
}
}
}
impl Function {
pub fn opt_jump_thread(&mut self) {
if jump_thread(self) {
opt_fall_through(self);
}
}
}
impl Shader<'_> {
/// A simple jump threading pass
///
/// Note that this can introduce critical edges, so it cannot be run before RA
pub fn opt_jump_thread(&mut self) {
for f in &mut self.functions {
f.opt_jump_thread();
}
}
}
+266
View File
@@ -0,0 +1,266 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use rustc_hash::FxHashMap;
use std::slice;
struct LopEntry {
op: LogicOp3,
srcs_used: u8,
srcs: [Src; 3],
}
struct LopPass {
use_counts: FxHashMap<SSAValue, u32>,
ssa_lop: FxHashMap<SSAValue, LopEntry>,
}
fn src_as_bool(src: &Src) -> Option<bool> {
assert!(src.is_unmodified());
match src.src_ref {
SrcRef::Zero | SrcRef::False | SrcRef::Imm32(0) => Some(false),
SrcRef::True | SrcRef::Imm32(u32::MAX) => Some(true),
_ => None,
}
}
impl LopPass {
fn new(f: &Function) -> LopPass {
let mut use_counts = FxHashMap::default();
for b in &f.blocks {
for instr in &b.instrs {
if let PredRef::SSA(ssa) = instr.pred.pred_ref {
use_counts.entry(ssa).and_modify(|e| *e += 1).or_insert(1);
}
for src in instr.srcs() {
if let SrcRef::SSA(vec) = &src.src_ref {
for ssa in vec.iter() {
use_counts
.entry(*ssa)
.and_modify(|e| *e += 1)
.or_insert(1);
}
}
}
}
}
LopPass {
use_counts: use_counts,
ssa_lop: Default::default(),
}
}
fn add_lop(&mut self, ssa: SSAValue, op: LogicOp3, srcs: [Src; 3]) {
let mut srcs_used = 0;
for i in 0..3 {
if op.src_used(i) {
srcs_used |= 1 << i;
assert!(src_as_bool(&srcs[i]).is_none());
}
}
let entry = LopEntry {
op: op,
srcs_used: srcs_used,
srcs: srcs,
};
self.ssa_lop.insert(ssa, entry);
}
fn dedup_srcs(&self, op: &mut LogicOp3, srcs: &[Src; 3]) {
for i in 0..2 {
for j in (i + 1)..3 {
if srcs[i].src_ref == srcs[j].src_ref {
assert!(srcs[i].is_unmodified());
assert!(srcs[j].is_unmodified());
*op = LogicOp3::new_lut(&|x, y, z| {
let mut s = [x, y, z];
s[j] = s[i];
op.eval(s[0], s[1], s[2])
});
}
}
}
}
fn try_prop_to_src(
&self,
ops: &mut [LogicOp3],
srcs: &mut [Src; 3],
src_idx: usize,
) {
loop {
assert!(srcs[src_idx].is_unmodified());
let ssa = match &srcs[src_idx].src_ref {
SrcRef::SSA(vec) => {
assert!(vec.comps() == 1);
vec[0]
}
_ => return,
};
let Some(entry) = self.ssa_lop.get(&ssa) else {
return;
};
let entry_use_count = *self.use_counts.get(&ssa).unwrap();
if entry.srcs_used.count_ones() > 1 && entry_use_count > 1 {
return;
}
let mut entry_srcs = [usize::MAX; 3];
let mut next_src = 0_usize;
for i in 0..3 {
if entry.srcs_used & (1 << i) == 0 {
continue;
}
let mut found = false;
for j in 0..3 {
if entry.srcs[i].src_ref == srcs[j].src_ref {
entry_srcs[i] = j;
found = true;
break;
}
}
if found {
continue;
}
loop {
if next_src >= srcs.len() {
return;
}
// All callers of this function need to ensure that
// constant sources are already folded so we know we
// can always re-use them.
if next_src == src_idx
|| src_as_bool(&srcs[next_src]).is_some()
{
entry_srcs[i] = next_src;
next_src += 1;
break;
}
next_src += 1;
}
}
// Clear out the propagated source. What we put here doesn't matter
// since it's no longer used. It may be overwritten by one of the
// entry sources but there is no guarantee of this.
srcs[src_idx] = match ssa.file() {
RegFile::GPR | RegFile::UGPR => SrcRef::Zero.into(),
RegFile::Pred | RegFile::UPred => SrcRef::True.into(),
RegFile::Carry | RegFile::Bar | RegFile::Mem => {
panic!("Not a normal register");
}
};
for i in 0..3 {
if entry_srcs[i] != usize::MAX {
srcs[entry_srcs[i]] = entry.srcs[i].clone();
}
}
for op in ops.iter_mut() {
*op = LogicOp3::new_lut(&|x, y, z| {
let mut s = [x, y, z];
let mut es = [0; 3];
for i in 0..3 {
if entry_srcs[i] != usize::MAX {
es[i] = s[entry_srcs[i]];
}
}
let e = entry.op.eval(es[0], es[1], es[2]);
s[src_idx] = e;
op.eval(s[0], s[1], s[2])
});
}
}
}
fn opt_lop3(&mut self, op: &mut OpLop3) {
self.dedup_srcs(&mut op.op, &op.srcs);
for (i, src) in op.srcs.iter_mut().enumerate() {
assert!(src.is_unmodified());
if let Some(b) = src_as_bool(src) {
op.op.fix_src(i, b);
}
if !op.op.src_used(i) {
// Replace unused sources with RZ
*src = SrcRef::Zero.into();
}
}
for i in 0..3 {
self.try_prop_to_src(slice::from_mut(&mut op.op), &mut op.srcs, i);
}
if let Dst::SSA(ssa) = &op.dst {
assert!(ssa.comps() == 1);
self.add_lop(ssa[0], op.op, op.srcs.clone());
}
}
fn opt_plop3(&mut self, op: &mut OpPLop3) {
for (i, src) in op.srcs.iter_mut().enumerate() {
if src.src_mod.is_bnot() {
op.ops[0].invert_src(i);
op.ops[1].invert_src(i);
src.src_mod = SrcMod::None;
}
}
self.dedup_srcs(&mut op.ops[0], &op.srcs);
self.dedup_srcs(&mut op.ops[1], &op.srcs);
// Replace unused sources with PT
for (i, src) in op.srcs.iter_mut().enumerate() {
if let Some(b) = src_as_bool(src) {
op.ops[0].fix_src(i, b);
op.ops[1].fix_src(i, b);
}
if !op.ops[0].src_used(i) && !op.ops[1].src_used(i) {
*src = SrcRef::True.into();
}
}
for i in 0..3 {
self.try_prop_to_src(&mut op.ops, &mut op.srcs, i);
}
for i in 0..2 {
if let Dst::SSA(ssa) = &op.dsts[i] {
assert!(ssa.comps() == 1);
self.add_lop(ssa[0], op.ops[i], op.srcs.clone());
}
}
}
fn run(&mut self, f: &mut Function) {
for b in &mut f.blocks {
for instr in &mut b.instrs {
match &mut instr.op {
Op::Lop3(op) => self.opt_lop3(op),
Op::PLop3(op) => self.opt_plop3(op),
_ => (),
}
}
}
}
}
impl Shader<'_> {
pub fn opt_lop(&mut self) {
for f in &mut self.functions {
let mut pass = LopPass::new(f);
pass.run(f);
}
}
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::{
api::{GetDebugFlags, DEBUG},
ir::*,
};
fn try_combine_outs(emit: &mut Instr, cut: &Instr) -> bool {
let Op::Out(emit) = &mut emit.op else {
return false;
};
let Op::Out(cut) = &cut.op else {
return false;
};
if emit.out_type != OutType::Emit || cut.out_type != OutType::Cut {
return false;
}
let Some(handle) = emit.dst.as_ssa() else {
return false;
};
if cut.handle.as_ssa() != Some(handle) {
return false;
}
if emit.stream != cut.stream {
return false;
}
emit.dst = cut.dst.clone();
emit.out_type = OutType::EmitThenCut;
true
}
impl Shader<'_> {
pub fn opt_out(&mut self) {
if !matches!(self.info.stage, ShaderStageInfo::Geometry(_)) {
return;
}
for f in &mut self.functions {
for b in &mut f.blocks {
let mut instrs = Vec::new();
for instr in b.instrs.drain(..) {
if let Some(prev) = instrs.last_mut() {
if try_combine_outs(prev, &instr) {
if DEBUG.annotate() {
instrs.push(Instr::new(OpAnnotate {
annotation: "combined by opt_out".into(),
}));
}
continue;
}
}
instrs.push(instr);
}
b.instrs = instrs;
}
}
}
}
+284
View File
@@ -0,0 +1,284 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
use crate::ir::*;
use rustc_hash::FxHashMap;
struct PrmtSrcs {
srcs: [SrcRef; 2],
num_srcs: usize,
imm_src: usize,
num_imm_bytes: usize,
}
impl PrmtSrcs {
fn new() -> PrmtSrcs {
PrmtSrcs {
srcs: [SrcRef::Zero, SrcRef::Zero],
num_srcs: 0,
imm_src: usize::MAX,
num_imm_bytes: 0,
}
}
fn try_add_src(&mut self, src: &SrcRef) -> Option<usize> {
for i in 0..self.num_srcs {
if self.srcs[i] == *src {
return Some(i);
}
}
if self.num_srcs < 2 {
let i = self.num_srcs;
self.num_srcs += 1;
self.srcs[i] = src.clone();
Some(i)
} else {
None
}
}
fn try_add_imm_u8(&mut self, u: u8) -> Option<usize> {
if self.imm_src == usize::MAX {
if self.num_srcs >= 2 {
return None;
}
self.imm_src = self.num_srcs;
self.num_srcs += 1;
}
match &mut self.srcs[self.imm_src] {
SrcRef::Zero => {
if u == 0 {
// Common case, just leave it as a SrcRef::Zero
debug_assert!(self.num_imm_bytes <= 1);
self.num_imm_bytes = 1;
Some(0)
} else {
let b = self.num_imm_bytes;
self.num_imm_bytes += 1;
let imm = u32::from(u) << (b * 8);
self.srcs[self.imm_src] = SrcRef::Imm32(imm);
Some(b)
}
}
SrcRef::Imm32(imm) => {
let b = self.num_imm_bytes;
self.num_imm_bytes += 1;
*imm |= u32::from(u) << (b * 8);
Some(b)
}
_ => panic!("We said this was the imm src"),
}
}
}
struct PrmtEntry {
sel: PrmtSel,
srcs: [SrcRef; 2],
}
struct PrmtPass {
ssa_prmt: FxHashMap<SSAValue, PrmtEntry>,
}
impl PrmtPass {
fn new() -> PrmtPass {
PrmtPass {
ssa_prmt: Default::default(),
}
}
fn add_prmt(&mut self, op: &OpPrmt) {
let Dst::SSA(dst_ssa) = &op.dst else {
return;
};
debug_assert!(dst_ssa.comps() == 1);
let dst_ssa = dst_ssa[0];
let Some(sel) = op.get_sel() else {
return;
};
debug_assert!(op.srcs[0].is_unmodified());
debug_assert!(op.srcs[1].is_unmodified());
let srcs = [op.srcs[0].src_ref.clone(), op.srcs[1].src_ref.clone()];
self.ssa_prmt.insert(dst_ssa, PrmtEntry { sel, srcs });
}
fn get_prmt(&self, ssa: &SSAValue) -> Option<&PrmtEntry> {
self.ssa_prmt.get(ssa)
}
fn get_prmt_for_src(&self, src: &Src) -> Option<&PrmtEntry> {
debug_assert!(src.is_unmodified());
if let SrcRef::SSA(vec) = &src.src_ref {
debug_assert!(vec.comps() == 1);
self.get_prmt(&vec[0])
} else {
None
}
}
/// Try to optimize for the OpPrmt of OpPrmt case where only one source of
/// the inner OpPrmt is used
fn try_opt_prmt_src(&mut self, op: &mut OpPrmt, src_idx: usize) -> bool {
let Some(op_sel) = op.get_sel() else {
return false;
};
let Some(src_prmt) = self.get_prmt_for_src(&op.srcs[src_idx]) else {
return false;
};
let mut new_sel = [PrmtSelByte::INVALID; 4];
let mut src_prmt_src = usize::MAX;
for i in 0..4 {
let op_sel_byte = op_sel.get(i);
if op_sel_byte.src() != src_idx {
new_sel[i] = op_sel_byte;
continue;
}
let src_sel_byte = src_prmt.sel.get(op_sel_byte.byte());
if src_prmt_src != usize::MAX && src_prmt_src != src_sel_byte.src()
{
return false;
}
src_prmt_src = src_sel_byte.src();
new_sel[i] = PrmtSelByte::new(
src_idx,
src_sel_byte.byte(),
op_sel_byte.msb() | src_sel_byte.msb(),
);
}
let new_sel = PrmtSel::new(new_sel);
op.sel = new_sel.into();
if src_prmt_src == usize::MAX {
// This source is unused
op.srcs[src_idx] = 0.into();
} else {
op.srcs[src_idx] = src_prmt.srcs[src_prmt_src].clone().into();
}
true
}
/// Try to optimize for the OpPrmt of OpPrmt case as if we're considering a
/// full 4-way OpPrmt in which some sources may be duplicates
fn try_opt_prmt4(&mut self, op: &mut OpPrmt) -> bool {
let Some(op_sel) = op.get_sel() else {
return false;
};
let mut srcs = PrmtSrcs::new();
let mut new_sel = [PrmtSelByte::INVALID; 4];
for i in 0..4 {
let op_sel_byte = op_sel.get(i);
let src = &op.srcs[op_sel_byte.src()];
if let Some(src_prmt) = self.get_prmt_for_src(src) {
let src_sel_byte = src_prmt.sel.get(op_sel_byte.byte());
let src_prmt_src = &src_prmt.srcs[src_sel_byte.src()];
if let Some(u) = src_prmt_src.as_u32() {
let mut imm_u8 = src_sel_byte.fold_u32(u);
if op_sel_byte.msb() {
imm_u8 = ((imm_u8 as i8) >> 7) as u8;
}
let Some(byte_idx) = srcs.try_add_imm_u8(imm_u8) else {
return false;
};
new_sel[i] =
PrmtSelByte::new(srcs.imm_src, byte_idx, false);
} else {
let Some(src_idx) = srcs.try_add_src(src_prmt_src) else {
return false;
};
new_sel[i] = PrmtSelByte::new(
src_idx,
src_sel_byte.byte(),
op_sel_byte.msb() | src_sel_byte.msb(),
);
}
} else if let Some(u) = src.as_u32(SrcType::ALU) {
let imm_u8 = op_sel_byte.fold_u32(u);
let Some(byte_idx) = srcs.try_add_imm_u8(imm_u8) else {
return false;
};
new_sel[i] = PrmtSelByte::new(srcs.imm_src, byte_idx, false);
} else {
debug_assert!(src.is_unmodified());
let Some(src_idx) = srcs.try_add_src(&src.src_ref) else {
return false;
};
new_sel[i] = PrmtSelByte::new(
src_idx,
op_sel_byte.byte(),
op_sel_byte.msb(),
);
}
}
let new_sel = PrmtSel::new(new_sel);
if new_sel == op_sel
&& srcs.srcs[0] == op.srcs[0].src_ref
&& srcs.srcs[1] == op.srcs[1].src_ref
{
return false;
}
op.sel = new_sel.into();
let [srcs0, srcs1] = srcs.srcs;
op.srcs[0] = srcs0.into();
op.srcs[1] = srcs1.into();
true
}
fn opt_prmt(&mut self, op: &mut OpPrmt) {
for i in 0..2 {
loop {
if !self.try_opt_prmt_src(op, i) {
break;
}
}
}
loop {
if !self.try_opt_prmt4(op) {
break;
}
}
self.add_prmt(op);
}
fn run(&mut self, f: &mut Function) {
for b in &mut f.blocks {
for instr in &mut b.instrs {
if let Op::Prmt(op) = &mut instr.op {
self.opt_prmt(op);
}
}
}
}
}
impl Shader<'_> {
pub fn opt_prmt(&mut self) {
for f in &mut self.functions {
PrmtPass::new().run(f);
}
}
}
@@ -0,0 +1,116 @@
// Copyright © 2024 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use rustc_hash::FxHashMap;
fn should_lower_to_warp(
sm: &ShaderModelInfo,
instr: &Instr,
r2ur: &FxHashMap<SSAValue, SSAValue>,
) -> bool {
if !sm.op_can_be_uniform(&instr.op) {
return true;
}
let mut num_non_uniform_srcs = 0;
instr.for_each_ssa_use(|ssa| {
if !ssa.is_uniform() || r2ur.contains_key(ssa) {
num_non_uniform_srcs += 1;
}
});
num_non_uniform_srcs >= 2
}
fn propagate_r2ur(
instr: &mut Instr,
r2ur: &FxHashMap<SSAValue, SSAValue>,
) -> bool {
let mut progress = false;
// We don't want Instr::for_each_ssa_use_mut() because it would treat
// bindless cbuf sources as SSA sources.
for src in instr.srcs_mut() {
if let SrcRef::SSA(vec) = &mut src.src_ref {
for ssa in &mut vec[..] {
if let Some(r) = r2ur.get(ssa) {
progress = true;
*ssa = *r;
}
}
}
}
progress
}
impl Shader<'_> {
pub fn opt_uniform_instrs(&mut self) {
let sm = self.sm;
let mut r2ur = Default::default();
let mut propagated_r2ur = false;
self.map_instrs(|mut instr, alloc| {
match &instr.op {
Op::Redux(_)
| Op::PhiDsts(_)
| Op::PhiSrcs(_)
| Op::Pin(_)
| Op::Unpin(_)
| Op::Vote(_) => [instr].into(),
Op::Bra(bra) if sm.sm() >= 80 => match &instr.pred.pred_ref {
PredRef::SSA(ssa) if ssa.file() == RegFile::UPred => {
let bra_u = OpBra {
target: bra.target,
cond: instr.pred.into(),
};
[Instr::new(bra_u)].into()
}
_ => [instr].into(),
},
_ if instr.is_uniform() => {
let mut b = InstrBuilder::new(sm);
if should_lower_to_warp(sm, &instr, &r2ur) {
propagated_r2ur |= propagate_r2ur(&mut instr, &r2ur);
instr.for_each_ssa_def_mut(|ssa| {
let w = alloc.alloc(ssa.file().to_warp());
r2ur.insert(*ssa, w);
b.push_op(OpR2UR {
dst: (*ssa).into(),
src: w.into(),
});
*ssa = w;
});
let mut v = b.into_vec();
v.insert(0, instr);
v.into()
} else {
// We may have non-uniform sources
instr.for_each_ssa_use_mut(|ssa| {
let file = ssa.file();
if !file.is_uniform() {
let u = alloc.alloc(file.to_uniform().unwrap());
b.push_op(OpR2UR {
dst: u.into(),
src: (*ssa).into(),
});
*ssa = u;
}
});
b.push_instr(instr);
b.into_mapped_instrs()
}
}
_ => {
propagated_r2ur |= propagate_r2ur(&mut instr, &r2ur);
[instr].into()
}
}
});
if propagated_r2ur {
self.opt_dce();
}
}
}
+778
View File
@@ -0,0 +1,778 @@
// Copyright © 2024 Collabora, Ltd.
// SPDX-License-Identifier: MIT
extern crate nvidia_headers;
use std::cmp::min;
use compiler::bindings::*;
use nak_bindings::*;
use nvidia_headers::classes::{cla0c0, clc0c0, clc3c0, clc6c0, clcbc0, clcdc0};
use bitview::*;
use paste::paste;
type QMDBitView<'a> = BitMutView<'a, [u32]>;
trait QMD {
const GLOBAL_SIZE_LAYOUT: nak_qmd_dispatch_size_layout;
fn new() -> Self;
fn set_barrier_count(&mut self, barrier_count: u8);
fn set_cbuf(&mut self, idx: u8, addr: u64, size: u32);
fn cbuf_desc_layout(idx: u8) -> nak_qmd_cbuf_desc_layout;
fn set_global_size(&mut self, width: u32, height: u32, depth: u32);
fn set_local_size(&mut self, width: u16, height: u16, depth: u16);
fn set_prog_addr(&mut self, addr: u64);
fn set_register_count(&mut self, register_count: u8);
fn set_crs_size(&mut self, crs_size: u32);
fn set_slm_size(&mut self, slm_size: u32);
fn set_smem_size(
&mut self,
smem_size: u32,
dev: &nv_device_info,
info: &nak_shader_info,
);
}
macro_rules! set_enum {
($bv:expr, $cls:ident, $strct:ident, $field:ident, $enum:ident) => {
$bv.set_field(
paste! {$cls::[<$strct _ $field>]},
paste! {$cls::[<$strct _ $field _ $enum>]},
)
};
}
macro_rules! set_field {
($bv:expr, $cls:ident, $strct:ident, $field:ident, $val:expr) => {
$bv.set_field(paste! {$cls::[<$strct _ $field>]}, $val)
};
}
macro_rules! set_array {
($bv:expr, $cls:ident, $strct:ident, $f:ident, $i:expr, $x:expr) => {
$bv.set_field(paste! {$cls::[<$strct _ $f>]}($i), $x)
};
}
macro_rules! qmd_init {
($bv: expr, $c:ident, $s:ident, $mjv:expr, $mnv:expr) => {
set_field!($bv, $c, $s, QMD_MAJOR_VERSION, $mjv);
set_field!($bv, $c, $s, QMD_VERSION, $mnv);
set_enum!($bv, $c, $s, API_VISIBLE_CALL_LIMIT, NO_CHECK);
set_enum!($bv, $c, $s, SAMPLER_INDEX, INDEPENDENTLY);
};
}
macro_rules! qmd_impl_common {
($c:ident, $s:ident) => {
fn set_barrier_count(&mut self, barrier_count: u8) {
let mut bv = QMDBitView::new(&mut self.qmd);
set_field!(bv, $c, $s, BARRIER_COUNT, barrier_count);
}
const GLOBAL_SIZE_LAYOUT: nak_qmd_dispatch_size_layout = {
assert!(paste! {$c::[<$s _MAX_BIT>]} < size_of::<Self>() * 8);
assert!(NAK_MAX_QMD_SIZE_B as usize >= size_of::<Self>());
let w = paste! {$c::[<$s _CTA_RASTER_WIDTH>]};
let h = paste! {$c::[<$s _CTA_RASTER_HEIGHT>]};
let d = paste! {$c::[<$s _CTA_RASTER_DEPTH>]};
let local_w = paste! {$c::[<$s _CTA_THREAD_DIMENSION0>]};
let local_h = paste! {$c::[<$s _CTA_THREAD_DIMENSION1>]};
let local_d = paste! {$c::[<$s _CTA_THREAD_DIMENSION2>]};
nak_qmd_dispatch_size_layout {
x_start: w.start as u16,
x_end: w.end as u16,
y_start: h.start as u16,
y_end: h.end as u16,
z_start: d.start as u16,
z_end: d.end as u16,
local_x_start: local_w.start as u16,
local_x_end: local_w.end as u16,
local_y_start: local_h.start as u16,
local_y_end: local_h.end as u16,
local_z_start: local_d.start as u16,
local_z_end: local_d.end as u16,
}
};
fn set_global_size(&mut self, width: u32, height: u32, depth: u32) {
let mut bv = QMDBitView::new(&mut self.qmd);
set_field!(bv, $c, $s, CTA_RASTER_WIDTH, width);
set_field!(bv, $c, $s, CTA_RASTER_HEIGHT, height);
set_field!(bv, $c, $s, CTA_RASTER_DEPTH, depth);
}
fn set_local_size(&mut self, width: u16, height: u16, depth: u16) {
let mut bv = QMDBitView::new(&mut self.qmd);
set_field!(bv, $c, $s, CTA_THREAD_DIMENSION0, width);
set_field!(bv, $c, $s, CTA_THREAD_DIMENSION1, height);
set_field!(bv, $c, $s, CTA_THREAD_DIMENSION2, depth);
}
};
}
macro_rules! qmd_impl_set_crs_size {
($c:ident, $s:ident) => {
fn set_crs_size(&mut self, crs_size: u32) {
let mut bv = QMDBitView::new(&mut self.qmd);
let crs_size = crs_size.next_multiple_of(0x200);
set_field!(bv, $c, $s, SHADER_LOCAL_MEMORY_CRS_SIZE, crs_size);
}
};
}
const NONE_SHIFT: u8 = 0;
const SHIFTED4_SHIFT: u8 = 4;
const SHIFTED6_SHIFT: u8 = 6;
macro_rules! suffix_shift {
($suffix:ident) => {
paste! { [<$suffix _SHIFT>] }
};
}
macro_rules! suffix_field2 {
($c:ident, $s:ident, $field:ident, $subfield:ident, NONE) => {
paste! { $c::[<$s _ $field _ $subfield>] }
};
($c:ident, $s:ident, $field:ident, $subfield:ident, $suffix:ident) => {
paste! { $c::[<$s _ $field _ $subfield _ $suffix>] }
};
}
macro_rules! cbuf_suffix_field {
($c:ident, $s:ident, $subfield:ident, $suffix:ident) => {
suffix_field2!($c, $s, CONSTANT_BUFFER, $subfield, $suffix)
};
}
macro_rules! qmd_impl_set_cbuf {
($c:ident, $s:ident, $addr_suffix:ident, $size_suffix:ident) => {
fn set_cbuf(&mut self, idx: u8, addr: u64, size: u32) {
let mut bv = QMDBitView::new(&mut self.qmd);
let idx = idx.into();
let addr_shift = suffix_shift!($addr_suffix);
let addr_shifted = addr >> addr_shift;
assert!((addr_shifted << addr_shift) == addr);
bv.set_field(
cbuf_suffix_field!($c, $s, ADDR_LOWER, $addr_suffix)(idx),
addr_shifted as u32,
);
bv.set_field(
cbuf_suffix_field!($c, $s, ADDR_UPPER, $addr_suffix)(idx),
(addr_shifted >> 32) as u32,
);
let size_shift = suffix_shift!($size_suffix);
assert!(((size >> size_shift) << size_shift) == size);
bv.set_field(
cbuf_suffix_field!($c, $s, SIZE, $size_suffix)(idx),
size >> size_shift,
);
set_array!(bv, $c, $s, CONSTANT_BUFFER_VALID, idx, true);
}
fn cbuf_desc_layout(idx: u8) -> nak_qmd_cbuf_desc_layout {
let lo = cbuf_suffix_field!($c, $s, ADDR_LOWER, $addr_suffix)(
idx.into(),
);
let hi = cbuf_suffix_field!($c, $s, ADDR_UPPER, $addr_suffix)(
idx.into(),
);
nak_qmd_cbuf_desc_layout {
addr_shift: suffix_shift!($addr_suffix).into(),
addr_lo_start: lo.start as u16,
addr_lo_end: lo.end as u16,
addr_hi_start: hi.start as u16,
addr_hi_end: hi.end as u16,
}
}
};
}
macro_rules! qmd_impl_set_prog_addr_32 {
($c:ident, $s:ident) => {
fn set_prog_addr(&mut self, addr: u64) {
let mut bv = QMDBitView::new(&mut self.qmd);
set_field!(bv, $c, $s, PROGRAM_OFFSET, addr);
}
};
}
macro_rules! prog_addr_suffix_field {
($c:ident, $s:ident, $subfield:ident, $suffix:ident) => {
suffix_field2!($c, $s, PROGRAM_ADDRESS, $subfield, $suffix)
};
}
macro_rules! qmd_impl_set_prog_addr_64 {
($c:ident, $s:ident, $addr_suffix:ident) => {
fn set_prog_addr(&mut self, addr: u64) {
let mut bv = QMDBitView::new(&mut self.qmd);
let addr_shift = suffix_shift!($addr_suffix);
let addr_shifted = addr >> addr_shift;
assert!((addr_shifted << addr_shift) == addr);
bv.set_field(
prog_addr_suffix_field!($c, $s, LOWER, $addr_suffix),
addr_shifted as u32,
);
bv.set_field(
prog_addr_suffix_field!($c, $s, UPPER, $addr_suffix),
(addr_shifted >> 32) as u32,
);
}
};
}
macro_rules! qmd_impl_set_register_count {
($c:ident, $s:ident, $field:ident) => {
fn set_register_count(&mut self, register_count: u8) {
let mut bv = QMDBitView::new(&mut self.qmd);
set_field!(bv, $c, $s, $field, register_count);
}
};
}
macro_rules! qmd_impl_set_slm_size {
($c:ident, $s:ident, $size_suffix:ident) => {
fn set_slm_size(&mut self, slm_size: u32) {
let mut bv = QMDBitView::new(&mut self.qmd);
let slm_size = slm_size.next_multiple_of(0x10);
let size_shift = suffix_shift!($size_suffix);
let slm_size_shifted = slm_size >> size_shift;
assert!(slm_size_shifted << size_shift == slm_size);
bv.set_field(
suffix_field2!(
$c,
$s,
SHADER_LOCAL_MEMORY_HIGH,
SIZE,
$size_suffix
),
0,
);
bv.set_field(
suffix_field2!(
$c,
$s,
SHADER_LOCAL_MEMORY_LOW,
SIZE,
$size_suffix
),
slm_size_shifted,
);
}
};
}
mod qmd_0_6 {
use crate::qmd::*;
use nvidia_headers::classes::cla0c0::qmd as cla0c0;
#[repr(transparent)]
pub struct Qmd0_6 {
qmd: [u32; 64],
}
impl QMD for Qmd0_6 {
fn new() -> Self {
let mut qmd = [0; 64];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, cla0c0, QMDV00_06, 0, 6);
set_field!(bv, cla0c0, QMDV00_06, SASS_VERSION, 0x30);
Self { qmd }
}
qmd_impl_common!(cla0c0, QMDV00_06);
qmd_impl_set_crs_size!(cla0c0, QMDV00_06);
qmd_impl_set_cbuf!(cla0c0, QMDV00_06, NONE, NONE);
qmd_impl_set_prog_addr_32!(cla0c0, QMDV00_06);
qmd_impl_set_register_count!(cla0c0, QMDV00_06, REGISTER_COUNT);
qmd_impl_set_slm_size!(cla0c0, QMDV00_06, NONE);
fn set_smem_size(
&mut self,
smem_size: u32,
_dev: &nv_device_info,
_info: &nak_shader_info,
) {
let mut bv = QMDBitView::new(&mut self.qmd);
let smem_size = smem_size.next_multiple_of(0x100);
set_field!(bv, cla0c0, QMDV00_06, SHARED_MEMORY_SIZE, smem_size);
let l1_config = if smem_size <= (16 << 10) {
cla0c0::QMDV00_06_L1_CONFIGURATION_DIRECTLY_ADDRESSABLE_MEMORY_SIZE_16KB
} else if smem_size <= (32 << 10) {
cla0c0::QMDV00_06_L1_CONFIGURATION_DIRECTLY_ADDRESSABLE_MEMORY_SIZE_32KB
} else if smem_size <= (48 << 10) {
cla0c0::QMDV00_06_L1_CONFIGURATION_DIRECTLY_ADDRESSABLE_MEMORY_SIZE_48KB
} else {
panic!("Invalid shared memory size");
};
set_field!(bv, cla0c0, QMDV00_06, L1_CONFIGURATION, l1_config);
}
}
}
use qmd_0_6::Qmd0_6;
mod qmd_2_1 {
use crate::qmd::*;
use nvidia_headers::classes::clc0c0::qmd as clc0c0;
#[repr(transparent)]
pub struct Qmd2_1 {
qmd: [u32; 64],
}
impl QMD for Qmd2_1 {
fn new() -> Self {
let mut qmd = [0; 64];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, clc0c0, QMDV02_01, 2, 1);
set_field!(bv, clc0c0, QMDV02_01, SM_GLOBAL_CACHING_ENABLE, true);
Self { qmd }
}
qmd_impl_common!(clc0c0, QMDV02_01);
qmd_impl_set_crs_size!(clc0c0, QMDV02_01);
qmd_impl_set_cbuf!(clc0c0, QMDV02_01, NONE, SHIFTED4);
qmd_impl_set_prog_addr_32!(clc0c0, QMDV02_01);
qmd_impl_set_register_count!(clc0c0, QMDV02_01, REGISTER_COUNT);
qmd_impl_set_slm_size!(clc0c0, QMDV02_01, NONE);
fn set_smem_size(
&mut self,
smem_size: u32,
_dev: &nv_device_info,
_info: &nak_shader_info,
) {
let mut bv = QMDBitView::new(&mut self.qmd);
let smem_size = smem_size.next_multiple_of(0x100);
set_field!(bv, clc0c0, QMDV02_01, SHARED_MEMORY_SIZE, smem_size);
}
}
}
use qmd_2_1::Qmd2_1;
fn gv100_smem_size_to_hw(size_kb: u16) -> u16 {
assert!(size_kb % 4 == 0);
(size_kb / 4) + 1
}
/// Returns the SM_CONFIG_SHARED_MEM_SIZE for the given workgroups and shared
/// memory size.
fn gv100_pick_smem_size_kb(
size: u32,
smem_sizes_kb: &[u16],
workgroups: u32,
) -> u16 {
let highest = *smem_sizes_kb.last().unwrap();
// How many workgroups can we launch with the requested shared mem size?
let workgroups = min(workgroups, u32::from(highest) * 1024 / size.max(1));
assert!(
workgroups > 0,
"Requested shared memory not supported by the hw."
);
// Now that we know for how many workgroups we have shared memory, try to
// find the smallest allocation that fits.
*smem_sizes_kb
.iter()
.find(|&&val| u32::from(val) * 1024 >= size * workgroups)
.unwrap_or(&highest)
}
/// Returns the (MIN_, TARGET_, MAX_) SM_CONFIG_SHARED_MEM_SIZE values.
fn gv100_get_hw_smem_sizes(
smem_size: u32,
dev: &nv_device_info,
info: &nak_shader_info,
) -> (u16, u16, u16) {
let cs_info = unsafe {
assert!(info.stage == MESA_SHADER_COMPUTE);
&info.__bindgen_anon_1.cs
};
let smem_sizes = &dev.sm_smem_sizes_kB[0..dev.sm_smem_size_count.into()];
let threads =
cs_info.local_size[0] * cs_info.local_size[1] * cs_info.local_size[2];
let warps = threads.div_ceil(32);
let workgroups_per_sm = info.max_warps_per_sm / u32::from(warps);
let min = gv100_pick_smem_size_kb(smem_size, smem_sizes, 1);
let target =
gv100_pick_smem_size_kb(smem_size, smem_sizes, workgroups_per_sm);
let max = *smem_sizes.last().unwrap();
(
gv100_smem_size_to_hw(min),
gv100_smem_size_to_hw(target),
gv100_smem_size_to_hw(max),
)
}
macro_rules! qmd_impl_set_smem_size_bounded {
($c:ident, $s:ident) => {
fn set_smem_size(
&mut self,
smem_size: u32,
dev: &nv_device_info,
info: &nak_shader_info,
) {
let mut bv = QMDBitView::new(&mut self.qmd);
let smem_size = smem_size.next_multiple_of(0x100);
set_field!(bv, $c, $s, SHARED_MEMORY_SIZE, smem_size);
let (min, target, max) =
gv100_get_hw_smem_sizes(smem_size, dev, info);
set_field!(bv, $c, $s, MIN_SM_CONFIG_SHARED_MEM_SIZE, min);
set_field!(bv, $c, $s, MAX_SM_CONFIG_SHARED_MEM_SIZE, max);
set_field!(bv, $c, $s, TARGET_SM_CONFIG_SHARED_MEM_SIZE, target);
}
};
}
macro_rules! qmd_impl_set_smem_size_bounded_gb {
($c:ident, $s:ident) => {
fn set_smem_size(
&mut self,
smem_size: u32,
dev: &nv_device_info,
info: &nak_shader_info,
) {
let mut bv = QMDBitView::new(&mut self.qmd);
let smem_size = smem_size.next_multiple_of(0x100);
let size_shift = 7;
let smem_size_shifted = smem_size >> size_shift;
assert!((smem_size_shifted << size_shift) == smem_size);
set_field!(
bv,
$c,
$s,
SHARED_MEMORY_SIZE_SHIFTED7,
smem_size_shifted
);
let (min, target, max) =
gv100_get_hw_smem_sizes(smem_size, dev, info);
set_field!(bv, $c, $s, MIN_SM_CONFIG_SHARED_MEM_SIZE, min);
set_field!(bv, $c, $s, MAX_SM_CONFIG_SHARED_MEM_SIZE, max);
set_field!(bv, $c, $s, TARGET_SM_CONFIG_SHARED_MEM_SIZE, target);
}
};
}
mod qmd_2_2 {
use crate::qmd::*;
use nvidia_headers::classes::clc3c0::qmd as clc3c0;
#[repr(transparent)]
pub struct Qmd2_2 {
qmd: [u32; 64],
}
impl QMD for Qmd2_2 {
fn new() -> Self {
let mut qmd = [0; 64];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, clc3c0, QMDV02_02, 2, 2);
set_field!(bv, clc3c0, QMDV02_02, SM_GLOBAL_CACHING_ENABLE, true);
Self { qmd }
}
qmd_impl_common!(clc3c0, QMDV02_02);
qmd_impl_set_crs_size!(clc3c0, QMDV02_02);
qmd_impl_set_cbuf!(clc3c0, QMDV02_02, NONE, SHIFTED4);
qmd_impl_set_prog_addr_64!(clc3c0, QMDV02_02, NONE);
qmd_impl_set_register_count!(clc3c0, QMDV02_02, REGISTER_COUNT_V);
qmd_impl_set_smem_size_bounded!(clc3c0, QMDV02_02);
qmd_impl_set_slm_size!(clc3c0, QMDV02_02, NONE);
}
}
use qmd_2_2::Qmd2_2;
mod qmd_3_0 {
use crate::qmd::*;
use nvidia_headers::classes::clc6c0::qmd as clc6c0;
#[repr(transparent)]
pub struct Qmd3_0 {
qmd: [u32; 64],
}
impl QMD for Qmd3_0 {
fn new() -> Self {
let mut qmd = [0; 64];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, clc6c0, QMDV03_00, 3, 0);
set_field!(bv, clc6c0, QMDV03_00, SM_GLOBAL_CACHING_ENABLE, true);
Self { qmd }
}
qmd_impl_common!(clc6c0, QMDV03_00);
fn set_crs_size(&mut self, crs_size: u32) {
assert!(crs_size == 0);
}
qmd_impl_set_cbuf!(clc6c0, QMDV03_00, NONE, SHIFTED4);
qmd_impl_set_prog_addr_64!(clc6c0, QMDV03_00, NONE);
qmd_impl_set_register_count!(clc6c0, QMDV03_00, REGISTER_COUNT_V);
qmd_impl_set_smem_size_bounded!(clc6c0, QMDV03_00);
qmd_impl_set_slm_size!(clc6c0, QMDV03_00, NONE);
}
}
use qmd_3_0::Qmd3_0;
mod qmd_4_0 {
use crate::qmd::*;
mod clcbc0 {
pub use nvidia_headers::classes::clcbc0::qmd::*;
// Some renames we have to carry for Hopper
pub use QMDV04_00_GRID_DEPTH as QMDV04_00_CTA_RASTER_DEPTH;
pub use QMDV04_00_GRID_HEIGHT as QMDV04_00_CTA_RASTER_HEIGHT;
pub use QMDV04_00_GRID_WIDTH as QMDV04_00_CTA_RASTER_WIDTH;
pub use QMDV04_00_QMD_MINOR_VERSION as QMDV04_00_QMD_VERSION;
}
#[repr(transparent)]
pub struct Qmd4_0 {
qmd: [u32; 96],
}
impl QMD for Qmd4_0 {
fn new() -> Self {
let mut qmd = [0; 96];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, clcbc0, QMDV04_00, 4, 0);
Self { qmd }
}
qmd_impl_common!(clcbc0, QMDV04_00);
fn set_crs_size(&mut self, crs_size: u32) {
assert!(crs_size == 0);
}
qmd_impl_set_cbuf!(clcbc0, QMDV04_00, SHIFTED6, SHIFTED4);
qmd_impl_set_prog_addr_64!(clcbc0, QMDV04_00, NONE);
qmd_impl_set_register_count!(clcbc0, QMDV04_00, REGISTER_COUNT);
qmd_impl_set_smem_size_bounded!(clcbc0, QMDV04_00);
qmd_impl_set_slm_size!(clcbc0, QMDV04_00, NONE);
}
}
use qmd_4_0::Qmd4_0;
mod qmd_5_0 {
use crate::qmd::*;
// Blackwell_A is CD97, Blackwell_B is CE97, however if Hopper is v4 and
// Blackwell_B is v5, then Blackwell_A also has to be v5 hence we'll use
// clcdc0qmd here
mod clcdc0 {
pub use nvidia_headers::classes::clcdc0::qmd::*;
// Some renames we may have to carry for Blackwell
pub use QMDV05_00_GRID_DEPTH as QMDV05_00_CTA_RASTER_DEPTH;
pub use QMDV05_00_GRID_HEIGHT as QMDV05_00_CTA_RASTER_HEIGHT;
pub use QMDV05_00_GRID_WIDTH as QMDV05_00_CTA_RASTER_WIDTH;
pub use QMDV05_00_QMD_MINOR_VERSION as QMDV05_00_QMD_VERSION;
}
pub struct Qmd5_0 {
qmd: [u32; 96],
}
impl QMD for Qmd5_0 {
fn new() -> Self {
let mut qmd = [0; 96];
let mut bv = QMDBitView::new(&mut qmd);
qmd_init!(bv, clcdc0, QMDV05_00, 5, 0);
set_field!(bv, clcdc0, QMDV05_00, QMD_TYPE, 0x2);
set_field!(bv, clcdc0, QMDV05_00, QMD_GROUP_ID, 0x1f);
Self { qmd }
}
qmd_impl_common!(clcdc0, QMDV05_00);
fn set_crs_size(&mut self, crs_size: u32) {
assert!(crs_size == 0);
}
qmd_impl_set_cbuf!(clcdc0, QMDV05_00, SHIFTED6, SHIFTED4);
qmd_impl_set_prog_addr_64!(clcdc0, QMDV05_00, SHIFTED4);
qmd_impl_set_register_count!(clcdc0, QMDV05_00, REGISTER_COUNT);
qmd_impl_set_smem_size_bounded_gb!(clcdc0, QMDV05_00);
qmd_impl_set_slm_size!(clcdc0, QMDV05_00, SHIFTED4);
}
}
use qmd_5_0::Qmd5_0;
fn fill_qmd<Q: QMD>(
dev: &nv_device_info,
info: &nak_shader_info,
qmd_info: &nak_qmd_info,
) -> Q {
let cs_info = unsafe {
assert!(info.stage == MESA_SHADER_COMPUTE);
&info.__bindgen_anon_1.cs
};
let mut qmd = Q::new();
qmd.set_barrier_count(info.num_control_barriers);
qmd.set_global_size(
qmd_info.global_size[0],
qmd_info.global_size[1],
qmd_info.global_size[2],
);
qmd.set_local_size(
cs_info.local_size[0],
cs_info.local_size[1],
cs_info.local_size[2],
);
qmd.set_prog_addr(qmd_info.addr);
qmd.set_register_count(info.num_gprs);
qmd.set_crs_size(info.crs_size);
qmd.set_slm_size(info.slm_size);
assert!(qmd_info.smem_size <= u32::from(dev.max_smem_per_wg_kB) * 1024);
qmd.set_smem_size(qmd_info.smem_size, dev, info);
for i in 0..qmd_info.num_cbufs {
let cb = &qmd_info.cbufs[usize::try_from(i).unwrap()];
if cb.size > 0 {
qmd.set_cbuf(cb.index.try_into().unwrap(), cb.addr, cb.size);
}
}
qmd
}
#[no_mangle]
pub extern "C" fn nak_qmd_size_B(dev: &nv_device_info) -> u32 {
let size_B = if dev.cls_compute >= clcdc0::BLACKWELL_COMPUTE_A {
size_of::<Qmd5_0>().try_into().unwrap()
} else if dev.cls_compute >= clcbc0::HOPPER_COMPUTE_A {
size_of::<Qmd4_0>().try_into().unwrap()
} else if dev.cls_compute >= clc6c0::AMPERE_COMPUTE_A {
size_of::<Qmd3_0>().try_into().unwrap()
} else if dev.cls_compute >= clc3c0::VOLTA_COMPUTE_A {
size_of::<Qmd2_2>().try_into().unwrap()
} else if dev.cls_compute >= clc0c0::PASCAL_COMPUTE_A {
size_of::<Qmd2_1>().try_into().unwrap()
} else if dev.cls_compute >= cla0c0::KEPLER_COMPUTE_A {
size_of::<Qmd0_6>().try_into().unwrap()
} else {
panic!("Unknown shader model");
};
assert!(size_B <= NAK_MAX_QMD_SIZE_B);
size_B
}
#[no_mangle]
pub extern "C" fn nak_fill_qmd(
dev: *const nv_device_info,
info: *const nak_shader_info,
qmd_info: *const nak_qmd_info,
qmd_out: *mut ::std::os::raw::c_void,
qmd_size: usize,
) {
assert!(!dev.is_null());
let dev = unsafe { &*dev };
assert!(!info.is_null());
let info = unsafe { &*info };
assert!(!qmd_info.is_null());
let qmd_info = unsafe { &*qmd_info };
unsafe {
if dev.cls_compute >= clcdc0::BLACKWELL_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd5_0;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else if dev.cls_compute >= clcbc0::HOPPER_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd4_0;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else if dev.cls_compute >= clc6c0::AMPERE_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd3_0;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else if dev.cls_compute >= clc3c0::VOLTA_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd2_2;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else if dev.cls_compute >= clc0c0::PASCAL_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd2_1;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else if dev.cls_compute >= cla0c0::KEPLER_COMPUTE_A {
let qmd_out = qmd_out as *mut Qmd0_6;
assert!(qmd_size == size_of_val(&*qmd_out));
qmd_out.write(fill_qmd(dev, info, qmd_info));
} else {
panic!("Unknown shader model");
}
}
}
#[no_mangle]
pub extern "C" fn nak_get_qmd_dispatch_size_layout(
dev: &nv_device_info,
) -> nak_qmd_dispatch_size_layout {
if dev.cls_compute >= clcdc0::BLACKWELL_COMPUTE_A {
Qmd5_0::GLOBAL_SIZE_LAYOUT
} else if dev.cls_compute >= clcbc0::HOPPER_COMPUTE_A {
Qmd4_0::GLOBAL_SIZE_LAYOUT
} else if dev.cls_compute >= clc6c0::AMPERE_COMPUTE_A {
Qmd3_0::GLOBAL_SIZE_LAYOUT
} else if dev.cls_compute >= clc3c0::VOLTA_COMPUTE_A {
Qmd2_2::GLOBAL_SIZE_LAYOUT
} else if dev.cls_compute >= clc0c0::PASCAL_COMPUTE_A {
Qmd2_1::GLOBAL_SIZE_LAYOUT
} else if dev.cls_compute >= cla0c0::KEPLER_COMPUTE_A {
Qmd0_6::GLOBAL_SIZE_LAYOUT
} else {
panic!("Unsupported shader model");
}
}
#[no_mangle]
pub extern "C" fn nak_get_qmd_cbuf_desc_layout(
dev: &nv_device_info,
idx: u8,
) -> nak_qmd_cbuf_desc_layout {
if dev.cls_compute >= clcdc0::BLACKWELL_COMPUTE_A {
Qmd5_0::cbuf_desc_layout(idx)
} else if dev.cls_compute >= clcbc0::HOPPER_COMPUTE_A {
Qmd4_0::cbuf_desc_layout(idx)
} else if dev.cls_compute >= clc6c0::AMPERE_COMPUTE_A {
Qmd3_0::cbuf_desc_layout(idx)
} else if dev.cls_compute >= clc3c0::VOLTA_COMPUTE_A {
Qmd2_2::cbuf_desc_layout(idx)
} else if dev.cls_compute >= clc0c0::PASCAL_COMPUTE_A {
Qmd2_1::cbuf_desc_layout(idx)
} else if dev.cls_compute >= cla0c0::KEPLER_COMPUTE_A {
Qmd0_6::cbuf_desc_layout(idx)
} else {
panic!("Unsupported shader model");
}
}
+186
View File
@@ -0,0 +1,186 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use rustc_hash::FxHashMap;
use crate::ir::*;
use std::ops::{Index, IndexMut, Range};
pub struct RegTracker<T> {
reg: [T; 255],
ureg: [T; 80],
pred: [T; 7],
upred: [T; 7],
carry: [T; 1],
}
fn new_array_with<T, const N: usize>(f: &impl Fn() -> T) -> [T; N] {
let mut v = Vec::with_capacity(N);
for _ in 0..N {
v.push(f());
}
v.try_into()
.unwrap_or_else(|_| panic!("Array size mismatch"))
}
impl<T> RegTracker<T> {
pub fn new_with(f: &impl Fn() -> T) -> Self {
Self {
reg: new_array_with(f),
ureg: new_array_with(f),
pred: new_array_with(f),
upred: new_array_with(f),
carry: new_array_with(f),
}
}
}
impl<T> Index<RegRef> for RegTracker<T> {
type Output = [T];
fn index(&self, reg: RegRef) -> &[T] {
let range = reg.idx_range();
let range = Range {
start: usize::try_from(range.start).unwrap(),
end: usize::try_from(range.end).unwrap(),
};
match reg.file() {
RegFile::GPR => &self.reg[range],
RegFile::UGPR => &self.ureg[range],
RegFile::Pred => &self.pred[range],
RegFile::UPred => &self.upred[range],
RegFile::Carry => &self.carry[range],
RegFile::Bar => &[], // Barriers have a HW scoreboard
RegFile::Mem => panic!("Not a register"),
}
}
}
impl<T> IndexMut<RegRef> for RegTracker<T> {
fn index_mut(&mut self, reg: RegRef) -> &mut [T] {
let range = reg.idx_range();
let range = Range {
start: usize::try_from(range.start).unwrap(),
end: usize::try_from(range.end).unwrap(),
};
match reg.file() {
RegFile::GPR => &mut self.reg[range],
RegFile::UGPR => &mut self.ureg[range],
RegFile::Pred => &mut self.pred[range],
RegFile::UPred => &mut self.upred[range],
RegFile::Carry => &mut self.carry[range],
RegFile::Bar => &mut [], // Barriers have a HW scoreboard
RegFile::Mem => panic!("Not a register"),
}
}
}
/// Memory-light version of [RegTracker].
///
/// This version uses sparse hashmaps instead of dense arrays.
#[derive(Clone, PartialEq, Eq, Default)]
pub struct SparseRegTracker<T: Default> {
regs: FxHashMap<RegRef, T>,
}
impl<T: Default + Clone + Eq> SparseRegTracker<T> {
pub fn for_each_pred(&mut self, f: impl FnMut(&mut T)) {
self.for_each_ref_mut(RegRef::new(RegFile::Pred, 0, 7), f);
}
pub fn for_each_carry(&mut self, f: impl FnMut(&mut T)) {
self.for_each_ref_mut(RegRef::new(RegFile::Carry, 0, 1), f);
}
pub fn merge_with(&mut self, other: &Self, mut f: impl FnMut(&mut T, &T)) {
use std::collections::hash_map::Entry;
for (k, v) in other.regs.iter() {
match self.regs.entry(*k) {
Entry::Occupied(mut occupied_entry) => {
f(occupied_entry.get_mut(), v);
}
Entry::Vacant(vacant_entry) => {
vacant_entry.insert((*v).clone());
}
}
}
}
pub fn retain(&mut self, mut f: impl FnMut(&mut T) -> bool) {
self.regs.retain(|_k, v| f(v));
}
}
/// Common behavior for [RegTracker] and [SparseRegTracker]
pub trait RegRefIterable<T> {
fn for_each_ref_mut(&mut self, reg: RegRef, f: impl FnMut(&mut T));
fn for_each_instr_pred_mut(
&mut self,
instr: &Instr,
mut f: impl FnMut(&mut T),
) {
if let PredRef::Reg(reg) = &instr.pred.pred_ref {
self.for_each_ref_mut(*reg, |t| f(t));
}
}
fn for_each_instr_src_mut(
&mut self,
instr: &Instr,
mut f: impl FnMut(usize, &mut T),
) {
for (i, src) in instr.srcs().iter().enumerate() {
match &src.src_ref {
SrcRef::Reg(reg) => {
self.for_each_ref_mut(*reg, |t| f(i, t));
}
SrcRef::CBuf(CBufRef {
buf: CBuf::BindlessUGPR(reg),
..
}) => {
self.for_each_ref_mut(*reg, |t| f(i, t));
}
_ => (),
}
}
}
fn for_each_instr_dst_mut(
&mut self,
instr: &Instr,
mut f: impl FnMut(usize, &mut T),
) {
for (i, dst) in instr.dsts().iter().enumerate() {
if let Dst::Reg(reg) = dst {
self.for_each_ref_mut(*reg, |t| f(i, t));
}
}
}
}
impl<T: Default> RegRefIterable<T> for SparseRegTracker<T> {
fn for_each_ref_mut(&mut self, reg: RegRef, mut f: impl FnMut(&mut T)) {
match reg.file() {
RegFile::Bar => return, // Barriers have a HW scoreboard
RegFile::Mem => panic!("Not a register"),
_ => {}
}
for i in 0..reg.comps() {
f(self.regs.entry(reg.comp(i)).or_default());
}
}
}
impl<T> RegRefIterable<T> for RegTracker<T> {
fn for_each_ref_mut(&mut self, reg: RegRef, mut f: impl FnMut(&mut T)) {
for entry in &mut self[reg] {
f(entry);
}
}
}
+346
View File
@@ -0,0 +1,346 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use crate::union_find::UnionFind;
use compiler::bitset::BitSet;
use rustc_hash::{FxBuildHasher, FxHashMap};
use std::cell::RefCell;
use std::cmp::Reverse;
use std::collections::BinaryHeap;
struct PhiTracker {
phi: Phi,
orig: SSAValue,
dst: SSAValue,
srcs: FxHashMap<usize, SSAValue>,
}
struct DefTrackerBlock {
pred: Vec<usize>,
succ: Vec<usize>,
defs: RefCell<FxHashMap<SSAValue, SSAValue>>,
phis: RefCell<Vec<PhiTracker>>,
}
fn get_ssa_or_phi(
worklist: &mut BinaryHeap<Reverse<usize>>,
ssa_alloc: &mut SSAValueAllocator,
phi_alloc: &mut PhiAllocator,
blocks: &[DefTrackerBlock],
needs_src: &mut BitSet,
b_idx: usize,
ssa: SSAValue,
) -> SSAValue {
// Annoyingly, Rust stack sizes get to be a problem so we don't want to use
// actual recursion here. Instead we use a worklist in the form of a binary
// heap. Using a binary heap ensures that we process the earliest blocks
// first.
debug_assert!(worklist.is_empty());
worklist.push(Reverse(b_idx));
loop {
let b_idx = worklist.peek().unwrap().0;
let b = &blocks[b_idx];
if let Some(&b_ssa) = b.defs.borrow().get(&ssa) {
// We already sorted this one out, pop the stack.
worklist.pop();
if worklist.is_empty() {
return b_ssa;
} else {
continue;
}
}
let mut pushed_pred = false;
let mut pred_ssa = None;
let mut all_same = true;
for &p_idx in &b.pred {
if p_idx >= b_idx {
// This is a loop back-edge, add a phi just in case. We'll
// remove it later if it's not needed
all_same = false;
} else if let Some(&p_ssa) = blocks[p_idx].defs.borrow().get(&ssa) {
if *pred_ssa.get_or_insert(p_ssa) != p_ssa {
all_same = false;
}
} else {
worklist.push(Reverse(p_idx));
pushed_pred = true;
}
}
// If we pushed any predecessors to the stack, loop again to sort them
// out before we try to sort out this block.
if pushed_pred {
continue;
}
// We now have everything we need to sort out this block
let b_ssa = if all_same {
pred_ssa.expect("Undefined value")
} else {
let phi = phi_alloc.alloc();
let phi_ssa = ssa_alloc.alloc(ssa.file());
let mut pt = PhiTracker {
phi,
orig: ssa,
dst: phi_ssa,
srcs: Default::default(),
};
for &p_idx in &b.pred {
if p_idx >= b_idx {
needs_src.insert(p_idx);
continue;
}
// Earlier iterations of the loop ensured this exists
let p_ssa = *blocks[p_idx].defs.borrow().get(&ssa).unwrap();
pt.srcs.insert(p_idx, p_ssa);
}
blocks[b_idx].phis.borrow_mut().push(pt);
phi_ssa
};
blocks[b_idx].defs.borrow_mut().insert(ssa, b_ssa);
worklist.pop();
if worklist.is_empty() {
return b_ssa;
}
}
}
fn get_or_insert_phi_dsts(bb: &mut BasicBlock) -> &mut OpPhiDsts {
let ip = if let Some(ip) = bb.phi_dsts_ip() {
ip
} else {
bb.instrs.insert(0, Instr::new(OpPhiDsts::new()));
0
};
match &mut bb.instrs[ip].op {
Op::PhiDsts(op) => op,
_ => panic!("Expected to find the phi we just inserted"),
}
}
fn get_or_insert_phi_srcs(bb: &mut BasicBlock) -> &mut OpPhiSrcs {
let ip = if let Some(ip) = bb.phi_srcs_ip() {
ip
} else if let Some(ip) = bb.branch_ip() {
bb.instrs.insert(ip, Instr::new(OpPhiSrcs::new()));
ip
} else {
bb.instrs.push(Instr::new(OpPhiSrcs::new()));
bb.instrs.len() - 1
};
match &mut bb.instrs[ip].op {
Op::PhiSrcs(op) => op,
_ => panic!("Expected to find the phi we just inserted"),
}
}
impl Function {
/// Repairs SSA form
///
/// Certain passes such as register spilling may produce a program that is
/// no longer in SSA form. This pass is able to repair SSA by inserting
/// phis as needed. Even though we do not require dominance or that each
/// value be defined once we do require that, for every use of an SSAValue
/// and for every path from the start of the program to that use, there must
/// be some definition of the value along that path.
///
/// The algorithm implemented here is based on the one in "Simple and
/// Efficient Construction of Static Single Assignment Form" by Braun, et.
/// al. The primary difference between our implementation and the paper is
/// that we can't rewrite the IR on-the-fly. Instead, we store everything
/// in hash tables and handle removing redundant phis with back-edges as a
/// separate pass between figuring out where phis are needed and actually
/// constructing the phi instructions.
pub fn repair_ssa(&mut self) {
// First, count the number of defs for each SSA value. This will allow
// us to skip any SSA values which only have a single definition in
// later passes.
let mut has_mult_defs = false;
let mut num_defs = FxHashMap::default();
for b in &self.blocks {
for instr in &b.instrs {
instr.for_each_ssa_def(|ssa| {
num_defs
.entry(*ssa)
.and_modify(|e| {
has_mult_defs = true;
*e += 1;
})
.or_insert(1);
});
}
}
if !has_mult_defs {
return;
}
let cfg = &mut self.blocks;
let ssa_alloc = &mut self.ssa_alloc;
let phi_alloc = &mut self.phi_alloc;
let mut blocks = Vec::new();
let mut needs_src = BitSet::new();
let mut ssa_or_phi_worklist = BinaryHeap::new();
for b_idx in 0..cfg.len() {
assert!(blocks.len() == b_idx);
blocks.push(DefTrackerBlock {
pred: cfg.pred_indices(b_idx).to_vec(),
succ: cfg.succ_indices(b_idx).to_vec(),
defs: RefCell::new(Default::default()),
phis: RefCell::new(Vec::new()),
});
for instr in &mut cfg[b_idx].instrs {
instr.for_each_ssa_use_mut(|ssa| {
if num_defs.get(ssa).cloned().unwrap_or(0) > 1 {
*ssa = get_ssa_or_phi(
&mut ssa_or_phi_worklist,
ssa_alloc,
phi_alloc,
&blocks,
&mut needs_src,
b_idx,
*ssa,
);
}
});
instr.for_each_ssa_def_mut(|ssa| {
if num_defs.get(ssa).cloned().unwrap_or(0) > 1 {
let new_ssa = ssa_alloc.alloc(ssa.file());
blocks[b_idx].defs.borrow_mut().insert(*ssa, new_ssa);
*ssa = new_ssa;
}
});
}
}
// Populate phi sources for any back-edges
loop {
let Some(b_idx) = needs_src.iter().next() else {
break;
};
needs_src.remove(b_idx);
for s_idx in &blocks[b_idx].succ {
if *s_idx <= b_idx {
let s = &blocks[*s_idx];
// We do a mutable borrow here. The algorithm is recursive
// and may insert phis into other blocks. However, because
// this is phi exists, its destination should be in the def
// set for s and so no new phis should need to be added.
// RefCell's dynamic borrow checks will assert this.
for phi in s.phis.borrow_mut().iter_mut() {
phi.srcs.entry(b_idx).or_insert_with(|| {
get_ssa_or_phi(
&mut ssa_or_phi_worklist,
ssa_alloc,
phi_alloc,
&blocks,
&mut needs_src,
b_idx,
phi.orig,
)
});
}
}
}
}
// For loop back-edges, we inserted a phi whether we need one or not.
// We want to eliminate any redundant phis.
let mut ssa_map = UnionFind::<SSAValue, FxBuildHasher>::new();
if cfg.has_loop() {
let mut to_do = true;
while to_do {
to_do = false;
for b_idx in 0..cfg.len() {
let b = &blocks[b_idx];
b.phis.borrow_mut().retain_mut(|phi| {
let mut ssa = None;
for (_, p_ssa) in phi.srcs.iter_mut() {
// Apply the remap to the phi sources so that we
// pick up any remaps from previous loop iterations.
*p_ssa = ssa_map.find(*p_ssa);
if *p_ssa == phi.dst {
continue;
}
if *ssa.get_or_insert(*p_ssa) != *p_ssa {
// Multiple unique sources
return true;
}
}
// All sources are identical or the phi destination so
// we can delete this phi and add it to the remap
let ssa = ssa.expect("Circular SSA def");
// union(a, b) ensures that the representative is the representative
// for a. This means union(ssa, phi.dst) ensures that phi.dst gets
// mapped to ssa, not the other way around.
ssa_map.union(ssa, phi.dst);
to_do = true;
false
});
}
}
}
// Now we apply the remap to instruction sources and place the actual
// phis
for b_idx in 0..cfg.len() {
// Grab the successor index for inserting OpPhiSrc before we take a
// mutable reference to the CFG. There are no critical edges so we
// can only have an OpPhiSrc if there is a single successor.
let succ = cfg.succ_indices(b_idx);
let s_idx = if succ.len() == 1 {
Some(succ[0])
} else {
for s_idx in succ {
debug_assert!(blocks[*s_idx].phis.borrow().is_empty());
}
None
};
let bb = &mut cfg[b_idx];
// First we have phi destinations
let b_phis = blocks[b_idx].phis.borrow();
if !b_phis.is_empty() {
let phi_dst = get_or_insert_phi_dsts(bb);
for pt in b_phis.iter() {
phi_dst.dsts.push(pt.phi, pt.dst.into());
}
}
// Fix up any remapped SSA values in sources
if !ssa_map.is_empty() {
for instr in &mut bb.instrs {
instr.for_each_ssa_use_mut(|ssa| {
*ssa = ssa_map.find(*ssa);
});
}
}
if let Some(s_idx) = s_idx {
let s_phis = blocks[s_idx].phis.borrow();
if !s_phis.is_empty() {
let phi_src = get_or_insert_phi_srcs(bb);
for pt in s_phis.iter() {
let mut ssa = *pt.srcs.get(&b_idx).unwrap();
ssa = ssa_map.find(ssa);
phi_src.srcs.push(pt.phi, ssa.into());
}
}
}
}
}
}
@@ -0,0 +1,561 @@
// Copyright © 2025 Red Hat.
// SPDX-License-Identifier: MIT
#![allow(non_camel_case_types)]
use crate::ir::*;
use nak_latencies::sm100::*;
// This contains the register scheduling information provided by NVIDIA. This
// file is for Blackwell only.
//
// These latencies come from B100 (SM100) and not the consumer RTX chips
// (SM120). We have to add some padding to get everything passing on the RTX
// chips so that's done in this file while using the sm100 CSVs.
// Coupled instructions are ones with fixed latencies, they need delays but not
// scoreboards. Decoupled instructions are ones with variable latencies, need
// scoreboards but not delays. There are also redirected instructions which
// depending on the SM, can be coupled or Decoupled so both delays and
// scoreboards needs to be provided.
fn op_reg_latency(op: &Op, reader: bool, op_reg_idx: usize) -> RegLatencySM100 {
use RegLatencySM100::*;
match op {
// this will need updating if imad grows support for input predicates
Op::IMad(_) | Op::IMul(_) => Fma,
Op::IMad64(_) => {
if reader {
match op_reg_idx {
0 | 1 => ImadWideReadAb,
2 => ImadWideReadCl, // vs upper C operand - work it out
_ => {
panic!("Illegal field in imadwide")
}
}
} else {
ImadWideWriteDh // as above this needs more work
}
}
Op::PopC(_) => Decoupled,
Op::IAdd3(_) | Op::IAdd3X(_) => Alu,
Op::BMsk(_) => Alu,
Op::Sgxt(_) => Alu,
Op::Lop3(_) => Alu,
Op::Flo(_) => Decoupled,
Op::ISetP(_) => Dualalu,
Op::IAbs(_) => Alu,
Op::Lea(_) => Alu,
Op::LeaX(_) => Alu,
Op::IMnMx(_) => Dualalu,
Op::I2I(_) => Alu,
// I2IP => alu
Op::Shf(_) => Alu,
Op::F2FP(_) => Alu,
Op::FFma(_) => Fma,
Op::FAdd(_) => Fma,
Op::FMul(_) => Fma,
Op::FMnMx(_) => Dualalu,
Op::FSwzAdd(_) => Fma,
Op::FSet(_) => Dualalu,
// FSel => Alu,
Op::FSetP(_) => Dualalu,
// FChk => Decoupled,
Op::DAdd(_) | Op::DFma(_) | Op::DMul(_) | Op::DSetP(_) => {
RedirectedFp64
}
Op::DMnMx(_) => RedirectedFp64, // not in docs
Op::HAdd2(hadd2) => {
if hadd2.f32 {
Fp16F32
} else {
Fp16
}
}
Op::HFma2(_) | Op::HMul2(_) => Fp16,
Op::HSet2(_) | Op::HSetP2(_) | Op::HMnMx2(_) => Fp16Alu,
Op::Hmma(_) => Hmma,
Op::Ipa(_) => DecoupledAgu,
Op::MuFu(_) => Decoupled,
// Conversion functions all Decoupled
Op::F2F(_) => Decoupled,
Op::F2I(_) => Decoupled,
Op::I2F(_) => Decoupled,
Op::FRnd(_) => Decoupled,
Op::AL2P(_) => Decoupled,
Op::Mov(_) => Dualalu,
Op::Movm(_) => DecoupledAgu,
Op::Sel(_) => Dualalu,
Op::BRev(_) => Decoupled,
// P2R => Alu,
// R2P => Alu,
Op::PLop3(_) => Alu,
Op::Prmt(_) => Alu,
Op::Nop(_) => Disp64,
Op::Vote(_) => Dualalu,
Op::Match(_) => Decoupled,
Op::S2R(_) => DecoupledAgu,
Op::R2UR(_) => Alu,
Op::Redux(_) => {
if reader {
Decoupled
} else {
panic!("Illegal R2UR");
}
}
Op::CS2R(cs2r) => {
if cs2r.dst.comps() == 2 {
Disp64
} else {
Dualalu
}
}
// B2R => DecoupledAgu,
// LEPC => Disp64
Op::BMov(bmov) => match bmov.dst {
Dst::Reg(_) => Branch,
_ => Branch,
},
// RPCMOV.32 => Alu,
// RPCMOV.64 => Disp64
// PMTRIG => Disp64
// CSMTEST => Alu,
Op::Bar(_) => DecoupledAgu,
Op::Imma(_) => Imma,
Op::IDp4(_) => Fma,
Op::BClear(_) => Decoupled,
Op::Bra(_) => Decoupled,
Op::BSSy(_) => Decoupled,
Op::Kill(_) => Decoupled,
Op::Exit(_) => Decoupled,
Op::BSync(_) => Decoupled,
Op::Tex(_) => Decoupled,
Op::Tld(_) => Decoupled,
Op::Tld4(_) => Decoupled,
Op::Tmml(_) => Decoupled,
Op::Txd(_) => Decoupled,
Op::Txq(_) => Decoupled,
Op::Ldc(_) => Decoupled,
Op::ALd(_) => DecoupledAgu,
Op::ASt(_) => DecoupledAgu,
Op::Out(_) => DecoupledAgu,
Op::OutFinal(_) => DecoupledAgu,
Op::Ld(_) => DecoupledAgu,
Op::St(_) => DecoupledAgu,
Op::Atom(_) => DecoupledAgu,
//CCtl.i,c are coupled
Op::CCtl(_) => DecoupledAgu,
Op::MemBar(_) => Decoupled,
Op::SuLd(_) => Decoupled,
Op::SuSt(_) => Decoupled,
Op::SuAtom(_) => Decoupled,
Op::PixLd(_) => DecoupledAgu,
Op::Isberd(_) => DecoupledAgu,
Op::Isbewr(_) => DecoupledAgu,
Op::LdTram(_) => DecoupledAgu,
Op::Shfl(_) => DecoupledAgu,
Op::Ldsm(_) => DecoupledAgu,
x => {
panic!("Illegal instuction in reg category {}", x);
}
}
}
fn op_pred_latency(op: &Op) -> PredLatencySM100 {
use PredLatencySM100::*;
match op {
Op::Atom(_) => Decoupled,
Op::Bra(_) => Decoupled,
Op::DSetP(_) => RedirectedFp64,
Op::FMnMx(_) | Op::FSetP(_) => Dualalu,
Op::HFma2(_) => Fp16,
Op::HMnMx2(_) => Fp16,
Op::HSetP2(_) => Fp16,
Op::IAdd3(_) => Coupled,
Op::IAdd3X(_) => Coupled,
Op::IMad(_) => Fma,
Op::IMad64(_) => Fma,
Op::IMnMx(_) => Dualalu,
Op::IMul(_) => Fma,
Op::Ipa(_) => Decoupled,
Op::ISetP(_) => Dualalu,
Op::Ld(_) => Decoupled,
Op::Lea(_) | Op::LeaX(_) => Coupled,
Op::PixLd(_) => Decoupled,
Op::PLop3(_) => Coupled,
Op::PSetP(_) => Coupled,
Op::R2UR(_) => R2Ur,
Op::Sel(_) => Dualalu,
Op::Shfl(_) => Decoupled,
Op::SuLd(_) => Decoupled,
Op::SuSt(_) => Decoupled,
Op::Tex(_) => Decoupled,
Op::Tld(_) => Decoupled,
Op::Tld4(_) => Decoupled,
Op::Tmml(_) => Decoupled,
Op::Txd(_) => Decoupled,
Op::Txq(_) => Decoupled,
Op::Vote(_) => DispDualAlu,
Op::Match(_) => Decoupled,
_ => {
panic!("Illegal op in sm120 pred latency {}", op);
}
}
}
fn op_ureg_latency(
op: &Op,
reader: bool,
op_reg_idx: usize,
) -> UregLatencySM100 {
use UregLatencySM100::*;
// this decides between the category types for readers.
let bindless = reader && op.srcs_as_slice()[op_reg_idx].is_bindless_cbuf();
let coupled = if bindless { CoupledBindless } else { Coupled };
let decoupled = if bindless {
DecoupledBindless
} else {
Decoupled
};
// if this is a reader from a ureg, it could be a U* instruction or a
// regular instruction.
let uniform_op = op.is_uniform();
let coupled = if uniform_op { Udp } else { coupled };
let decoupled = if uniform_op { Udp } else { decoupled };
match op {
Op::BMsk(_) => coupled,
Op::BRev(_) => decoupled,
// uclea?
Op::Flo(_) => decoupled,
Op::IAdd3(_) | Op::IAdd3X(_) => coupled,
Op::IAbs(_) => coupled,
Op::IDp4(_) => coupled,
Op::IMnMx(_) => coupled,
Op::IMad(_) => coupled,
Op::IMad64(_) => coupled,
Op::ISetP(_) => coupled,
Op::ALd(_)
| Op::ASt(_)
| Op::Ld(_)
| Op::Ldsm(_)
| Op::St(_)
| Op::Atom(_) => decoupled,
Op::SuLd(_) | Op::SuSt(_) | Op::SuAtom(_) => decoupled,
Op::Ldc(_) => {
if uniform_op {
ToUr
} else {
decoupled
}
}
Op::Lea(_) => coupled,
Op::LeaX(_) => coupled,
Op::Lop2(_) | Op::Lop3(_) => coupled,
Op::MuFu(_) => decoupled,
Op::Mov(_) => {
if uniform_op {
Umov
} else {
coupled
}
}
// mov32i => uldc
// p2ur => udp,
Op::PLop3(_) => coupled,
Op::PopC(_) => {
if uniform_op {
coupled
} else {
decoupled
}
}
Op::Prmt(_) => coupled,
Op::PSetP(_) => coupled,
// UR2UP
Op::Sel(_) => coupled,
Op::Sgxt(_) => coupled,
Op::Shf(_) => coupled,
Op::Shfl(_) => decoupled,
Op::I2F(_) => decoupled,
Op::F2I(_) => decoupled,
Op::F2F(_) => decoupled,
Op::R2UR(_) => {
if !reader {
R2Ur
} else {
panic!("Illegal R2UR in ureg");
}
}
Op::Redux(_) => {
if !reader {
ToUr
} else {
panic!("Illegal R2UR in ureg");
}
}
Op::Vote(_) => Voteu,
Op::S2R(_) => ToUr,
Op::Tex(_) | Op::Tld(_) | Op::Tld4(_) | Op::Txq(_) => Tex,
Op::FRnd(_) => decoupled,
Op::F2FP(_)
| Op::FAdd(_)
| Op::FMul(_)
| Op::FFma(_)
| Op::FSet(_)
| Op::FSetP(_)
| Op::FMnMx(_)
| Op::HAdd2(_)
| Op::HMul2(_)
| Op::HSet2(_)
| Op::HFma2(_)
| Op::HMnMx2(_)
| Op::HSetP2(_) => coupled,
Op::DMul(_) | Op::DFma(_) | Op::DAdd(_) | Op::DSetP(_) => decoupled,
_ => {
panic!("Illegal instuction in ureg category {}", op);
}
}
}
fn op_upred_latency(op: &Op) -> UpredLatencySM100 {
use UpredLatencySM100::*;
let uniform_op = op.is_uniform();
match op {
Op::BMsk(_)
| Op::BRev(_)
| Op::Flo(_)
| Op::IAdd3(_)
| Op::IAdd3X(_)
| Op::IMad(_)
| Op::ISetP(_)
| Op::Lea(_)
| Op::LeaX(_)
| Op::Lop3(_)
| Op::Mov(_) => Udp,
Op::Bra(_) => BraJmp,
Op::Ldc(_) => UldcMma,
Op::PLop3(_) => {
if uniform_op {
Udp
} else {
Coupled
}
}
Op::PSetP(_) => {
if uniform_op {
Udp
} else {
Coupled
}
}
Op::Sel(_) => {
if uniform_op {
Udp
} else {
Coupled
}
}
Op::Vote(_) => {
if uniform_op {
Voteu
} else {
panic!("Illegal Vote in upred");
}
}
_ => {
panic!("Illegal instuction in upred category {}", op);
}
}
}
pub struct SM120Latency {}
impl SM120Latency {
pub fn needs_scoreboards(op: &Op) -> bool {
if op.is_uniform() {
matches!(
op_ureg_latency(op, false, 0),
UregLatencySM100::Uldc
| UregLatencySM100::ToUr
| UregLatencySM100::Tex
)
} else {
matches!(
op_reg_latency(op, false, 0),
RegLatencySM100::Dmma
| RegLatencySM100::Hmma
| RegLatencySM100::RedirectedFp64
| RegLatencySM100::Branch
| RegLatencySM100::Decoupled
| RegLatencySM100::DecoupledAgu
)
}
}
pub fn raw(
write: &Op,
dst_idx: usize,
read: Option<&Op>,
src_idx: usize,
) -> u32 {
let Some(dst_file) = write.dsts_as_slice()[dst_idx].file() else {
return 0;
};
match dst_file {
RegFile::GPR => {
let write_latency = op_reg_latency(write, false, dst_idx);
let read_latency = match read {
Some(op) => op_reg_latency(op, true, src_idx),
None => RegLatencySM100::RedirectedFp64,
};
// The latencies are for SM100 docs, but some chips need large
// one just override here.
if write_latency == RegLatencySM100::Hmma
|| read_latency == RegLatencySM100::Hmma
{
RegLatencySM100::raw(write_latency, read_latency, false) + 9
} else if write_latency == RegLatencySM100::Imma
|| read_latency == RegLatencySM100::Imma
{
RegLatencySM100::raw(write_latency, read_latency, false) + 5
} else {
RegLatencySM100::raw(write_latency, read_latency, false) + 1
}
}
RegFile::UGPR => {
let write_latency = op_ureg_latency(write, false, dst_idx);
let read_latency = match read {
Some(op) => op_ureg_latency(op, true, src_idx),
None => UregLatencySM100::Uldc,
};
UregLatencySM100::raw(write_latency, read_latency, false) + 1
}
RegFile::Pred => {
let write_latency = op_pred_latency(write);
let read_latency = match read {
Some(op) => op_pred_latency(op),
None => PredLatencySM100::RedirectedFp64,
};
PredLatencySM100::raw(write_latency, read_latency, false) + 1
}
RegFile::UPred => {
let write_latency = op_upred_latency(write);
let read_latency = match read {
Some(op) => op_upred_latency(op),
None => UpredLatencySM100::UGuard,
};
UpredLatencySM100::raw(write_latency, read_latency, false) + 1
}
RegFile::Bar => 0, // Barriers have a HW scoreboard
_ => panic!("Not a register"),
}
}
pub fn war(read: &Op, src_idx: usize, write: &Op, dst_idx: usize) -> u32 {
let Some(dst_file) = write.dsts_as_slice()[dst_idx].file() else {
return 0;
};
match dst_file {
RegFile::GPR => {
let write_latency = op_reg_latency(write, false, dst_idx);
let read_latency = op_reg_latency(read, true, src_idx);
if write_latency == RegLatencySM100::Hmma
|| read_latency == RegLatencySM100::Hmma
{
RegLatencySM100::war(read_latency, write_latency, false) + 7
} else {
RegLatencySM100::war(read_latency, write_latency, false)
}
}
RegFile::UGPR => {
let write_latency = op_ureg_latency(write, false, dst_idx);
let read_latency = op_ureg_latency(read, true, src_idx);
UregLatencySM100::war(read_latency, write_latency, false)
}
RegFile::Pred => {
let write_latency = op_pred_latency(write);
let read_latency = op_pred_latency(read);
PredLatencySM100::war(read_latency, write_latency, false)
}
RegFile::UPred => {
let write_latency = op_upred_latency(write);
let read_latency = op_upred_latency(read);
UpredLatencySM100::war(read_latency, write_latency, false)
}
_ => panic!("Not a register"),
}
}
pub fn waw(
a: &Op,
a_dst_idx: usize,
b: &Op,
b_dst_idx: usize,
a_op_pred: bool,
) -> u32 {
let Some(dst_file) = a.dsts_as_slice()[a_dst_idx].file() else {
return 0;
};
match dst_file {
RegFile::GPR => {
let write1_latency = op_reg_latency(a, false, a_dst_idx);
let write2_latency = op_reg_latency(b, false, b_dst_idx);
if write1_latency == RegLatencySM100::Hmma
|| write2_latency == RegLatencySM100::Hmma
{
RegLatencySM100::waw(
write1_latency,
write2_latency,
a_op_pred,
) + 7
} else {
RegLatencySM100::waw(
write1_latency,
write2_latency,
a_op_pred,
)
}
}
RegFile::UGPR => {
let write1_latency = op_ureg_latency(a, false, a_dst_idx);
let write2_latency = op_ureg_latency(b, false, b_dst_idx);
UregLatencySM100::waw(write1_latency, write2_latency, a_op_pred)
}
RegFile::Pred => {
let write1_latency = op_pred_latency(a);
let write2_latency = op_pred_latency(b);
PredLatencySM100::waw(write1_latency, write2_latency, a_op_pred)
}
RegFile::UPred => {
let write1_latency = op_upred_latency(a);
let write2_latency = op_upred_latency(b);
UpredLatencySM100::waw(write1_latency, write2_latency, false)
}
_ => panic!("Not a register"),
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,168 @@
use bitview::{BitMutViewable, BitViewable, SetField};
use rustc_hash::FxHashMap;
use crate::ir::{Instr, InstrDeps, Label, Op, OpNop, Shader};
pub fn instr_latency(_sm: u8, op: &Op, _dst_idx: usize) -> u32 {
if op.is_fp64() {
return 20;
}
match op {
Op::Ipa(_) => 15,
Op::Ld(_) => 24,
Op::ALd(_) => 24,
Op::IMul(_) => 15, // This does not apply to imad, right? right???
Op::ISetP(_) => 13,
Op::PSetP(_) => 13,
Op::IAdd2(o) if !o.carry_out.is_none() => 13,
Op::Tex(_)
| Op::Tld(_)
| Op::Tld4(_)
| Op::Tmml(_)
| Op::Txd(_)
| Op::Txq(_) => 17,
_ => 9,
}
}
pub fn latency_upper_bound() -> u32 {
24
}
pub fn instr_exec_latency(sm: u8, op: &Op) -> u32 {
let is_kepler_a = sm == 30;
match op {
Op::Tex(_)
| Op::Tld(_)
| Op::Tld4(_)
| Op::Tmml(_)
| Op::Txd(_)
| Op::Txq(_) => 17,
Op::MemBar(_) => 16,
Op::Cont(_) | Op::Brk(_) if is_kepler_a => 5,
Op::Exit(_) => 15,
_ => 1,
}
}
fn calc_instr_sched(prev_op: Option<&Op>, op: &Op, deps: &InstrDeps) -> u8 {
// Kepler is the first generation to lift scoreboarding from the
// hardware into the compiler. For each instruction we encode
// the delay but not all the other information necessary for newer
// architectures.
// The hardware still checks for data-hazard and, if present, it
// will delay the instruction by 32 cycles.
match op {
Op::TexDepBar(_) => 0xc2,
Op::Sync(_) => 0x00, // Wait 16 cycles
_ => {
// TODO: when we support dual-issue this should check for
// both previous ops
let base = match prev_op {
Some(Op::ASt(_)) => 0x40,
_ => 0x20,
};
let delay = deps.delay;
debug_assert!(delay >= 1 && delay <= 32);
base | (delay - 1)
}
}
// 0x00: wait for 16 cycles
// 0x04: dual-issue with next instruction
// 0xc2: if TEXBAR
// 0x20 | 0x40: suspend for N+1 cycles (N = bitmask 0x1f)
// 0x40 only if prev_op is attribute store
// Unsure:
// 0x80: global memory bit
//
// TODO:
// - Dual issue (0x04)
// - Functional Unit tracking
}
pub trait KeplerInstructionEncoder {
/// Encode the instruction and push it into the "encoded" vec
fn encode_instr(
&self,
instr: &Instr,
labels: &FxHashMap<Label, usize>,
encoded: &mut Vec<u32>,
);
/// Prepare the scheduling instruction opcode-field and return a
/// subset where the actual scheduling information will be written
fn prepare_sched_instr<'a>(
&self,
sched_instr: &'a mut [u32; 2],
) -> impl BitMutViewable + 'a;
}
/// Helper function that encodes shaders for both KeplerA and KeplerB.
/// Difference in the encoders are handled by KeplerInstructionEncoder.
pub fn encode_kepler_shader<E>(encoder: &E, s: &Shader<'_>) -> Vec<u32>
where
E: KeplerInstructionEncoder,
{
const INSTR_LEN_BYTES: usize = 8;
assert!(s.functions.len() == 1);
let func = &s.functions[0];
// --- Compute label addresses ---
// We need a schedule instruction every 7 instructions, these don't
// define jump boundaries so we can have multible blocks in the same
// 7-instr group.
let mut ip = 0_usize;
let mut labels = FxHashMap::default();
for b in &func.blocks {
let num_sched = (ip / 7) + 1;
labels.insert(b.label, (ip + num_sched) * INSTR_LEN_BYTES);
ip += b.instrs.len();
}
// --- Real encoding ---
// Create an instruction iterator and iterate it in chunks of 7.
// fill the last chunk with a nop (it should never be executed).
let mut instr_iter =
func.blocks.iter().flat_map(|b| b.instrs.iter()).peekable();
let mut filling_instr = Instr {
pred: true.into(),
op: Op::Nop(OpNop { label: None }),
deps: InstrDeps::new(),
};
filling_instr.deps.set_delay(1);
let mut sched_chunk_gen = || {
if instr_iter.peek().is_none() {
return None;
}
Some([0; 7].map(|_| instr_iter.next().unwrap_or(&filling_instr)))
};
let mut encoded = Vec::new();
let mut prev_op = None;
while let Some(sched_chunk) = sched_chunk_gen() {
let sched_i = encoded.len();
let mut sched_instr = [0u32; 2];
encoded.extend(&sched_instr[..]); // Push now, will edit later
let mut bv = encoder.prepare_sched_instr(&mut sched_instr);
// There should be 8 bits for each instr in a scheduling block
debug_assert!(bv.bits() == 8 * 7);
for (i, instr) in sched_chunk.iter().enumerate() {
encoder.encode_instr(instr, &labels, &mut encoded);
let sched = calc_instr_sched(prev_op, &instr.op, &instr.deps);
bv.set_field(i * 8..(i + 1) * 8, sched);
prev_op = Some(&instr.op);
}
drop(bv);
encoded[sched_i] = sched_instr[0];
encoded[sched_i + 1] = sched_instr[1];
}
encoded
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+296
View File
@@ -0,0 +1,296 @@
// Copyright © 2022 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::*;
use crate::legalize::LegalizeBuilder;
use crate::sm120_instr_latencies::SM120Latency;
use crate::sm70_encode::*;
use crate::sm75_instr_latencies::SM75Latency;
use crate::sm80_instr_latencies::SM80Latency;
pub struct ShaderModel70 {
sm: u8,
}
impl ShaderModel70 {
pub fn new(sm: u8) -> Self {
assert!(sm >= 70);
Self { sm }
}
fn has_uniform_alu(&self) -> bool {
self.sm >= 73
}
fn instr_latency(&self, op: &Op, dst_idx: usize) -> u32 {
let file = match &op.dsts_as_slice()[dst_idx] {
Dst::None => return 0,
Dst::SSA(vec) => vec.file(),
Dst::Reg(reg) => reg.file(),
};
let (gpr_latency, pred_latency) = if self.sm < 80 {
match op {
// Double-precision float ALU
Op::DAdd(_)
| Op::DFma(_)
| Op::DMnMx(_)
| Op::DMul(_)
| Op::DSetP(_)
// Half-precision float ALU
| Op::HAdd2(_)
| Op::HFma2(_)
| Op::HMul2(_)
| Op::HSet2(_)
| Op::HSetP2(_)
| Op::HMnMx2(_) => if self.is_volta() {
// Volta is even slower
(13, 15)
} else {
(13, 14)
}
_ => (6, 13)
}
} else {
(6, 13)
};
// This is BS and we know it
match file {
RegFile::GPR => gpr_latency,
RegFile::UGPR => 12,
RegFile::Pred => pred_latency,
RegFile::UPred => 11,
RegFile::Bar => 0, // Barriers have a HW scoreboard
RegFile::Carry => 6,
RegFile::Mem => panic!("Not a register"),
}
}
}
impl ShaderModel for ShaderModel70 {
fn sm(&self) -> u8 {
self.sm
}
fn num_regs(&self, file: RegFile) -> u32 {
match file {
RegFile::GPR => 255 - self.hw_reserved_gprs(),
RegFile::UGPR => {
if !self.has_uniform_alu() {
0
} else if self.sm >= 100 {
80
} else {
63
}
}
RegFile::Pred => 7,
RegFile::UPred => {
if self.has_uniform_alu() {
7
} else {
0
}
}
RegFile::Carry => 0,
RegFile::Bar => 16,
RegFile::Mem => RegRef::MAX_IDX + 1,
}
}
fn hw_reserved_gprs(&self) -> u32 {
// On Volta+, 2 GPRs get burned for the program counter - see the
// footnote on table 2 of the volta whitepaper
// https://images.nvidia.com/content/volta-architecture/pdf/volta-architecture-whitepaper.pdf
2
}
fn crs_size(&self, max_crs_depth: u32) -> u32 {
assert!(max_crs_depth == 0);
0
}
fn op_can_be_uniform(&self, op: &Op) -> bool {
if !self.has_uniform_alu() {
return false;
}
match op {
Op::R2UR(_)
| Op::Redux(_)
| Op::S2R(_)
| Op::BMsk(_)
| Op::BRev(_)
| Op::Flo(_)
| Op::IAdd3(_)
| Op::IAdd3X(_)
| Op::IMad(_)
| Op::IMad64(_)
| Op::ISetP(_)
| Op::Lea(_)
| Op::LeaX(_)
| Op::Lop3(_)
| Op::Mov(_)
| Op::PLop3(_)
| Op::PopC(_)
| Op::Prmt(_)
| Op::PSetP(_)
| Op::Sel(_)
| Op::Sgxt(_)
| Op::Shf(_)
| Op::Shl(_)
| Op::Shr(_)
| Op::Vote(_)
| Op::Copy(_)
| Op::Pin(_)
| Op::Unpin(_) => true,
Op::Ldc(op) => op.offset.is_zero(),
// UCLEA USHL USHR
_ => false,
}
}
fn op_needs_scoreboard(&self, op: &Op) -> bool {
if op.no_scoreboard() {
return false;
}
if self.is_blackwell() {
SM120Latency::needs_scoreboards(op)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::needs_scoreboards(op)
} else if self.is_turing() {
SM75Latency::needs_scoreboards(op)
} else {
!op.has_fixed_latency(self.sm())
}
}
fn exec_latency(&self, op: &Op) -> u32 {
match op {
Op::Bar(_) | Op::MemBar(_) => {
if self.sm >= 80 {
6
} else {
5
}
}
Op::CCtl(_op) => {
// CCTL.C needs 8, CCTL.I needs 11
11
}
// Op::DepBar(_) => 4,
_ => 1, // TODO: co-issue
}
}
fn raw_latency(
&self,
write: &Op,
dst_idx: usize,
read: &Op,
src_idx: usize,
) -> u32 {
if self.is_blackwell() {
SM120Latency::raw(write, dst_idx, Some(read), src_idx)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::raw(write, dst_idx, Some(read), src_idx)
} else if self.is_turing() {
SM75Latency::raw(write, dst_idx, Some(read), src_idx)
} else {
self.instr_latency(write, dst_idx)
}
}
fn war_latency(
&self,
read: &Op,
src_idx: usize,
write: &Op,
dst_idx: usize,
) -> u32 {
if self.is_blackwell() {
SM120Latency::war(read, src_idx, write, dst_idx)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::war(read, src_idx, write, dst_idx)
} else if self.is_turing() {
SM75Latency::war(read, src_idx, write, dst_idx)
} else {
// We assume the source gets read in the first 4 cycles. We don't
// know how quickly the write will happen. This is all a guess.
4
}
}
fn waw_latency(
&self,
a: &Op,
a_dst_idx: usize,
a_has_pred: bool,
b: &Op,
b_dst_idx: usize,
) -> u32 {
if self.is_blackwell() {
SM120Latency::waw(a, a_dst_idx, b, b_dst_idx, a_has_pred)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::waw(a, a_dst_idx, b, b_dst_idx, a_has_pred)
} else if self.is_turing() {
SM75Latency::waw(a, a_dst_idx, b, b_dst_idx, a_has_pred)
} else {
// We know our latencies are wrong so assume the wrote could happen
// anywhere between 0 and instr_latency(a) cycles
self.instr_latency(a, a_dst_idx)
}
}
fn paw_latency(&self, write: &Op, dst_idx: usize) -> u32 {
if self.is_blackwell() {
SM120Latency::raw(write, dst_idx, None, 0)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::raw(write, dst_idx, None, 0)
} else if self.is_turing() {
SM75Latency::raw(write, dst_idx, None, 0)
} else if self.is_volta() {
match write {
Op::DSetP(_) | Op::HSetP2(_) => 15,
_ => 13,
}
} else {
13
}
}
fn latency_upper_bound(&self) -> u32 {
if self.is_blackwell() || self.is_ampere() || self.is_ada() {
30
} else if self.is_turing() {
25
} else {
15
}
}
fn worst_latency(&self, write: &Op, dst_idx: usize) -> u32 {
if self.is_blackwell() {
SM120Latency::raw(write, dst_idx, None, 0)
} else if self.is_ampere() || self.is_ada() {
SM80Latency::raw(write, dst_idx, None, 0)
} else if self.is_turing() {
SM75Latency::raw(write, dst_idx, None, 0)
} else {
self.instr_latency(write, dst_idx)
}
}
fn max_instr_delay(&self) -> u8 {
15
}
fn legalize_op(&self, b: &mut LegalizeBuilder, op: &mut Op) {
legalize_sm70_op(self, b, op);
}
fn encode_shader(&self, s: &Shader<'_>) -> Vec<u32> {
encode_sm70_shader(self, s)
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+609
View File
@@ -0,0 +1,609 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
extern crate bitview;
extern crate nvidia_headers;
use crate::ir::{
MeshShaderInfo, ShaderInfo, ShaderIoInfo, ShaderModel, ShaderModelInfo,
ShaderStageInfo, VtgIoInfo,
};
use bitview::{
BitMutView, BitMutViewable, BitView, BitViewable, SetBit, SetField,
};
use nak_bindings::*;
use nvidia_headers::classes::cla097::sph::*;
use std::ops::Range;
pub const _SPHV3_SHADER_HEADER_SIZE: usize = 20;
pub const SPHV4_SHADER_HEADER_SIZE: usize = 32;
pub const CURRENT_MAX_SHADER_HEADER_SIZE: usize = SPHV4_SHADER_HEADER_SIZE;
type SubSPHView<'a> = BitMutView<'a, [u32; CURRENT_MAX_SHADER_HEADER_SIZE]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShaderType {
Vertex,
TessellationInit,
Tessellation,
Geometry,
Fragment,
}
impl From<&ShaderStageInfo> for ShaderType {
fn from(value: &ShaderStageInfo) -> Self {
match value {
ShaderStageInfo::Vertex(_) | ShaderStageInfo::Task(_) => {
ShaderType::Vertex
}
ShaderStageInfo::Fragment(_) => ShaderType::Fragment,
ShaderStageInfo::Geometry(_) => ShaderType::Geometry,
ShaderStageInfo::TessellationInit(_) => {
ShaderType::TessellationInit
}
ShaderStageInfo::Tessellation(_) => ShaderType::Tessellation,
ShaderStageInfo::Mesh(info) => {
if info.has_task_shader {
ShaderType::Tessellation
} else {
ShaderType::Vertex
}
}
_ => panic!("Invalid ShaderStageInfo {:?}", value),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OutputTopology {
PointList,
LineStrip,
TriangleStrip,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PixelImap {
Unused,
Constant,
Perspective,
ScreenLinear,
}
impl From<PixelImap> for u8 {
fn from(value: PixelImap) -> u8 {
match value {
PixelImap::Unused => 0,
PixelImap::Constant => 1,
PixelImap::Perspective => 2,
PixelImap::ScreenLinear => 3,
}
}
}
#[derive(Debug)]
pub struct ShaderProgramHeader {
pub data: [u32; CURRENT_MAX_SHADER_HEADER_SIZE],
shader_type: ShaderType,
}
impl BitViewable for ShaderProgramHeader {
fn bits(&self) -> usize {
BitView::new(&self.data).bits()
}
fn get_bit_range_u64(&self, range: Range<usize>) -> u64 {
BitView::new(&self.data).get_bit_range_u64(range)
}
}
impl BitMutViewable for ShaderProgramHeader {
fn set_bit_range_u64(&mut self, range: Range<usize>, val: u64) {
BitMutView::new(&mut self.data).set_bit_range_u64(range, val);
}
}
impl ShaderProgramHeader {
pub fn new(shader_type: ShaderType, sm: u8) -> Self {
let mut res = Self {
data: [0; CURRENT_MAX_SHADER_HEADER_SIZE],
shader_type,
};
let sph_type = if shader_type == ShaderType::Fragment {
SPHV3_T1_SPH_TYPE_TYPE_02_PS
} else {
SPHV3_T1_SPH_TYPE_TYPE_01_VTG
};
let sph_version = if sm >= 73 { 4 } else { 3 };
res.set_sph_type(sph_type, sph_version);
res.set_shader_type(shader_type);
res
}
#[inline]
fn imap_system_values_ab(&mut self) -> SubSPHView<'_> {
BitMutView::new_subset(&mut self.data, 160..192)
}
#[inline]
fn imap_g_vtg(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 192..320)
}
#[inline]
fn imap_g_ps(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type == ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 192..448)
}
#[inline]
fn imap_system_values_c(&mut self) -> SubSPHView<'_> {
if self.shader_type == ShaderType::Fragment {
BitMutView::new_subset(&mut self.data, 464..480)
} else {
BitMutView::new_subset(&mut self.data, 336..352)
}
}
#[inline]
fn imap_system_values_d_vtg(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 392..400)
}
#[inline]
fn omap_system_values_ab(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 400..432)
}
#[inline]
fn omap_g(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 432..560)
}
#[inline]
fn omap_system_values_c(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 576..592)
}
#[inline]
fn imap_system_values_d_ps(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type == ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 560..576)
}
#[inline]
fn omap_target(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type == ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 576..608)
}
#[inline]
fn omap_system_values_d_vtg(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type != ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 632..640)
}
#[inline]
fn set_sph_type(&mut self, sph_type: u32, sph_version: u8) {
self.set_field(SPHV3_T1_SPH_TYPE, sph_type);
self.set_field(SPHV3_T1_VERSION, sph_version);
}
#[inline]
fn set_shader_type(&mut self, shader_type: ShaderType) {
self.set_field(
SPHV3_T1_SHADER_TYPE,
match shader_type {
ShaderType::Vertex => SPHV3_T1_SHADER_TYPE_VERTEX,
ShaderType::TessellationInit => {
SPHV3_T1_SHADER_TYPE_TESSELLATION_INIT
}
ShaderType::Tessellation => SPHV3_T1_SHADER_TYPE_TESSELLATION,
ShaderType::Geometry => SPHV3_T1_SHADER_TYPE_GEOMETRY,
ShaderType::Fragment => SPHV3_T1_SHADER_TYPE_PIXEL,
},
);
}
#[inline]
pub fn set_multiple_render_target_enable(&mut self, mrt_enable: bool) {
self.set_field(SPHV3_T1_MRT_ENABLE, mrt_enable);
}
#[inline]
pub fn set_kills_pixels(&mut self, kills_pixels: bool) {
self.set_field(SPHV3_T1_KILLS_PIXELS, kills_pixels);
}
#[inline]
pub fn set_does_global_store(&mut self, does_global_store: bool) {
self.set_field(SPHV3_T1_DOES_GLOBAL_STORE, does_global_store);
}
#[inline]
pub fn set_sass_version(&mut self, sass_version: u8) {
self.set_field(SPHV3_T1_SASS_VERSION, sass_version);
}
#[inline]
pub fn set_gs_passthrough_enable(&mut self, gs_passthrough_enable: bool) {
assert!(self.shader_type == ShaderType::Geometry);
self.set_bit(24, gs_passthrough_enable);
}
#[inline]
pub fn set_isbe_space_sharing_enable(
&mut self,
isbe_space_sharing_enable: bool,
) {
assert!(self.shader_type == ShaderType::Vertex);
self.set_bit(25, isbe_space_sharing_enable);
}
#[inline]
pub fn set_does_load_or_store(&mut self, does_load_or_store: bool) {
self.set_field(SPHV3_T1_DOES_LOAD_OR_STORE, does_load_or_store);
}
#[inline]
pub fn set_does_fp64(&mut self, does_fp64: bool) {
self.set_field(SPHV3_T1_DOES_FP64, does_fp64);
}
#[inline]
pub fn set_stream_out_mask(&mut self, stream_out_mask: u8) {
self.set_field(SPHV3_T1_STREAM_OUT_MASK, stream_out_mask);
}
#[inline]
pub fn set_shader_local_memory_size(
&mut self,
shader_local_memory_size: u64,
) {
assert!(shader_local_memory_size <= 0xffffffffffff);
assert!(shader_local_memory_size % 0x10 == 0);
let low = (shader_local_memory_size & 0xffffff) as u32;
let high = ((shader_local_memory_size >> 32) & 0xffffff) as u32;
self.set_field(SPHV3_T1_SHADER_LOCAL_MEMORY_LOW_SIZE, low);
self.set_field(SPHV3_T1_SHADER_LOCAL_MEMORY_HIGH_SIZE, high);
}
#[inline]
pub fn set_per_patch_attribute_count(
&mut self,
per_patch_attribute_count: u8,
) {
assert!(self.shader_type == ShaderType::TessellationInit);
self.set_field(
SPHV3_T1_PER_PATCH_ATTRIBUTE_COUNT,
per_patch_attribute_count,
);
// This is Kepler+
self.set_field(
SPHV3_T1_RESERVED_COMMON_B,
per_patch_attribute_count & 0xf,
);
self.set_field(148..152, per_patch_attribute_count >> 4);
}
#[inline]
pub fn set_threads_per_input_primitive(
&mut self,
threads_per_input_primitive: u8,
) {
self.set_field(
SPHV3_T1_THREADS_PER_INPUT_PRIMITIVE,
threads_per_input_primitive,
);
}
#[inline]
pub fn set_shader_local_memory_crs_size(
&mut self,
shader_local_memory_crs_size: u32,
) {
assert!(shader_local_memory_crs_size <= 0xffffff);
self.set_field(
SPHV3_T1_SHADER_LOCAL_MEMORY_CRS_SIZE,
shader_local_memory_crs_size,
);
}
#[inline]
pub fn set_output_topology(&mut self, output_topology: OutputTopology) {
self.set_field(
SPHV3_T1_OUTPUT_TOPOLOGY,
match output_topology {
OutputTopology::PointList => SPHV3_T1_OUTPUT_TOPOLOGY_POINTLIST,
OutputTopology::LineStrip => SPHV3_T1_OUTPUT_TOPOLOGY_LINESTRIP,
OutputTopology::TriangleStrip => {
SPHV3_T1_OUTPUT_TOPOLOGY_TRIANGLESTRIP
}
},
);
}
#[inline]
pub fn set_max_output_vertex_count(
&mut self,
max_output_vertex_count: u16,
) {
assert!(max_output_vertex_count <= 0xfff);
self.set_field(
SPHV3_T1_MAX_OUTPUT_VERTEX_COUNT,
max_output_vertex_count,
);
}
#[inline]
pub fn set_store_req_start(&mut self, store_req_start: u8) {
self.set_field(SPHV3_T1_STORE_REQ_START, store_req_start);
}
#[inline]
pub fn set_store_req_end(&mut self, store_req_end: u8) {
self.set_field(SPHV3_T1_STORE_REQ_END, store_req_end);
}
pub fn set_imap_system_values_ab(&mut self, val: u32) {
self.imap_system_values_ab().set_field(0..32, val);
}
pub fn set_imap_system_values_c(&mut self, val: u16) {
self.imap_system_values_c().set_field(0..16, val);
}
pub fn set_imap_system_values_d_vtg(&mut self, val: u8) {
assert!(self.shader_type != ShaderType::Fragment);
self.imap_system_values_d_vtg().set_field(0..8, val);
}
#[inline]
pub fn set_imap_vector_ps(&mut self, index: usize, value: PixelImap) {
assert!(index < 128);
assert!(self.shader_type == ShaderType::Fragment);
self.imap_g_ps()
.set_field(index * 2..(index + 1) * 2, u8::from(value));
}
#[inline]
pub fn set_imap_system_values_d_ps(
&mut self,
index: usize,
value: PixelImap,
) {
assert!(index < 8);
assert!(self.shader_type == ShaderType::Fragment);
self.imap_system_values_d_ps()
.set_field(index * 2..(index + 1) * 2, u8::from(value));
}
#[inline]
pub fn set_imap_vector_vtg(&mut self, index: usize, value: u32) {
assert!(index < 4);
assert!(self.shader_type != ShaderType::Fragment);
self.imap_g_vtg()
.set_field(index * 32..(index + 1) * 32, value);
}
#[inline]
pub fn set_omap_system_values_ab(&mut self, val: u32) {
self.omap_system_values_ab().set_field(0..32, val);
}
#[inline]
pub fn set_omap_system_values_c(&mut self, val: u16) {
self.omap_system_values_c().set_field(0..16, val);
}
pub fn set_omap_system_values_d_vtg(&mut self, val: u8) {
assert!(self.shader_type != ShaderType::Fragment);
self.omap_system_values_d_vtg().set_field(0..8, val);
}
#[inline]
pub fn set_omap_vector(&mut self, index: usize, value: u32) {
assert!(index < 4);
assert!(self.shader_type != ShaderType::Fragment);
self.omap_g().set_field(index * 32..(index + 1) * 32, value);
}
#[inline]
pub fn set_omap_targets(&mut self, value: u32) {
self.omap_target().set_field(0..32, value)
}
#[inline]
pub fn set_omap_sample_mask(&mut self, sample_mask: bool) {
assert!(self.shader_type == ShaderType::Fragment);
self.set_field(SPHV3_T2_OMAP_SAMPLE_MASK, sample_mask);
}
#[inline]
pub fn set_omap_depth(&mut self, depth: bool) {
assert!(self.shader_type == ShaderType::Fragment);
self.set_field(SPHV3_T2_OMAP_DEPTH, depth);
}
#[inline]
pub fn set_does_interlock(&mut self, does_interlock: bool) {
assert!(self.shader_type == ShaderType::Fragment);
self.set_bit(610, does_interlock);
}
#[inline]
pub fn set_uses_underestimate(&mut self, uses_underestimate: bool) {
assert!(self.shader_type == ShaderType::Fragment);
self.set_bit(611, uses_underestimate);
}
#[inline]
fn pervertex_imap_vector_ps(&mut self) -> SubSPHView<'_> {
assert!(self.shader_type == ShaderType::Fragment);
BitMutView::new_subset(&mut self.data, 672..800)
}
#[inline]
pub fn set_pervertex_imap_vector(&mut self, index: usize, value: u32) {
assert!(index < 4);
assert!(self.shader_type == ShaderType::Fragment);
self.pervertex_imap_vector_ps()
.set_field(index * 32..(index + 1) * 32, value);
}
}
fn encode_vtg_io(sph: &mut ShaderProgramHeader, io: &VtgIoInfo) {
sph.set_imap_system_values_ab(io.sysvals_in.ab);
sph.set_imap_system_values_c(io.sysvals_in.c);
sph.set_imap_system_values_d_vtg(io.sysvals_in_d);
for (index, value) in io.attr_in.iter().enumerate() {
sph.set_imap_vector_vtg(index, *value);
}
for (index, value) in io.attr_out.iter().enumerate() {
sph.set_omap_vector(index, *value);
}
sph.set_store_req_start(io.store_req_start);
sph.set_store_req_end(io.store_req_end);
sph.set_omap_system_values_ab(io.sysvals_out.ab);
sph.set_omap_system_values_c(io.sysvals_out.c);
sph.set_omap_system_values_d_vtg(io.sysvals_out_d);
}
pub fn encode_header(
sm: &ShaderModelInfo,
shader_info: &ShaderInfo,
fs_key: Option<&nak_fs_key>,
) -> [u32; CURRENT_MAX_SHADER_HEADER_SIZE] {
if let ShaderStageInfo::Compute(_) = shader_info.stage {
return [0_u32; CURRENT_MAX_SHADER_HEADER_SIZE];
}
let mut sph =
ShaderProgramHeader::new(ShaderType::from(&shader_info.stage), sm.sm());
let slm_size = shader_info.slm_size.next_multiple_of(16);
sph.set_sass_version(1);
sph.set_does_load_or_store(
shader_info.uses_global_mem || (sm.is_kepler() && slm_size > 0),
);
sph.set_does_global_store(shader_info.writes_global_mem);
sph.set_does_fp64(shader_info.uses_fp64);
sph.set_shader_local_memory_size(slm_size.into());
let crs_size = sm.crs_size(shader_info.max_crs_depth);
sph.set_shader_local_memory_crs_size(crs_size);
match &shader_info.io {
ShaderIoInfo::Vtg(io) => encode_vtg_io(&mut sph, io),
ShaderIoInfo::Fragment(io) => {
sph.set_imap_system_values_ab(io.sysvals_in.ab);
sph.set_imap_system_values_c(io.sysvals_in.c);
for (index, imap) in io.sysvals_in_d.iter().enumerate() {
sph.set_imap_system_values_d_ps(index, *imap);
}
for (index, imap) in io.attr_in.iter().enumerate() {
sph.set_imap_vector_ps(index, *imap);
}
let uses_underestimate =
fs_key.is_some_and(|key| key.uses_underestimate);
// This isn't so much a "Do we write multiple render targets?" bit
// as a "Should color0 be broadcast to all render targets?" bit. In
// other words, it's the gl_FragCoord behavior, not gl_FragData.
//
// For now, we always set it to true because Vulkan requires
// explicit fragment output locations.
sph.set_multiple_render_target_enable(true);
sph.set_omap_sample_mask(io.writes_sample_mask);
sph.set_omap_depth(io.writes_depth);
sph.set_omap_targets(io.writes_color);
sph.set_uses_underestimate(uses_underestimate);
for (index, value) in io.barycentric_attr_in.iter().enumerate() {
sph.set_pervertex_imap_vector(index, *value);
}
}
_ => {}
}
match &shader_info.stage {
ShaderStageInfo::Vertex(stage) => {
sph.set_isbe_space_sharing_enable(stage.isbe_space_sharing_enable);
}
ShaderStageInfo::Fragment(stage) => {
let zs_self_dep = fs_key.is_some_and(|key| key.zs_self_dep);
sph.set_kills_pixels(stage.uses_kill || zs_self_dep);
sph.set_does_interlock(stage.does_interlock);
}
ShaderStageInfo::Geometry(stage) => {
sph.set_gs_passthrough_enable(stage.passthrough_enable);
sph.set_stream_out_mask(stage.stream_out_mask);
sph.set_threads_per_input_primitive(
stage.threads_per_input_primitive,
);
sph.set_output_topology(stage.output_topology);
sph.set_max_output_vertex_count(stage.max_output_vertex_count);
}
ShaderStageInfo::TessellationInit(stage) => {
sph.set_per_patch_attribute_count(stage.per_patch_attribute_count);
sph.set_threads_per_input_primitive(stage.threads_per_patch);
}
ShaderStageInfo::Compute(_) => {
panic!("Compute shaders don't have a SPH!")
}
_ => {}
};
sph.data
}
pub fn encode_gs_mesh_header(
sm: u8,
mesh_info: &MeshShaderInfo,
) -> [u32; CURRENT_MAX_SHADER_HEADER_SIZE] {
// If there is no usage of per primitive output, we can skip the GS stage.
if !mesh_info.has_gs_sph {
return [0_u32; CURRENT_MAX_SHADER_HEADER_SIZE];
}
let mut sph = ShaderProgramHeader::new(ShaderType::Geometry, sm);
sph.set_sass_version(1);
sph.set_gs_passthrough_enable(true);
sph.set_stream_out_mask(0);
sph.set_threads_per_input_primitive(1);
sph.set_output_topology(OutputTopology::PointList);
sph.set_max_output_vertex_count(1);
encode_vtg_io(&mut sph, &mesh_info.primitive_io);
sph.data
}
File diff suppressed because it is too large. Load diff
+338
View File
@@ -0,0 +1,338 @@
// Copyright © 2025 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::ir::{HasRegFile, RegFile};
use compiler::bitset::IntoBitIndex;
use compiler::lower_bounded::*;
use std::fmt;
use std::ops::{Deref, DerefMut};
type SSAValueInner = LowerBoundedU32<{ SSAValue::IDX_DELTA }>;
/// An SSA value
///
/// Each SSA in NAK represents a single 32-bit or 1-bit (if a predicate) value
/// which must either be spilled to memory or allocated space in the specified
/// register file. Whenever more data is required such as a 64-bit memory
/// address, double-precision float, or a vec4 texture result, multiple SSA
/// values are used.
///
/// Each SSA value logically contains two things: an index and a register file.
/// It is required that each index refers to a unique SSA value, regardless of
/// register file. This way the index can be used to index tightly-packed data
/// structures such as bitsets without having to determine separate ranges for
/// each register file.
#[repr(transparent)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct SSAValue {
packed: SSAValueInner,
}
impl SSAValue {
const IDX_DELTA: u32 = 17;
/// Returns an SSA value with the given register file and index
fn new(file: RegFile, idx: u32) -> SSAValue {
assert!(idx < (1 << 29) - SSAValue::IDX_DELTA);
let mut packed = idx + SSAValue::IDX_DELTA;
assert!(u8::from(file) < 8);
packed |= u32::from(u8::from(file)) << 29;
SSAValue {
packed: packed.try_into().unwrap(),
}
}
/// Returns the index of this SSA value
pub fn idx(&self) -> u32 {
(self.packed.get() & 0x1fffffff) - SSAValue::IDX_DELTA
}
}
impl HasRegFile for SSAValue {
/// Returns the register file of this SSA value
fn file(&self) -> RegFile {
RegFile::try_from((self.packed.get() >> 29) as u8).unwrap()
}
}
impl IntoBitIndex for SSAValue {
fn into_bit_index(self) -> usize {
// Indices are guaranteed unique by the allocator
self.idx().try_into().unwrap()
}
}
impl fmt::Display for SSAValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "%{}{}", self.file().fmt_prefix(), self.idx())
}
}
impl fmt::Debug for SSAValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
type SSARefSmall =
LowerBoundedU32Array<{ SSAValue::IDX_DELTA }, { SSARef::SMALL_MAX_IDX }>;
type SSARefLarge =
LowerBoundedU32Array<{ SSAValue::IDX_DELTA }, { SSARef::LARGE_MAX_IDX }>;
#[derive(Clone, Eq, Hash, PartialEq)]
enum SSARefInner {
Small(SSARefSmall),
Large(Box<SSARefLarge>),
}
impl SSARefInner {
fn push(&mut self, val: SSAValue) {
match self {
SSARefInner::Small(small) => {
if small.try_push(val.packed).is_ok() {
return;
}
SSARef::cold();
let mut large: Box<SSARefLarge> = Default::default();
for s in small.into_iter() {
large.try_push(s).expect("Small has to fit in large");
}
large.try_push(val.packed).unwrap();
*self = Self::Large(large);
}
SSARefInner::Large(arr) => {
SSARef::cold();
arr.try_push(val.packed)
.expect("Too many vector components for SSARef");
}
}
}
}
/// A reference to one or more SSA values
///
/// Because each SSA value represents a single 1 or 32-bit scalar, we need a way
/// to reference multiple SSA values for instructions which read or write
/// multiple registers in the same source. When the register allocator runs,
/// all the SSA values in a given SSA ref will be placed in consecutive
/// registers, with the base register aligned to the number of values, aligned
/// to the next power of two.
///
/// An SSA reference can reference between 1 and 16 SSA values. It dereferences
/// to a slice for easy access to individual SSA values. The structure is
/// designed so that is always 16B, regardless of how many SSA values are
/// referenced so it's easy and fairly cheap to clone and embed in other
/// structures.
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct SSARef {
v: SSARefInner,
}
#[cfg(target_arch = "x86_64")]
const _: () = {
debug_assert!(size_of::<SSARef>() == 16);
};
impl SSARef {
const SMALL_MAX_IDX: usize = 3;
const LARGE_MAX_IDX: usize = 15;
/// Returns a new SSA reference.
///
/// # Panics
///
/// This method will panic if the number of SSA values in the slice do not
/// fit in an SSARef.
#[inline]
pub fn new(comps: &[SSAValue]) -> SSARef {
assert!(comps.len() > 0);
let mut v = SSARefInner::Small(Default::default());
for ssa in comps {
v.push(*ssa);
}
SSARef { v }
}
/// Constructs an SSA reference from an iterator of SSA values.
///
/// # Panics
///
/// This method will panic if the number of SSA values in the slice do not
/// fit in an SSARef.
fn from_iter(it: impl ExactSizeIterator<Item = SSAValue>) -> Self {
assert!(it.len() > 0);
let mut v = SSARefInner::Small(Default::default());
for ssa in it {
v.push(ssa);
}
SSARef { v }
}
/// Returns the number of components in this SSA reference.
pub fn comps(&self) -> u8 {
match &self.v {
SSARefInner::Small(x) => x.len() as u8,
SSARefInner::Large(x) => {
Self::cold();
x.len() as u8
}
}
}
#[cold]
#[inline]
fn cold() {}
}
impl Deref for SSARef {
type Target = [SSAValue];
fn deref(&self) -> &[SSAValue] {
let s = match &self.v {
SSARefInner::Small(x) => x.as_slice(),
SSARefInner::Large(x) => {
Self::cold();
x.as_slice()
}
};
// SAFETY: SSAValue is repr(transparent) of SSAValueInner
unsafe { std::mem::transmute(s) }
}
}
impl DerefMut for SSARef {
fn deref_mut(&mut self) -> &mut [SSAValue] {
let s = match &mut self.v {
SSARefInner::Small(x) => x.as_mut_slice(),
SSARefInner::Large(x) => {
Self::cold();
x.as_mut_slice()
}
};
// SAFETY: SSAValue is repr(transparent) of SSAValueInner
unsafe { std::mem::transmute(s) }
}
}
impl TryFrom<&[SSAValue]> for SSARef {
type Error = &'static str;
fn try_from(comps: &[SSAValue]) -> Result<Self, Self::Error> {
if comps.len() == 0 {
Err("Empty vector")
} else if comps.len() > SSARefLarge::MAX_LEN {
Err("Too many vector components")
} else {
Ok(SSARef::new(comps))
}
}
}
impl TryFrom<Vec<SSAValue>> for SSARef {
type Error = &'static str;
fn try_from(comps: Vec<SSAValue>) -> Result<Self, Self::Error> {
SSARef::try_from(&comps[..])
}
}
macro_rules! impl_ssa_ref_from_arr {
($n: expr) => {
impl From<[SSAValue; $n]> for SSARef {
fn from(comps: [SSAValue; $n]) -> Self {
SSARef::new(&comps[..])
}
}
};
}
impl_ssa_ref_from_arr!(1);
impl_ssa_ref_from_arr!(2);
impl_ssa_ref_from_arr!(3);
impl_ssa_ref_from_arr!(4);
impl From<SSAValue> for SSARef {
fn from(val: SSAValue) -> Self {
[val].into()
}
}
impl fmt::Display for SSARef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.comps() == 1 {
write!(f, "{}", self[0])
} else {
write!(f, "{{")?;
for (i, v) in self.iter().enumerate() {
if i != 0 {
write!(f, " ")?;
}
write!(f, "{}", v)?;
}
write!(f, "}}")
}
}
}
impl HasRegFile for SSARef {
fn file(&self) -> RegFile {
(&self[..]).file()
}
}
#[test]
fn test_ssa_ref_round_trip() {
for len in 1..16 {
let vec: Vec<_> = (0..len)
.map(|i| SSAValue::new(RegFile::GPR, 1337 ^ i ^ len))
.collect();
let ssa_ref = SSARef::new(&vec);
assert_eq!(ssa_ref[..], vec[..]);
}
}
pub struct SSAValueAllocator {
count: u32,
}
/// An allocator for SSA values.
///
/// This is the only valid way to create SSAValues. At most one SSA value
/// allocator may exist per shader to ensure the invariant that SSA value
/// indices are unique.
impl SSAValueAllocator {
/// Creates a new SSA value allocator.
pub fn new() -> SSAValueAllocator {
SSAValueAllocator { count: 0 }
}
#[allow(dead_code)]
pub fn max_idx(&self) -> u32 {
self.count
}
/// Allocates an SSA value.
pub fn alloc(&mut self, file: RegFile) -> SSAValue {
let idx = self.count;
self.count += 1;
SSAValue::new(file, idx)
}
/// Allocates multiple SSA values and returns them as an SSA reference.
pub fn alloc_vec(&mut self, file: RegFile, comps: u8) -> SSARef {
SSARef::from_iter((0..comps).map(|_| self.alloc(file)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssa_value_print() {
let ssa = SSAValue::new(RegFile::UPred, 42);
assert_eq!(format!("{}", ssa), "%up42");
assert_eq!(format!("{:?}", ssa), "%up42");
}
}
+404
View File
@@ -0,0 +1,404 @@
// Copyright © 2023 Collabora, Ltd.
// SPDX-License-Identifier: MIT
use crate::api::{GetDebugFlags, DEBUG};
use crate::ir::*;
use crate::liveness::{BlockLiveness, Liveness, SimpleLiveness};
use compiler::cfg::CFG;
use rustc_hash::FxHashMap;
use std::iter::Peekable;
struct MergedIter<I: Iterator> {
a: Peekable<I>,
b: Peekable<I>,
}
impl<I: Iterator> MergedIter<I> {
fn new(a: I, b: I) -> Self {
Self {
a: a.peekable(),
b: b.peekable(),
}
}
}
impl<I: Iterator> Iterator for MergedIter<I>
where
<I as Iterator>::Item: Ord,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<<I as Iterator>::Item> {
if let Some(a) = self.a.peek() {
if let Some(b) = self.b.peek() {
if a <= b {
self.a.next()
} else {
self.b.next()
}
} else {
self.a.next()
}
} else {
self.b.next()
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let (a_max, a_size) = self.a.size_hint();
let (b_max, b_size) = self.b.size_hint();
(a_max + b_max, a_size.zip(b_size).map(|(a, b)| a + b))
}
}
enum CoalesceItem {
SSA(SSAValue),
Phi(Phi),
}
struct CoalesceNode {
set: usize,
block: usize,
ip_1: usize,
item: CoalesceItem,
}
struct CoalesceSet {
nodes: Vec<usize>,
}
struct CoalesceGraph<'a> {
live: &'a SimpleLiveness,
nodes: Vec<CoalesceNode>,
sets: Vec<CoalesceSet>,
ssa_node: FxHashMap<SSAValue, usize>,
phi_node_file: FxHashMap<Phi, (usize, RegFile)>,
}
impl<'a> CoalesceGraph<'a> {
fn new(live: &'a SimpleLiveness) -> Self {
Self {
live: live,
nodes: Vec::new(),
sets: Vec::new(),
ssa_node: Default::default(),
phi_node_file: Default::default(),
}
}
fn add_ssa(&mut self, ssa: SSAValue) {
debug_assert!(self.sets.is_empty());
// Set it to usize::MAX for now. We'll update later
if self.ssa_node.insert(ssa, usize::MAX).is_none() {
let (block, ip) = self.live.def_block_ip(&ssa);
self.nodes.push(CoalesceNode {
set: usize::MAX,
block: block,
ip_1: ip + 1,
item: CoalesceItem::SSA(ssa),
});
}
}
fn add_phi_dst(&mut self, phi: Phi, file: RegFile, block: usize) {
debug_assert!(self.sets.is_empty());
// Record the register file now. We'll set the node later
let old = self.phi_node_file.insert(phi, (usize::MAX, file));
debug_assert!(old.is_none());
self.nodes.push(CoalesceNode {
set: usize::MAX,
block: block,
ip_1: 0,
item: CoalesceItem::Phi(phi),
});
}
fn add_phi_src(&mut self, phi: Phi, block: usize) {
debug_assert!(self.sets.is_empty());
self.nodes.push(CoalesceNode {
set: usize::MAX,
block: block,
ip_1: usize::MAX,
item: CoalesceItem::Phi(phi),
});
}
fn init_sets<N>(&mut self, cfg: &CFG<N>) {
// Sort the nodes by dom_dfs_pre_index followed by ip+1. Stash the
// dom_dfs_pre_index in the set for now. We don't actually fill out
// the set field until later.
for n in self.nodes.iter_mut() {
n.set = cfg.dom_dfs_pre_index(n.block);
}
self.nodes
.sort_by(|a, b| a.set.cmp(&b.set).then(a.ip_1.cmp(&b.ip_1)));
for ni in 0..self.nodes.len() {
match &self.nodes[ni].item {
CoalesceItem::SSA(ssa) => {
let old = self.ssa_node.insert(*ssa, ni);
debug_assert!(old == Some(usize::MAX));
self.nodes[ni].set = self.sets.len();
self.sets.push(CoalesceSet { nodes: vec![ni] });
}
CoalesceItem::Phi(phi) => {
let (pn, _) = self.phi_node_file.get_mut(phi).unwrap();
// We only want one set per phi and phi_node contains the
// index to any one of the nodes.
if *pn == usize::MAX {
self.nodes[ni].set = self.sets.len();
self.sets.push(CoalesceSet { nodes: vec![ni] });
*pn = ni;
} else {
let s = self.nodes[*pn].set;
self.nodes[ni].set = s;
}
}
}
}
}
fn node_dominates<N>(&self, p: usize, c: usize, cfg: &CFG<N>) -> bool {
if self.nodes[p].block == self.nodes[c].block {
self.nodes[p].ip_1 <= self.nodes[c].ip_1
} else {
cfg.dominates(self.nodes[p].block, self.nodes[c].block)
}
}
fn phi_ssa_interferes(&self, phi: &CoalesceNode, ssa: &SSAValue) -> bool {
if phi.ip_1 == 0 {
self.live.block_live(phi.block).is_live_in(ssa)
} else {
debug_assert!(phi.ip_1 == usize::MAX);
self.live.block_live(phi.block).is_live_out(ssa)
}
}
fn nodes_interfere(&self, a: usize, b: usize) -> bool {
let a = &self.nodes[a];
let b = &self.nodes[b];
match &a.item {
CoalesceItem::SSA(a_ssa) => match &b.item {
CoalesceItem::SSA(b_ssa) => self.live.interferes(a_ssa, b_ssa),
CoalesceItem::Phi(_) => self.phi_ssa_interferes(b, a_ssa),
},
CoalesceItem::Phi(_) => match &b.item {
CoalesceItem::SSA(b_ssa) => self.phi_ssa_interferes(a, b_ssa),
CoalesceItem::Phi(_) => {
// Phi nodes represent the temporary SSA value made between
// the parallel copy and the phi in the Boissinot algorithm
// so they interfere if and only if they're in the same
// block and both at the start or both at the end.
a.block == b.block && a.ip_1 == b.ip_1
}
},
}
}
pub fn sets_interfere<N>(&self, a: usize, b: usize, cfg: &CFG<N>) -> bool {
let a = &self.sets[a];
let b = &self.sets[b];
// Stack of nodes which dominate the current node
let mut dom = Vec::new();
for n in MergedIter::new(a.nodes.iter(), b.nodes.iter()) {
while let Some(p) = dom.last() {
if !self.node_dominates(*p, *n, cfg) {
dom.pop();
} else {
break;
}
}
if let Some(p) = dom.last() {
if self.nodes_interfere(*n, *p) {
return true;
}
}
dom.push(*n);
}
false
}
pub fn sets_merge(&mut self, a: usize, b: usize) -> usize {
let a_nodes = std::mem::take(&mut self.sets[a].nodes);
let b_nodes = std::mem::take(&mut self.sets[b].nodes);
let nodes = MergedIter::new(a_nodes.into_iter(), b_nodes.into_iter());
self.sets[a].nodes = nodes
.inspect(|&n| {
self.nodes[n].set = a;
})
.collect();
a
}
pub fn ssa_set(&self, ssa: &SSAValue) -> usize {
self.nodes[*self.ssa_node.get(ssa).unwrap()].set
}
pub fn phi_set_file(&self, phi: &Phi) -> (usize, RegFile) {
let (n, file) = self.phi_node_file.get(phi).unwrap();
(self.nodes[*n].set, *file)
}
}
impl Function {
/// Convert a function to CSSA (Conventional SSA) form
///
/// In "Translating Out of Static Single Assignment Form" by Sreedhar, et.
/// al., they define CSSA form via what they call the Phi Congruence
/// Property:
///
/// > The occurrences of all resources which belong to the same phi
/// > congruence class in a program can be replaced by a representative
/// > resource. After the replacement, the phi instruction can be
/// > eliminated without violating the semantics of the original program.
///
/// A more compiler-theoretic definition of CSSA form is a version of SSA
/// form in which, for each phi, none of the SSA values involved in the phi
/// (either as a source or destination) interfere. While most of the papers
/// discussing CSSA form do so in the context of out-of-SSA, this property
/// is also useful for SSA-based spilling and register allocation.
///
/// Our implementation is based on the algorithm described in "Revisiting
/// Out-of-SSA Translation for Correctness, Code Quality, and Effciency" by
/// Boissinot et. al. The primary difference between this algorithm and
/// the one in that paper is that we don't actually insert parallel copies
/// and remove redundant entries. Instead, we treat OpPhiSrcs and OpPhiDsts
/// as as the parallel copies with the phi index standing in for all of the
/// SSA values used directly by the phi. Then, instead of removing copies
/// where the source and destination don't interfere, we insert copies
/// whenever the source or destination and phi index do interfere. This
/// lets us avoid inserting pointless instructions.
pub fn to_cssa(&mut self) {
let live = SimpleLiveness::for_function(self);
let mut cg = CoalesceGraph::new(&live);
for (bi, b) in self.blocks.iter().enumerate() {
if let Some(phi) = b.phi_dsts() {
for (idx, dst) in phi.dsts.iter() {
let vec = dst.as_ssa().unwrap();
debug_assert!(vec.comps() == 1);
cg.add_ssa(vec[0]);
cg.add_phi_dst(*idx, vec[0].file(), bi);
}
}
if let Some(phi) = b.phi_srcs() {
for (idx, src) in phi.srcs.iter() {
if let SrcRef::SSA(vec) = &src.src_ref {
debug_assert!(vec.comps() == 1);
cg.add_ssa(vec[0]);
}
cg.add_phi_src(*idx, bi);
}
}
}
cg.init_sets(&self.blocks);
for bi in 0..self.blocks.len() {
let block_instrs = std::mem::take(&mut self.blocks[bi].instrs);
let mut instrs = Vec::new();
for mut instr in block_instrs.into_iter() {
match &mut instr.op {
Op::PhiDsts(phi) => {
let mut pcopy = OpParCopy::new();
for (idx, dst) in phi.dsts.iter_mut() {
let (ps, file) = cg.phi_set_file(idx);
let vec = dst.as_ssa().unwrap();
debug_assert!(vec.comps() == 1);
debug_assert!(vec[0].file() == file);
let ds = cg.ssa_set(&vec[0]);
if !cg.sets_interfere(ps, ds, &self.blocks) {
cg.sets_merge(ps, ds);
continue;
}
let tmp = self.ssa_alloc.alloc(file);
let old_dst = std::mem::replace(dst, tmp.into());
pcopy.push(old_dst, tmp.into());
}
instrs.push(instr);
if !pcopy.is_empty() {
if DEBUG.annotate() {
instrs.push(Instr::new(OpAnnotate {
annotation: "generated by to_cssa".into(),
}));
}
instrs.push(Instr::new(pcopy));
}
}
Op::PhiSrcs(phi) => {
let mut pcopy = OpParCopy::new();
for (idx, src) in phi.srcs.iter_mut() {
let (ps, file) = cg.phi_set_file(idx);
debug_assert!(src.is_unmodified());
if let SrcRef::SSA(vec) = &src.src_ref {
debug_assert!(vec.comps() == 1);
if vec[0].file() == file {
let ss = cg.ssa_set(&vec[0]);
if cg.sets_interfere(ps, ss, &self.blocks) {
let tmp = self.ssa_alloc.alloc(file);
let old_src =
std::mem::replace(src, tmp.into());
pcopy.push(tmp.into(), old_src);
} else {
cg.sets_merge(ps, ss);
}
continue;
}
}
// Non-SSA sources or sources with a different file
// from the destination get an actual copy
// instruction and are not considered part of the
// parallel copy.
let tmp = self.ssa_alloc.alloc(file);
if DEBUG.annotate() {
instrs.push(Instr::new(OpAnnotate {
annotation: "generated by to_cssa".into(),
}));
}
let old_src = std::mem::replace(src, tmp.into());
instrs.push(Instr::new(OpCopy {
dst: tmp.into(),
src: old_src,
}));
}
if !pcopy.is_empty() {
if DEBUG.annotate() {
instrs.push(Instr::new(OpAnnotate {
annotation: "generated by to_cssa".into(),
}));
}
instrs.push(Instr::new(pcopy));
}
instrs.push(instr);
}
_ => instrs.push(instr),
}
}
self.blocks[bi].instrs = instrs;
}
}
}
+293
View File
@@ -0,0 +1,293 @@
// Copyright © 2024 Mel Henning
// SPDX-License-Identifier: MIT
use std::collections::HashMap;
use std::hash::{BuildHasher, Hash};
#[derive(Copy, Clone)]
struct Root<X: Copy> {
size: usize,
representative: X,
}
#[derive(Copy, Clone)]
enum Node<X: Copy> {
Child { parent_idx: usize },
Root(Root<X>),
}
/// Union-find structure
///
/// This implementation follows Tarjan and van Leeuwen - specifically the
/// "link by size" and "halving" variant.
///
/// Robert E. Tarjan and Jan van Leeuwen. 1984. Worst-case Analysis of Set
/// Union Algorithms. J. ACM 31, 2 (April 1984), 245–281.
/// <https://doi.org/10.1145/62.2160>
pub struct UnionFind<X: Copy + Hash + Eq, H: BuildHasher + Default> {
idx_map: HashMap<X, usize, H>,
nodes: Vec<Node<X>>,
}
impl<X: Copy + Hash + Eq, H: BuildHasher + Default> UnionFind<X, H> {
/// Create a new union-find structure
///
/// At initialization, each possible value is in its own set
pub fn new() -> Self {
UnionFind {
idx_map: Default::default(),
nodes: Vec::new(),
}
}
fn find_root(&mut self, mut idx: usize) -> (usize, Root<X>) {
loop {
match self.nodes[idx] {
Node::Child { parent_idx } => {
match self.nodes[parent_idx] {
Node::Child {
parent_idx: grandparent_idx,
} => {
// "Halving" in Tarjan and van Leeuwen
self.nodes[idx] = Node::Child {
parent_idx: grandparent_idx,
};
idx = grandparent_idx;
}
Node::Root(parent_root) => {
return (parent_idx, parent_root)
}
}
}
Node::Root(root) => return (idx, root),
}
}
}
/// Find the representative element for x
pub fn find(&mut self, x: X) -> X {
match self.idx_map.get(&x) {
Some(&idx) => {
let (_, Root { representative, .. }) = self.find_root(idx);
representative
}
None => x,
}
}
fn map_or_create(&mut self, x: X) -> usize {
*self.idx_map.entry(x).or_insert_with(|| {
self.nodes.push(Node::Root(Root {
size: 1,
representative: x,
}));
self.nodes.len() - 1
})
}
/// Union the sets containing a and b
///
/// The representative for a will become the representative of
/// the combined set
pub fn union(&mut self, a: X, b: X) {
if a == b {
return;
}
let a_idx = self.map_or_create(a);
let b_idx = self.map_or_create(b);
let (a_root_idx, a_root) = self.find_root(a_idx);
let (b_root_idx, b_root) = self.find_root(b_idx);
if a_root_idx != b_root_idx {
// Keep the tree balanced
let (new_root_idx, new_child_idx) = if a_root.size >= b_root.size {
(a_root_idx, b_root_idx)
} else {
(b_root_idx, a_root_idx)
};
self.nodes[new_root_idx] = Node::Root(Root {
size: a_root.size + b_root.size,
representative: a_root.representative,
});
self.nodes[new_child_idx] = Node::Child {
parent_idx: new_root_idx,
};
}
}
/// Return true if find() is the identity mapping
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
}
#[cfg(test)]
mod tests {
use crate::union_find::Node;
use crate::union_find::UnionFind;
use std::cmp::max;
use std::hash::BuildHasher;
use std::hash::Hash;
use std::hash::RandomState;
fn ceil_log2(x: usize) -> u32 {
assert!(x > 0);
usize::BITS - (x - 1).leading_zeros()
}
struct HeightInfo {
height: u32,
size: usize,
}
pub struct HeightCalc<'a, X: Copy + Hash + Eq, H: BuildHasher + Default> {
uf: &'a UnionFind<X, H>,
downward_edges: Vec<Vec<usize>>,
}
impl<'a, X: Copy + Hash + Eq, H: BuildHasher + Default> HeightCalc<'a, X, H> {
fn new(uf: &'a UnionFind<X, H>) -> Self {
let mut downward_edges: Vec<Vec<usize>> =
uf.nodes.iter().map(|_| Vec::new()).collect();
for (i, node) in uf.nodes.iter().enumerate() {
if let Node::Child { parent_idx } = node {
downward_edges[*parent_idx].push(i);
}
}
HeightCalc { uf, downward_edges }
}
fn calc_info(&self, idx: usize) -> HeightInfo {
let mut result = HeightInfo { height: 0, size: 1 };
for child in &self.downward_edges[idx] {
let child_result = self.calc_info(*child);
result.height = max(result.height, child_result.height + 1);
result.size += child_result.size;
}
result
}
fn check_roots(&self) -> u32 {
let mut total_size = 0;
let mut max_height = 0;
for (i, node) in self.uf.nodes.iter().enumerate() {
if let Node::Root(root) = node {
let info = self.calc_info(i);
assert_eq!(root.size, info.size);
total_size += info.size;
max_height = max(max_height, info.height);
let max_expected_height = ceil_log2(root.size + 1) - 1;
if info.height > max_expected_height {
eprintln!(
"height {}\t max_expected_height {}\t size {}",
info.height, max_expected_height, info.size
);
}
assert!(info.height <= max_expected_height);
}
}
assert_eq!(total_size, self.uf.nodes.len());
assert_eq!(total_size, self.uf.idx_map.len());
max_height
}
pub fn check(uf: &'a UnionFind<X, H>) -> u32 {
HeightCalc::new(uf).check_roots()
}
}
#[test]
fn test_basic() {
let mut f = UnionFind::<u32, RandomState>::new();
assert_eq!(f.find(10), 10);
assert_eq!(f.find(12), 12);
f.union(10, 12);
f.union(11, 13);
HeightCalc::check(&f);
assert_eq!(f.find(13), 11);
assert_eq!(f.find(12), 10);
assert_eq!(f.find(11), 11);
assert_eq!(f.find(10), 10);
f.union(12, 13);
HeightCalc::check(&f);
assert_eq!(f.find(13), 10);
assert_eq!(f.find(12), 10);
assert_eq!(f.find(11), 10);
assert_eq!(f.find(10), 10);
assert_eq!(f.find(14), 14);
HeightCalc::check(&f);
// Union the set with itself
f.union(11, 10);
HeightCalc::check(&f);
assert_eq!(f.find(13), 10);
assert_eq!(f.find(12), 10);
assert_eq!(f.find(11), 10);
assert_eq!(f.find(10), 10);
}
#[test]
fn test_chain_a_height() {
let mut f = UnionFind::<u32, RandomState>::new();
for i in 0..1000 {
f.union(i, i + 1);
HeightCalc::check(&f);
}
assert_eq!(f.find(1000), 0);
}
#[test]
fn test_chain_b_height() {
let mut f = UnionFind::<u32, RandomState>::new();
for i in 0..1000 {
f.union(i + 1, i);
HeightCalc::check(&f);
}
assert_eq!(f.find(0), 1000);
}
#[test]
fn test_binary_tree_height() {
let height = 8;
let count = 1 << height;
let mut f = UnionFind::<usize, RandomState>::new();
for current_height in 0..height {
let stride = 1 << current_height;
for i in (0..count).step_by(2 * stride) {
f.union(i, i + stride);
}
let actual_height = HeightCalc::check(&f);
// actual_height can vary based on tiebreaker condition
assert!(
actual_height == current_height
|| actual_height == current_height + 1
);
}
// Check path halving
let actual_height_before = HeightCalc::check(&f);
for i in 0..count {
assert_eq!(f.find(i), 0);
}
let actual_height_after = HeightCalc::check(&f);
assert!(actual_height_after <= actual_height_before.div_ceil(2));
}
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright © 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nouveau_bo.h"
#include "nouveau_context.h"
#include "nouveau_device.h"
#include <xf86drm.h>
#include "drm-uapi/nouveau_drm.h"
#define DRM_RS_IOCTL(FOO) \
DRM_RS_IOCTL_##FOO = DRM_IOCTL_##FOO
enum ENUM_PACKED drm_rs_ioctls {
DRM_RS_IOCTL(NOUVEAU_EXEC),
};
File diff suppressed because it is too large. Load diff
+117
View File
@@ -0,0 +1,117 @@
# Copyright (C) 2023 Red Hat, Inc.
# Copyright (C) 2021 Collabora, Ltd.
# Copyright (C) 2016 Intel Corporation
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
import argparse
import sys
a = 'a'
b = 'b'
c = 'c'
s = 's'
# common conditions to improve readability
volta = 'nak->sm >= 70 && nak->sm < 73'
fp16_round_is_rtz = 'nir_is_rounding_mode_rtz(info->float_controls_execution_mode, 16)'
algebraic_lowering = [
# Volta doesn't have `IMNMX`
(('imin', 'a', 'b'), ('bcsel', ('ilt', a, b), a, b), volta),
(('imax', 'a', 'b'), ('bcsel', ('ilt', a, b), b, a), volta),
(('umin', 'a', 'b'), ('bcsel', ('ult', a, b), a, b), volta),
(('umax', 'a', 'b'), ('bcsel', ('ult', a, b), b, a), volta),
(('iadd', 'a@64', ('ineg', 'b@64')), ('isub', a, b)),
(('iadd', ('iadd(is_used_once)', 'a(is_not_const)', '#b'), 'c(is_not_const)'),
('iadd3', a, b, c), 'options->has_iadd3'),
(('iadd', ('iadd(is_used_once)', 'a(is_not_const)', 'b(is_not_const)'), '#c'),
('iadd3', a, b, c), 'options->has_iadd3'),
(('iadd(is_used_by_non_ldc_nv)', 'a@32', ('ishl', 'b@32', '#s@32')),
('lea_nv', a, b, s), 'nak->sm >= 70'),
(('iadd', 'a@64', ('ishl', 'b@64', '#s@32')),
('lea_nv', a, b, s), 'nak->sm >= 70'),
]
for f2f16 in ['f2f16', 'f2f16_rtz', 'f2f16_rtne']:
algebraic_lowering += [
(('vec2', (f2f16 + '(is_used_once)', 'a@32'), (f2f16 + '(is_used_once)', 'b@32')), (f2f16, ('vec2', a, b)), 'nak->sm >= 86')
]
# If we find mufu surrounded by bit_size conversions, just do the op in the
# original bit_size.
# MUFU.F16 internally appears to operate with the same precision as F32 does
# with the result being rounded towards zero to F16. EXP2 and RCP seem to be
# off by one around Inf, so it's only safe if we can ignore inf for those.
#
# This was verified with the `hw_tests::test_op_mufu_f16_down`.
# mufu.f16 for those is identical to mufu.f32 with rtz rounding except for results around infinity
for op in ['fexp2', 'frcp']:
algebraic_lowering += [
(('f2f16_rtz(ninf)', (op + '(is_used_once)', ('f2f32', 'a@16'))), (op, a), 'nak->sm >= 73'),
(('f2f16(ninf)', (op + '(is_used_once)', ('f2f32', 'a@16'))),
(op, a),
'nak->sm >= 73 && ' + fp16_round_is_rtz),
]
# mufu.f16 for those is identical to mufu.f32 with rtz rounding
for op in ['fcos_normalized_2_pi', 'flog2', 'frsq', 'fsin_normalized_2_pi', 'fsqrt']:
algebraic_lowering += [
(('f2f16_rtz', (op + '(is_used_once)', ('f2f32', 'a@16'))), (op, a), 'nak->sm >= 73'),
(('f2f16', (op + '(is_used_once)', ('f2f32', 'a@16'))),
(op, a),
'nak->sm >= 73 && ' + fp16_round_is_rtz),
]
# If contract is on we can always remove the conversions
for op in ['fcos_normalized_2_pi', 'fexp2', 'flog2', 'frcp', 'frsq', 'fsin_normalized_2_pi', 'fsqrt']:
for f2f16 in ['f2f16_rtz', 'f2f16_rtne', 'f2f16']:
algebraic_lowering += [
((f2f16 + '(contract)', (op + '(is_used_once)', ('f2f32', 'a@16'))),
(op, a), 'nak->sm >= 73'),
(('f2f32(contract)', (op + '(is_used_once)', (f2f16, 'a@32'))), (op, a)),
]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--out', required=True, help='Output file.')
parser.add_argument('-p', '--import-path', required=True)
args = parser.parse_args()
sys.path.insert(0, args.import_path)
import nir_algebraic # pylint: disable=import-error
try:
with open(args.out, 'w', encoding='utf-8') as f:
f.write('#include "nak_private.h"')
f.write(nir_algebraic.AlgebraicPass(
"nak_nir_lower_algebraic_late",
algebraic_lowering,
[
("const struct nak_compiler *", "nak"),
]).render())
except Exception:
sys.exit(1)
if __name__ == '__main__':
main()
+539
View File
@@ -0,0 +1,539 @@
/*
* Copyright © 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
static void
push_block(nir_builder *b, nir_block *block, bool divergent)
{
assert(nir_cursors_equal(b->cursor, nir_after_impl(b->impl)));
block->divergent = divergent;
block->cf_node.parent = &b->impl->cf_node;
exec_list_push_tail(&b->impl->body, &block->cf_node.node);
b->cursor = nir_after_block(block);
}
enum scope_type {
SCOPE_TYPE_SHADER,
SCOPE_TYPE_IF_MERGE,
SCOPE_TYPE_LOOP_BREAK,
SCOPE_TYPE_LOOP_CONT,
};
struct scope {
enum scope_type type;
struct scope *parent;
uint32_t depth;
/**
* True if control-flow ever diverges within this scope, not accounting
* for divergence in child scopes.
*/
bool divergent;
nir_block *merge;
nir_def *bar;
uint32_t escapes;
};
static struct scope
push_scope(nir_builder *b,
enum scope_type scope_type,
struct scope *parent,
bool divergent,
bool needs_sync,
nir_block *merge_block)
{
struct scope scope = {
.type = scope_type,
.parent = parent,
.depth = parent->depth + 1,
.divergent = parent->divergent || divergent,
.merge = merge_block,
};
if (needs_sync)
scope.bar = nir_bar_set_nv(b);
return scope;
}
static void
pop_scope(nir_builder *b, nir_def *esc_reg, struct scope scope)
{
if (scope.bar == NULL)
return;
nir_bar_sync_nv(b, scope.bar, scope.bar);
if (scope.escapes > 0) {
/* Find the nearest scope with a sync. */
nir_block *parent_merge = b->impl->end_block;
for (struct scope *p = scope.parent; p != NULL; p = p->parent) {
if (p->bar != NULL) {
parent_merge = p->merge;
break;
}
}
/* No escape is ~0, halt is 0, and we choose outer scope indices such
* that outer scopes always have lower indices than inner scopes.
*/
nir_def *esc = nir_ult_imm(b, nir_load_reg(b, esc_reg), scope.depth);
/* We have to put the escape in its own block to avoid critical edges.
* If we just did goto_if, we would end up with multiple successors,
* including a jump to the parent's merge block which has multiple
* predecessors.
*/
nir_block *esc_block = nir_block_create(b->shader);
nir_block *next_block = nir_block_create(b->shader);
nir_goto_if(b, esc_block, esc, next_block);
push_block(b, esc_block, false);
nir_goto(b, parent_merge);
push_block(b, next_block, scope.parent->divergent);
}
}
static enum scope_type
jump_target_scope_type(nir_jump_type jump_type)
{
switch (jump_type) {
case nir_jump_break: return SCOPE_TYPE_LOOP_BREAK;
case nir_jump_continue: return SCOPE_TYPE_LOOP_CONT;
default:
UNREACHABLE("Unknown jump type");
}
}
static void
break_scopes(nir_builder *b, nir_def *esc_reg,
struct scope *current_scope,
nir_jump_type jump_type)
{
nir_block *first_sync = NULL;
uint32_t target_depth = UINT32_MAX;
enum scope_type target_scope_type = jump_target_scope_type(jump_type);
for (struct scope *scope = current_scope; scope; scope = scope->parent) {
if (first_sync == NULL && scope->bar != NULL)
first_sync = scope->merge;
if (scope->type == target_scope_type) {
if (first_sync == NULL) {
first_sync = scope->merge;
} else {
/* In order for our cascade to work, we need to have the invariant
* that anything which escapes any scope with a warp sync needs to
* target a scope with a warp sync.
*/
assert(scope->bar != NULL);
}
target_depth = scope->depth;
break;
} else {
scope->escapes++;
}
}
assert(target_depth < UINT32_MAX);
nir_store_reg(b, nir_imm_int(b, target_depth), esc_reg);
nir_goto(b, first_sync);
}
static void
normal_exit(nir_builder *b, nir_def *esc_reg, nir_block *merge_block)
{
assert(nir_cursors_equal(b->cursor, nir_after_impl(b->impl)));
nir_block *block = nir_cursor_current_block(b->cursor);
if (!nir_block_ends_in_jump(block)) {
nir_store_reg(b, nir_imm_int(b, ~0), esc_reg);
nir_goto(b, merge_block);
}
}
/* This is a heuristic for what instructions are allowed before we sync.
* Annoyingly, we've gotten rid of phis so it's not as simple as "is it a
* phi?".
*/
static bool
instr_is_allowed_before_sync(nir_instr *instr)
{
switch (instr->type) {
case nir_instr_type_alu: {
nir_alu_instr *alu = nir_instr_as_alu(instr);
/* We could probably allow more ALU as long as it doesn't contain
* derivatives but let's be conservative and only allow mov for now.
*/
return alu->op == nir_op_mov;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
return intrin->intrinsic == nir_intrinsic_load_reg ||
intrin->intrinsic == nir_intrinsic_store_reg;
}
default:
return false;
}
}
/** Returns true if our successor will sync for us
*
* This is a bit of a heuristic
*/
static bool
parent_scope_will_sync(nir_cf_node *node, struct scope *parent_scope)
{
/* First search forward to see if there's anything non-trivial after this
* node within the parent scope.
*/
nir_block *block = nir_cf_node_as_block(nir_cf_node_next(node));
nir_foreach_instr(instr, block) {
if (!instr_is_allowed_before_sync(instr))
return false;
}
/* There's another loop or if following and we didn't find a sync */
if (nir_cf_node_next(&block->cf_node))
return false;
/* See if the parent scope will sync for us. */
if (parent_scope->bar != NULL)
return true;
switch (parent_scope->type) {
case SCOPE_TYPE_SHADER:
return true;
case SCOPE_TYPE_IF_MERGE:
return parent_scope_will_sync(block->cf_node.parent,
parent_scope->parent);
case SCOPE_TYPE_LOOP_CONT:
/* In this case, the loop doesn't have a sync of its own so we're
* expected to be uniform before we hit the continue.
*/
return false;
case SCOPE_TYPE_LOOP_BREAK:
UNREACHABLE("Loops must have a continue scope");
default:
UNREACHABLE("Unknown scope type");
}
}
static bool
block_is_merge(const nir_block *block)
{
/* If it's unreachable, there is no merge */
if (block->imm_dom == NULL)
return false;
unsigned num_preds = 0;
nir_foreach_pred(pred, block) {
/* We don't care about unreachable blocks */
if (pred->imm_dom == NULL)
continue;
num_preds++;
}
return num_preds > 1;
}
static void
lower_cf_list(nir_builder *b, nir_def *esc_reg, struct scope *parent_scope,
struct exec_list *cf_list)
{
foreach_list_typed_safe(nir_cf_node, node, node, cf_list) {
switch (node->type) {
case nir_cf_node_block: {
nir_block *block = nir_cf_node_as_block(node);
if (exec_list_is_empty(&block->instr_list))
break;
nir_cursor start = nir_before_block(block);
nir_cursor end = nir_after_block(block);
nir_jump_instr *jump = NULL;
nir_instr *last_instr = nir_block_last_instr(block);
if (last_instr->type == nir_instr_type_jump) {
jump = nir_instr_as_jump(last_instr);
end = nir_before_instr(&jump->instr);
}
nir_cf_list instrs;
nir_cf_extract(&instrs, start, end);
b->cursor = nir_cf_reinsert(&instrs, b->cursor);
if (jump != NULL) {
if (jump->type == nir_jump_halt) {
/* Halt instructions map to OpExit on NVIDIA hardware and
* exited lanes never block a bsync.
*/
nir_instr_remove(&jump->instr);
nir_builder_instr_insert(b, &jump->instr);
} else {
/* Everything else needs a break cascade */
break_scopes(b, esc_reg, parent_scope, jump->type);
}
}
break;
}
case nir_cf_node_if: {
nir_if *nif = nir_cf_node_as_if(node);
nir_def *cond = nif->condition.ssa;
bool divergent = nir_src_is_divergent(&nif->condition);
nir_instr_clear_src(NULL, &nif->condition);
nir_block *then_block = nir_block_create(b->shader);
nir_block *else_block = nir_block_create(b->shader);
nir_block *merge_block = nir_block_create(b->shader);
const bool needs_sync = divergent &&
block_is_merge(nir_cf_node_as_block(nir_cf_node_next(node))) &&
!parent_scope_will_sync(&nif->cf_node, parent_scope);
struct scope scope = push_scope(b, SCOPE_TYPE_IF_MERGE,
parent_scope, divergent,
needs_sync, merge_block);
nir_goto_if(b, then_block, cond, else_block);
push_block(b, then_block, scope.divergent);
lower_cf_list(b, esc_reg, &scope, &nif->then_list);
normal_exit(b, esc_reg, merge_block);
push_block(b, else_block, scope.divergent);
lower_cf_list(b, esc_reg, &scope, &nif->else_list);
normal_exit(b, esc_reg, merge_block);
push_block(b, merge_block, parent_scope->divergent);
pop_scope(b, esc_reg, scope);
break;
}
case nir_cf_node_loop: {
nir_loop *loop = nir_cf_node_as_loop(node);
nir_block *head_block = nir_block_create(b->shader);
nir_block *break_block = nir_block_create(b->shader);
nir_block *cont_block = nir_block_create(b->shader);
/* TODO: We can potentially avoid the break sync for loops when the
* parent scope syncs for us. However, we still need to handle the
* continue clause cascading to the break. If there is a
* nir_jump_halt involved, then we have a real cascade where it needs
* to then jump to the next scope. Getting all these cases right
* while avoiding an extra sync for the loop break is tricky at best.
*/
struct scope break_scope = push_scope(b, SCOPE_TYPE_LOOP_BREAK,
parent_scope,
nir_loop_is_divergent(loop),
nir_loop_is_divergent(loop),
break_block);
nir_goto(b, head_block);
push_block(b, head_block, break_scope.divergent);
struct scope cont_scope = push_scope(b, SCOPE_TYPE_LOOP_CONT,
&break_scope,
nir_loop_is_divergent(loop),
nir_loop_is_divergent(loop),
cont_block);
lower_cf_list(b, esc_reg, &cont_scope, &loop->body);
normal_exit(b, esc_reg, cont_block);
push_block(b, cont_block, break_scope.divergent);
pop_scope(b, esc_reg, cont_scope);
lower_cf_list(b, esc_reg, &break_scope, &loop->continue_list);
nir_goto(b, head_block);
push_block(b, break_block, parent_scope->divergent);
pop_scope(b, esc_reg, break_scope);
break;
}
default:
UNREACHABLE("Unknown CF node type");
}
}
}
static void
recompute_phi_divergence_impl(nir_function_impl *impl)
{
bool progress;
do {
progress = false;
nir_foreach_block_unstructured(block, impl) {
nir_foreach_instr(instr, block) {
if (instr->type != nir_instr_type_phi)
break;
nir_phi_instr *phi = nir_instr_as_phi(instr);
bool divergent = false;
nir_foreach_phi_src(phi_src, phi) {
/* There is a tricky case we need to care about here where a
* convergent block has a divergent dominator. This can happen
* if, for instance, you have the following loop:
*
* loop {
* if (div) {
* %20 = load_ubo(0, 0);
* } else {
* terminate;
* }
* }
* use(%20);
*
* In this case, the load_ubo() dominates the use() even though
* the load_ubo() exists in divergent control-flow. In this
* case, we simply flag the whole phi divergent because we
* don't want to deal with inserting a r2ur somewhere.
*/
if (phi_src->pred->divergent || phi_src->src.ssa->divergent ||
nir_def_block(phi_src->src.ssa)->divergent) {
divergent = true;
break;
}
}
if (divergent != phi->def.divergent) {
phi->def.divergent = divergent;
progress = true;
}
}
}
} while(progress);
impl->valid_metadata |= nir_metadata_divergence;
}
static bool
lower_cf_func(nir_function *func)
{
if (func->impl == NULL)
return false;
if (exec_list_is_singular(&func->impl->body)) {
return nir_no_progress(func->impl);
}
nir_function_impl *old_impl = func->impl;
/* We use this in block_is_merge() */
nir_metadata_require(old_impl, nir_metadata_dominance | nir_metadata_divergence);
/* We get rid of single source phis, because they would be converted to phis
* with undef after lowering regs to SSA.
*/
nir_foreach_block(block, old_impl) {
if (nir_block_num_preds(block) <= 1)
nir_remove_single_src_phis_block(block);
}
/* First, we temporarily get rid of SSA. This will make all our block
* motion way easier. Ask the pass to place reg writes directly in the
* immediate predecessors of the phis instead of trying to be clever.
* This will ensure that we never get a write to a uniform register from
* non-uniform control flow and makes our divergence reconstruction for
* phis more reliable.
*/
nir_foreach_block(block, old_impl)
nir_lower_phis_to_regs_block(block, true);
/* We create a whole new nir_function_impl and copy the contents over */
func->impl = NULL;
nir_function_impl *new_impl = nir_function_impl_create(func);
new_impl->structured = false;
/* We copy defs from the old impl */
new_impl->ssa_alloc = old_impl->ssa_alloc;
nir_builder b = nir_builder_at(nir_before_impl(new_impl));
nir_def *esc_reg = nir_decl_reg(&b, 1, 32, 0);
/* Having a function scope makes everything easier */
struct scope scope = {
.type = SCOPE_TYPE_SHADER,
.merge = new_impl->end_block,
};
lower_cf_list(&b, esc_reg, &scope, &old_impl->body);
normal_exit(&b, esc_reg, new_impl->end_block);
/* Now sort by reverse PDFS and restore SSA
*
* Note: Since we created a new nir_function_impl, there is no metadata,
* dirty or otherwise, so we have no need to call nir_progress().
*/
nir_sort_unstructured_blocks(new_impl);
nir_repair_ssa_impl(new_impl);
nir_lower_reg_intrinsics_to_ssa_impl(new_impl);
recompute_phi_divergence_impl(new_impl);
return true;
}
bool
nak_nir_lower_cf(nir_shader *nir)
{
bool progress = false;
nir_foreach_function(func, nir) {
if (lower_cf_func(func))
progress = true;
}
return progress;
}
bool
nak_block_is_divergent(const nir_block *block)
{
const nir_cf_node* node = &block->cf_node;
while (node) {
switch (node->type) {
case nir_cf_node_if: {
nir_if *nif = nir_cf_node_as_if(node);
if (nir_src_is_divergent(&nif->condition))
return true;
break;
}
case nir_cf_node_loop: {
nir_loop *loop = nir_cf_node_as_loop(node);
if (nir_loop_is_divergent(loop))
return true;
break;
}
case nir_cf_node_block: {
nir_block *block = nir_cf_node_as_block(node);
if (block->divergent)
return true;
break;
}
case nir_cf_node_function:
break;
}
node = node->parent;
}
return false;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,412 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
/** Load a flat FS input */
static nir_def *
load_fs_input(nir_builder *b, unsigned num_components, uint32_t addr,
UNUSED const struct nak_compiler *nak)
{
const struct nak_nir_ipa_flags flags = {
.interp_mode = NAK_INTERP_MODE_CONSTANT,
.interp_freq = NAK_INTERP_FREQ_CONSTANT,
.interp_loc = NAK_INTERP_LOC_DEFAULT,
};
nir_def *comps[NIR_MAX_VEC_COMPONENTS];
for (unsigned c = 0; c < num_components; c++) {
comps[c] = nir_ipa_nv(b, nir_imm_float(b, 0), nir_imm_int(b, 0),
.base = addr + c * 4, .flags = NAK_AS_U32(flags));
}
return nir_vec(b, comps, num_components);
}
static nir_def *
load_frag_w(nir_builder *b, enum nak_interp_loc interp_loc, nir_def *offset,
const struct nak_compiler *nak)
{
if (offset == NULL)
offset = nir_imm_int(b, 0);
const uint16_t w_addr =
nak_sysval_attr_addr(nak, SYSTEM_VALUE_FRAG_COORD) + 12;
const struct nak_nir_ipa_flags flags = {
.interp_mode = NAK_INTERP_MODE_SCREEN_LINEAR,
.interp_freq = NAK_INTERP_FREQ_PASS,
.interp_loc = interp_loc,
};
return nir_ipa_nv(b, nir_imm_float(b, 0), offset,
.base = w_addr, .flags = NAK_AS_U32(flags));
}
static nir_def *
interp_fs_input(nir_builder *b, unsigned num_components, uint32_t addr,
enum nak_interp_mode interp_mode,
enum nak_interp_loc interp_loc,
nir_def *inv_w, nir_def *offset,
const struct nak_compiler *nak)
{
if (offset == NULL)
offset = nir_imm_int(b, 0);
if (nak->sm >= 70) {
const struct nak_nir_ipa_flags flags = {
.interp_mode = interp_mode,
.interp_freq = NAK_INTERP_FREQ_PASS,
.interp_loc = interp_loc,
};
nir_def *comps[NIR_MAX_VEC_COMPONENTS];
for (unsigned c = 0; c < num_components; c++) {
comps[c] = nir_ipa_nv(b, nir_imm_float(b, 0), offset,
.base = addr + c * 4,
.flags = NAK_AS_U32(flags));
if (interp_mode == NAK_INTERP_MODE_PERSPECTIVE) {
unsigned fp_math_ctrl = b->fp_math_ctrl;
b->fp_math_ctrl |= nir_fp_exact;
/* It seems critical that this is done as round to zero.
* The Surge 2 and Shadow of the Tomb Raider show artifacts if not.
*/
comps[c] = nir_fmul_rtz(b, comps[c], inv_w);
b->fp_math_ctrl = fp_math_ctrl;
}
}
return nir_vec(b, comps, num_components);
} else if (nak->sm >= 20) {
struct nak_nir_ipa_flags flags = {
.interp_mode = interp_mode,
.interp_freq = NAK_INTERP_FREQ_PASS,
.interp_loc = interp_loc,
};
if (interp_mode == NAK_INTERP_MODE_PERSPECTIVE)
flags.interp_freq = NAK_INTERP_FREQ_PASS_MUL_W;
else
inv_w = nir_imm_float(b, 0);
nir_def *comps[NIR_MAX_VEC_COMPONENTS];
for (unsigned c = 0; c < num_components; c++) {
comps[c] = nir_ipa_nv(b, inv_w, offset,
.base = addr + c * 4,
.flags = NAK_AS_U32(flags));
}
return nir_vec(b, comps, num_components);
} else {
UNREACHABLE("Unsupported shader model");
}
}
static nir_def *
load_sample_pos_u4_at(nir_builder *b, nir_def *sample_id,
const struct nak_compiler* nak)
{
const struct nak_constant_offset_info *info = nak_const_offsets(nak, true);
nir_def *loc = nir_ldc_nv(b, 1, 8,
nir_imm_int(b, info->sample_info_cb),
nir_iadd_imm(b, sample_id,
info->sample_locations_offset),
.align_mul = 1, .align_offset = 0);
/* The rest of these calculations are in 32-bit */
loc = nir_u2u32(b, loc);
nir_def *loc_x_u4 = nir_iand_imm(b, loc, 0xf);
nir_def *loc_y_u4 = nir_iand_imm(b, nir_ushr_imm(b, loc, 4), 0xf);
return nir_vec2(b, loc_x_u4, loc_y_u4);
}
static nir_def *
load_pass_sample_mask_at(nir_builder *b, nir_def *sample_id,
const struct nak_compiler* nak)
{
const struct nak_constant_offset_info *info = nak_const_offsets(nak, true);
nir_def *offset =
nir_imul_imm(b, sample_id, sizeof(struct nak_sample_mask));
offset = nir_iadd_imm(b, offset, info->sample_masks_offset);
return nir_ldc_nv(b, 1, 8 * sizeof(struct nak_sample_mask),
nir_imm_int(b, info->sample_info_cb),
offset,
.align_mul = sizeof(struct nak_sample_mask),
.align_offset = 0);
}
static nir_def *
load_sample_pos_at(nir_builder *b, nir_def *sample_id,
const struct nak_compiler* nak)
{
nir_def *loc_u4 = load_sample_pos_u4_at(b, sample_id, nak);
nir_def *result = nir_fmul_imm(b, nir_i2f32(b, loc_u4), 1.0 / 16.0);
return result;
}
static nir_def *
load_barycentric_offset(nir_builder *b, nir_intrinsic_instr *bary,
const struct nak_compiler* nak)
{
nir_def *offset_s12;
if (bary->intrinsic == nir_intrinsic_load_barycentric_coord_at_sample ||
bary->intrinsic == nir_intrinsic_load_barycentric_at_sample) {
nir_def *sample_id = bary->src[0].ssa;
nir_def *offset_u4 = load_sample_pos_u4_at(b, sample_id, nak);
/* The sample position we loaded is a u4 from the upper-left and the
* sample position wanted by ipa.offset is s12
*/
offset_s12 = nir_iadd_imm(b, nir_ishl_imm(b, offset_u4, 8), -2048);
} else {
nir_def *offset_f = bary->src[0].ssa;
offset_f = nir_fclamp(b, offset_f, nir_imm_float(b, -0.5),
nir_imm_float(b, 0.437500));
offset_s12 = nir_f2i32(b, nir_fmul_imm(b, offset_f, 4096.0));
}
return nir_prmt_nv(b, nir_imm_int(b, 0x5410),
nir_channel(b, offset_s12, 0),
nir_channel(b, offset_s12, 1));
}
struct lower_fs_input_ctx {
const struct nak_compiler *nak;
const struct nak_fs_key *fs_key;
};
static uint16_t
fs_input_intrin_addr(nir_intrinsic_instr *intrin,
const struct nak_compiler *nak)
{
const nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
return nak_varying_attr_addr(nak, sem.location) +
nir_src_as_uint(*nir_get_io_offset_src(intrin)) * 16 +
nir_intrinsic_component(intrin) * 4;
}
static bool
lower_fs_input_intrin(nir_builder *b, nir_intrinsic_instr *intrin, void *data)
{
const struct lower_fs_input_ctx *ctx = data;
const struct nak_compiler *nak = ctx->nak;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *res;
switch (intrin->intrinsic) {
case nir_intrinsic_load_barycentric_pixel: {
if (!(ctx->fs_key && ctx->fs_key->force_sample_shading))
return false;
intrin->intrinsic = nir_intrinsic_load_barycentric_sample;
return true;
}
case nir_intrinsic_load_frag_coord:
case nir_intrinsic_load_point_coord: {
const enum nak_interp_loc interp_loc =
b->shader->info.fs.uses_sample_shading ? NAK_INTERP_LOC_CENTROID
: NAK_INTERP_LOC_DEFAULT;
const uint32_t addr =
intrin->intrinsic == nir_intrinsic_load_point_coord ?
nak_sysval_attr_addr(nak, SYSTEM_VALUE_POINT_COORD) :
nak_sysval_attr_addr(nak, SYSTEM_VALUE_FRAG_COORD);
res = interp_fs_input(b, intrin->def.num_components, addr,
NAK_INTERP_MODE_SCREEN_LINEAR,
interp_loc, NULL, NULL,
ctx->nak);
break;
}
case nir_intrinsic_load_front_face:
case nir_intrinsic_load_layer_id:
case nir_intrinsic_load_primitive_id: {
assert(b->shader->info.stage == MESA_SHADER_FRAGMENT);
const gl_system_value sysval =
nir_system_value_from_intrinsic(intrin->intrinsic);
const uint32_t addr = nak_sysval_attr_addr(nak, sysval);
res = load_fs_input(b, intrin->def.num_components, addr, ctx->nak);
if (intrin->def.bit_size == 1)
res = nir_i2b(b, res);
break;
}
case nir_intrinsic_load_input:
case nir_intrinsic_load_per_primitive_input: {
const uint16_t addr = fs_input_intrin_addr(intrin, ctx->nak);
res = load_fs_input(b, intrin->def.num_components, addr, ctx->nak);
break;
}
case nir_intrinsic_load_barycentric_coord_pixel:
case nir_intrinsic_load_barycentric_coord_centroid:
case nir_intrinsic_load_barycentric_coord_sample:
case nir_intrinsic_load_barycentric_coord_at_sample:
case nir_intrinsic_load_barycentric_coord_at_offset: {
uint32_t addr;
enum nak_interp_mode interp_mode;
if (nir_intrinsic_interp_mode(intrin) == INTERP_MODE_NOPERSPECTIVE) {
addr = NAK_ATTR_BARY_COORD_NO_PERSP;
interp_mode = NAK_INTERP_MODE_SCREEN_LINEAR;
} else {
addr = NAK_ATTR_BARY_COORD;
interp_mode = NAK_INTERP_MODE_PERSPECTIVE;
}
nir_def *offset = NULL;
enum nak_interp_loc interp_loc;
switch (intrin->intrinsic) {
case nir_intrinsic_load_barycentric_coord_at_sample:
case nir_intrinsic_load_barycentric_coord_at_offset:
interp_loc = NAK_INTERP_LOC_OFFSET;
offset = load_barycentric_offset(b, intrin, nak);
break;
case nir_intrinsic_load_barycentric_coord_centroid:
case nir_intrinsic_load_barycentric_coord_sample:
interp_loc = NAK_INTERP_LOC_CENTROID;
break;
case nir_intrinsic_load_barycentric_coord_pixel:
interp_loc = NAK_INTERP_LOC_DEFAULT;
break;
default:
UNREACHABLE("Unknown intrinsic");
}
nir_def *inv_w = NULL;
if (interp_mode == NAK_INTERP_MODE_PERSPECTIVE)
inv_w = nir_frcp(b, load_frag_w(b, interp_loc, offset, nak));
res = interp_fs_input(b, intrin->def.num_components,
addr, interp_mode, interp_loc,
inv_w, offset, ctx->nak);
break;
}
case nir_intrinsic_load_interpolated_input: {
const uint16_t addr = fs_input_intrin_addr(intrin, ctx->nak);
nir_intrinsic_instr *bary = nir_src_as_intrinsic(intrin->src[0]);
enum nak_interp_mode interp_mode;
if (nir_intrinsic_interp_mode(bary) == INTERP_MODE_SMOOTH ||
nir_intrinsic_interp_mode(bary) == INTERP_MODE_NONE)
interp_mode = NAK_INTERP_MODE_PERSPECTIVE;
else
interp_mode = NAK_INTERP_MODE_SCREEN_LINEAR;
nir_def *offset = NULL;
enum nak_interp_loc interp_loc;
switch (bary->intrinsic) {
case nir_intrinsic_load_barycentric_at_offset:
case nir_intrinsic_load_barycentric_at_sample: {
interp_loc = NAK_INTERP_LOC_OFFSET;
offset = load_barycentric_offset(b, bary, nak);
break;
}
case nir_intrinsic_load_barycentric_centroid:
case nir_intrinsic_load_barycentric_sample:
interp_loc = NAK_INTERP_LOC_CENTROID;
break;
case nir_intrinsic_load_barycentric_pixel:
interp_loc = NAK_INTERP_LOC_DEFAULT;
break;
default:
UNREACHABLE("Unsupported barycentric");
}
nir_def *inv_w = NULL;
if (interp_mode == NAK_INTERP_MODE_PERSPECTIVE)
inv_w = nir_frcp(b, load_frag_w(b, interp_loc, offset, nak));
res = interp_fs_input(b, intrin->def.num_components,
addr, interp_mode, interp_loc,
inv_w, offset, ctx->nak);
break;
}
case nir_intrinsic_load_sample_mask_in:
b->cursor = nir_after_instr(&intrin->instr);
/* pixld.covmask returns the coverage mask for the entire pixel being
* shaded, not the set of samples covered by the current FS invocation.
* We need to mask off excess samples in order to get the GL/Vulkan
* behavior.
*/
if (b->shader->info.fs.uses_sample_shading) {
/* Mask off just the current sample */
nir_def *sample = nir_load_sample_id(b);
nir_def *mask = nir_ishl(b, nir_imm_int(b, 1), sample);
mask = nir_iand(b, &intrin->def, mask);
nir_def_rewrite_uses_after(&intrin->def, mask);
return true;
} else if (ctx->fs_key && ctx->fs_key->force_sample_shading) {
/* In this case we don't know up-front how many passes will be run so
* we need to take the per-pass sample mask from the driver and AND
* that with the coverage mask.
*/
nir_def *sample = nir_load_sample_id(b);
nir_def *mask = load_pass_sample_mask_at(b, sample, nak);
mask = nir_iand(b, &intrin->def, nir_u2u32(b, mask));
nir_def_rewrite_uses_after(&intrin->def, mask);
return true;
} else {
/* We're always executing single-pass so just use the sample mask as
* given by the hardware.
*/
return false;
}
break;
case nir_intrinsic_load_sample_pos:
res = load_sample_pos_at(b, nir_load_sample_id(b), nak);
break;
case nir_intrinsic_load_input_vertex: {
const uint16_t addr = fs_input_intrin_addr(intrin, ctx->nak);
unsigned vertex_id = nir_src_as_uint(intrin->src[0]);
assert(vertex_id < 3);
nir_def *comps[NIR_MAX_VEC_COMPONENTS];
for (unsigned c = 0; c < intrin->def.num_components; c++) {
nir_def *data = nir_ldtram_nv(b, .base = addr + c * 4,
.flags = vertex_id == 2);
comps[c] = nir_channel(b, data, vertex_id & 1);
}
res = nir_vec(b, comps, intrin->num_components);
break;
}
default:
return false;
}
nir_def_replace(&intrin->def, res);
return true;
}
bool
nak_nir_lower_fs_inputs(nir_shader *nir,
const struct nak_compiler *nak,
const struct nak_fs_key *fs_key)
{
const struct lower_fs_input_ctx fs_in_ctx = {
.nak = nak,
.fs_key = fs_key,
};
NIR_PASS(_, nir, nir_shader_intrinsics_pass, lower_fs_input_intrin,
nir_metadata_control_flow,
(void *)&fs_in_ctx);
return true;
}
@@ -0,0 +1,124 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
#include "nir_format_convert.h"
struct state {
nir_builder *builder;
nir_variable *handle_var;
bool progress;
};
static void
rewrite_emit_vertex(nir_intrinsic_instr *intr, struct state *state)
{
nir_builder *b = state->builder;
const unsigned stream = nir_intrinsic_stream_id(intr);
b->cursor = nir_before_instr(&intr->instr);
nir_def *gs_handle = nir_load_var(b, state->handle_var);
nir_def *gs_handle_out = nir_emit_vertex_nv(b, 32, gs_handle, stream);
nir_store_var(b, state->handle_var, gs_handle_out, 0x1);
nir_instr_remove(&intr->instr);
state->progress = true;
}
static void
rewrite_end_primitive(nir_intrinsic_instr *intr, struct state *state)
{
nir_builder *b = state->builder;
const unsigned stream = nir_intrinsic_stream_id(intr);
b->cursor = nir_before_instr(&intr->instr);
nir_def *gs_handle = nir_load_var(b, state->handle_var);
nir_def *gs_handle_out = nir_end_primitive_nv(b, 32, gs_handle, stream);
nir_store_var(b, state->handle_var, gs_handle_out, 0x1);
nir_instr_remove(&intr->instr);
state->progress = true;
}
static void
rewrite_ast_nv(nir_intrinsic_instr *intr, struct state *state)
{
nir_builder *b = state->builder;
b->cursor = nir_before_instr(&intr->instr);
nir_src *vtx = &intr->src[1];
nir_def *gs_handle = nir_load_var(b, state->handle_var);
nir_src_rewrite(vtx, gs_handle);
state->progress = true;
}
static void
append_final_primitive_nv(nir_block *end_block, struct state *state)
{
nir_builder *b = state->builder;
nir_foreach_pred(pred, end_block) {
b->cursor = nir_after_block_before_jump(pred);
nir_def *gs_handle = nir_load_var(b, state->handle_var);
nir_final_primitive_nv(b, gs_handle);
state->progress = true;
}
}
bool
nak_nir_lower_gs_intrinsics(nir_shader *nir)
{
struct state state;
state.progress = false;
nir_function_impl *impl = nir_shader_get_entrypoint(nir);
nir_builder builder = nir_builder_at(nir_before_impl(impl));
state.builder = &builder;
state.handle_var = nir_local_variable_create(impl, glsl_uint_type(), "gs_handle");
nir_store_var(&builder, state.handle_var, nir_imm_int(&builder, 0), 0x1);
nir_foreach_block_safe(block, impl) {
nir_foreach_instr_safe(instr, block) {
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
switch (intr->intrinsic) {
case nir_intrinsic_emit_vertex:
rewrite_emit_vertex(intr, &state);
break;
case nir_intrinsic_end_primitive:
rewrite_end_primitive(intr, &state);
break;
case nir_intrinsic_ast_nv:
rewrite_ast_nv(intr, &state);
break;
default:
break;
}
}
}
append_final_primitive_nv(impl->end_block, &state);
nir_progress(true, impl, nir_metadata_none);
return state.progress;
}
@@ -0,0 +1,621 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak.h"
#include "nak_private.h"
#include "nil.h"
#include "nir_format_convert.h"
static nir_def *
build_load_su_info(nir_builder *b, nir_deref_instr *deref, uint32_t offset)
{
return nir_image_deref_load_info_nv(b, 1, &deref->def, .base = offset);
}
#define load_su_info(b, d, field) \
build_load_su_info((b), (d), offsetof(struct nil_su_info, field))
static enum pipe_format
format_for_bits(unsigned bits)
{
switch (bits) {
case 8: return PIPE_FORMAT_R8_UINT;
case 16: return PIPE_FORMAT_R16_UINT;
case 32: return PIPE_FORMAT_R32_UINT;
case 64: return PIPE_FORMAT_R32G32_UINT;
case 128: return PIPE_FORMAT_R32G32B32A32_UINT;
default: UNREACHABLE("Unknown number of image format bits");
}
}
static unsigned
sampler_dim_len(enum glsl_sampler_dim dim)
{
switch (dim) {
case GLSL_SAMPLER_DIM_1D:
case GLSL_SAMPLER_DIM_BUF:
return 1;
case GLSL_SAMPLER_DIM_CUBE:
case GLSL_SAMPLER_DIM_2D:
case GLSL_SAMPLER_DIM_RECT:
case GLSL_SAMPLER_DIM_MS:
return 2;
case GLSL_SAMPLER_DIM_3D:
return 3;
default:
UNREACHABLE("Unhandled sampler dim");
return 1;// Never reached
}
}
static nir_def *
lower_formatted_image_load(nir_builder *b,
nir_intrinsic_instr *intrin,
enum pipe_format format)
{
if (format == PIPE_FORMAT_NONE)
return NULL;
unsigned bits = util_format_get_blocksizebits(format);
assert(intrin->intrinsic == nir_intrinsic_suldga_nv);
nir_intrinsic_set_format(intrin, format_for_bits(bits));
const unsigned num_raw_components = DIV_ROUND_UP(bits, 32);
intrin->num_components = num_raw_components;
intrin->def.num_components = num_raw_components;
b->cursor = nir_after_instr(&intrin->instr);
nir_def *rgba = NULL;
switch (format) {
case PIPE_FORMAT_R64_UINT:
case PIPE_FORMAT_R64_SINT:
rgba = nir_vec4(b, nir_pack_64_2x32(b, &intrin->def),
nir_imm_int64(b, 0),
nir_imm_int64(b, 0),
nir_imm_int64(b, 1));
break;
default:
rgba = nir_format_unpack_rgba(b, &intrin->def, format);
break;
}
return rgba;
}
static nir_def *
load_su_info_clamp(nir_builder *b, nir_deref_instr *deref,
unsigned xyz)
{
/* Array length is always stored in clamp_z */
enum glsl_sampler_dim dim = glsl_get_sampler_dim(deref->type);
if (dim == GLSL_SAMPLER_DIM_1D && xyz == 1)
return load_su_info(b, deref, clamp_z);
switch (xyz) {
case 0: return load_su_info(b, deref, clamp_x);
case 1: return load_su_info(b, deref, clamp_y);
case 2: return load_su_info(b, deref, clamp_z);
default: UNREACHABLE("Invalid image dimension");
}
}
static nir_def *
clamp_coord(nir_builder *b, nir_deref_instr *deref,
nir_def* coord, int xyz)
{
nir_def *clamp = load_su_info_clamp(b, deref, xyz);
const struct nak_nir_suclamp_flags flags = {
.mode = NAK_SUCLAMP_MODE_STORED_DESCRIPTOR,
.round = NAK_SUCLAMP_ROUND_R1,
.is_s32 = false,
.is_2d = true,
};
nir_def *dst = nir_suclamp_nv(b, coord, clamp,
.flags = NAK_AS_U32(flags));
return nir_channel(b, dst, 0);
}
static void
load_sample_size(nir_builder *b, nir_deref_instr *deref,
nir_def **sample_width_log2,
nir_def **sample_height_log2)
{
/* MS width and height are stored in the lower 8 bits of pitch */
nir_def *pitch = load_su_info(b, deref, pitch);
*sample_width_log2 = nir_ubitfield_extract_imm(b, pitch, 24, 4);
*sample_height_log2 = nir_ubitfield_extract_imm(b, pitch, 28, 4);
}
// Kepler only supports suldga, sustga, so we need to compute
// the raw address manually, this is done through a weird dance
// and custom ops.
// - each coordinate is clamped through suclamp
// - compute block offset using gob coordinates (y * pitch + x)
// - merge bitfields (output of suclamps)
// this will both merge the block coordinates, combine
// pixel coordinates (in GoB-space) and OR together the OOB
// predicate
// - compute the effective upper address by combining block_offset,
// bitfield and base_address
// - combine effective upper address and the combined bitfield that
// contains the lower 8 bits of global address
// - pass the combined values into suldga/sustga
//
// Linear Layout support:
// Shader cannot know at compile time if an image is in linear format
// or in block format, thus it needs to support both modes using the
// same opcodes, the only difference should be in descriptors.
// The address for linear layout is computed with:
// eff_addr = base_addr + (y * pitch + x)*el_size_B
// Here we first compute off = (y * pitch + x) with imadsp
// then we split what goes in the first 8 bits using subfm
// (in 0..8 it loads off << el_size_B.log2, in 16..32 it
// loads off >> el_size_B.log2)
// For this we need to emit eau = sueau bf.x, bf.y, off
// this would totally screw up block-linear calcs, but we can use
// is_3d=false to skip the third argument only in block-linear.
static void
compute_image_address(nir_builder *b,
nir_intrinsic_instr *intrin,
enum glsl_sampler_dim sampler_dim,
nir_def **addr,
nir_def **is_oob)
{
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
const unsigned dim = sampler_dim_len(sampler_dim);
const bool is_cube = sampler_dim == GLSL_SAMPLER_DIM_CUBE;
// A CubeMap is a 6-array of 2D images, a CubeMap array is just
// multiple CubeMap concatenated together, so the cube access is alway
// treated as an array
const bool is_array = nir_intrinsic_image_array(intrin) || is_cube;
// Prevent read/write for null-descriptors
nir_def *base_addr = load_su_info(b, deref, addr_shifted8);
*is_oob = nir_ieq_imm(b, base_addr, 0);
nir_def *coords[3];
for (int i = 0; i < 3; i++)
coords[i] = nir_channel(b, intrin->src[1].ssa, i);
// Lower multi-sample coords
if (sampler_dim == GLSL_SAMPLER_DIM_MS) {
nir_def *s = intrin->src[2].ssa;
nir_def *sw_log2, *sh_log2;
load_sample_size(b, deref, &sw_log2, &sh_log2);
nir_def *s_map = load_su_info(b, deref, extra);// multi-sample table
nir_def *num_samples = nir_ishl(b, nir_imm_int(b, 1),
nir_iadd(b, sw_log2, sh_log2));
nir_def *s_xy = nir_ushr(b, s_map, nir_imul_imm(b, s, 4));
nir_def *sx = nir_ubitfield_extract_imm(b, s_xy, 0, 2);
nir_def *sy = nir_ubitfield_extract_imm(b, s_xy, 2, 2);
nir_def *sw = nir_ishl(b, nir_imm_int(b, 1), sw_log2);
nir_def *sh = nir_ishl(b, nir_imm_int(b, 1), sh_log2);
nir_def *x = nir_imad(b, coords[0], sw, sx);
nir_def *y = nir_imad(b, coords[1], sh, sy);
// Check if OOB
*is_oob = nir_ior(b, *is_oob, nir_uge(b, s, num_samples));
coords[0] = x;
coords[1] = y;
}
// Clamp coordinates
// This computes a bitfield with the following info inside:
// - Block coordinates
// - GoB coordinates
// - predicate for OOB access
// - Wether the clamp is using pitch_linear
// - n. of block tiles for the coordinate
nir_def *clamped_x = clamp_coord(b, deref, coords[0], 0);
nir_def *clamped_y = dim >= 2 ? clamp_coord(b, deref, coords[1], 1) : nir_imm_int(b, 0);
nir_def *clamped_z = dim >= 3 ? clamp_coord(b, deref, coords[2], 2) : nir_imm_int(b, 0);
// For arrays the clamp is "plain", no bitfield is computed
// just the OOB predicate.
nir_def *array_idx = NULL;
if (is_array) {
nir_def *clamp = load_su_info_clamp(b, deref, dim);
nir_def *coord = coords[dim];
const struct nak_nir_suclamp_flags flags = {
.mode = NAK_SUCLAMP_MODE_PITCH_LINEAR,
.round = NAK_SUCLAMP_ROUND_R1,
.is_s32 = false,
.is_2d = false,
};
nir_def *dst = nir_suclamp_nv(b, coord, clamp,
.flags = NAK_AS_U32(flags));
array_idx = nir_channel(b, dst, 0);
*is_oob = nir_ior(b, *is_oob, nir_ine_imm(b, nir_channel(b, dst, 1), 0));
}
// Compute offset
// for Block-Linear: GOB coordinates (offset that contributes *64)
// for Pitch-Linear: offset in pixels (y * pitch) + x
// for Buffer: offset = x
nir_def *off;
if (dim == 1) {
// This can be only 16 bits because it's only tile coordinates
// (actually it's 20 bits in pitch-linear mode, we don't support
// images that big)
off = nir_iand_imm(b, clamped_x, 0xffff);
} else if (dim == 2) {
// off = clamped.y * pitch + clamped.x
nir_def *pitch = load_su_info(b, deref, pitch);
const struct nak_nir_imadsp_flags flags = {
.src0 = NAK_IMAD_TYPE_U16_LO,
.src1 = NAK_IMAD_TYPE_U24,
.src2 = NAK_IMAD_TYPE_U16_LO,
.params_from_src1 = false,
};
off = nir_imadsp_nv(b, clamped_y, pitch, clamped_x,
.flags = NAK_AS_U32(flags));
} else {
assert(dim == 3);
// off = (clamped.z * height + clamped.y) * pitch + clamped.x
// height is the height in blocks, we can compute this by doing a
// block linear clamp with the maximum value.
// (block shift-right is applied by suclamp)
nir_def *dim_y = load_su_info_clamp(b, deref, 1);
const struct nak_nir_suclamp_flags clamp_flags = {
.mode = NAK_SUCLAMP_MODE_BLOCK_LINEAR,
.round = NAK_SUCLAMP_ROUND_R1,
.is_s32 = false,
.is_2d = false,
};
nir_def *max_y = nir_suclamp_nv(b, nir_imm_int(b, -1), dim_y,
.flags=NAK_AS_U32(clamp_flags));
// max_y is still a bitfield, we can add 1 but we must only use the
// lower 16 bits of height_b.
nir_def *height_b = nir_iadd_imm(b, nir_channel(b, max_y, 0), 1);
const struct nak_nir_imadsp_flags flags_zy = {
.src0 = NAK_IMAD_TYPE_U16_LO,
.src1 = NAK_IMAD_TYPE_U16_LO,
.src2 = NAK_IMAD_TYPE_U16_LO,
.params_from_src1 = false,
};
nir_def *off_2d = nir_imadsp_nv(b, clamped_z, height_b, clamped_y,
.flags = NAK_AS_U32(flags_zy));
nir_def *pitch = load_su_info(b, deref, pitch);
const struct nak_nir_imadsp_flags flags = {
.src0 = NAK_IMAD_TYPE_U32,
.src1 = NAK_IMAD_TYPE_U24,
.src2 = NAK_IMAD_TYPE_U16_LO,
.params_from_src1 = false,
};
off = nir_imadsp_nv(b, off_2d, pitch, clamped_x,
.flags = NAK_AS_U32(flags));
}
// Compute merged bitfield
nir_def *bf;
{
// bf, pred = subfm clamped.x, clamped.y, clamped.z
nir_def *bfz;
bool is_3d = dim >= 3;
if (dim == 2 && !is_array) {
// Special case for pitch_linear support, see comment above.
bfz = off;
} else {
bfz = clamped_z;
}
nir_def *combined = nir_subfm_nv(b, clamped_x, clamped_y, bfz,
.flags = is_3d);
bf = nir_channel(b, combined, 0);
nir_def *bfm_oob = nir_ine_imm(b, nir_channel(b, combined, 1), 0);
*is_oob = nir_ior(b, *is_oob, bfm_oob);
}
nir_def *eau = nir_sueau_nv(b, off, bf, base_addr);
// Apply array layer offset
if (is_array) {
nir_def *array_stride = load_su_info(b, deref, array_stride_shifted8);
// Note: this only works because array_idx has been plain-clamped
// so it's not a bitfield (and we can read more than u16)
const struct nak_nir_imadsp_flags flags = {
.src0 = NAK_IMAD_TYPE_U32,
.src1 = NAK_IMAD_TYPE_U24,
.src2 = NAK_IMAD_TYPE_U32,
.params_from_src1 = false,
};
eau = nir_imadsp_nv(b, array_idx, array_stride, eau,
.flags = NAK_AS_U32(flags));
}
*addr = nir_vec2(b, bf, eau);
}
// Buffer address calculation is much simpler than images
// In the end we need the global address to reach in two registers
// The high register should have the highest 32-bit part of the address
// The lower register should have the lowest 8-bit part
// This computation can be summarized as:
// res = addr + clamp(x) * num_comps
// Unfortunately, given the weird register division, we need some weird
// ops. We abuse some special functions from image addressing to reduce
// the number of instructions.
//
// For null descriptors, we put bit 31 high in lower_addr,
// this is then passed on bfm and sets the oob predicate high.
// And that's how we get safe null descriptors for free.
//
// TODO: check if we can also use vector instructions, codegen used VSHL
// but it also didn't support sub-0x100 aligned buffers
static void
compute_buffer_address(nir_builder *b,
nir_intrinsic_instr *intrin,
enum glsl_sampler_dim sampler_dim,
nir_def **addr,
nir_def **is_oob)
{
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
nir_def *num_elems = load_su_info(b, deref, clamp_x);
nir_def *el_size_B = load_su_info(b, deref, pitch);
nir_def *lower_addr = load_su_info(b, deref, extra);
nir_def *raw_off = nir_channel(b, intrin->src[1].ssa, 0);
*is_oob = nir_uge(b, raw_off, num_elems);
nir_def *offset = nir_imad(b, raw_off, el_size_B, lower_addr);
nir_def *base_addr = load_su_info(b, deref, addr_shifted8);
*addr = nir_vec2(b, offset, base_addr);
}
static nir_def *
compute_address_from_ga_offset(nir_builder *b, nir_def *addr,
enum pipe_format format,
enum nak_su_ga_offset_mode offset_mode)
{
assert(offset_mode == NAK_SUGA_OFF_MODE_U32 ||
offset_mode == NAK_SUGA_OFF_MODE_U8);
// mode U8: addr_hi contains bits 8..40, addr_lo contains 0..8
// mode U32: addr_hi contains bits 8..40, addr_lo contains 0..32
// and they should be added to addr_hi
nir_def *lo_8;
nir_def *hi_32 = nir_channel(b, addr, 1);
// With what should we fill the lower 8 bits?
if (offset_mode == NAK_SUGA_OFF_MODE_U8) {
lo_8 = nir_channel(b, addr, 0);
} else {
lo_8 = nir_imm_int(b, 0);
}
// Construct the 64-bit address (hi_32 << 8 | lo_8)
nir_def *low = nir_prmt_nv(b, nir_imm_int(b, 0x6540), lo_8, hi_32);
nir_def *high = nir_prmt_nv(b, nir_imm_int(b, 0x0007),
nir_imm_int(b, 0), hi_32);
nir_def *full_addr = nir_pack_64_2x32_split(b, low, high);
if (offset_mode == NAK_SUGA_OFF_MODE_U32) {
full_addr = nir_iadd(b, full_addr, nir_u2u64(b, nir_channel(b, addr, 0)));
}
return full_addr;
}
static void
lower_image_access(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_instr_remove(&intrin->instr);
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
enum pipe_format format = nir_intrinsic_format(intrin);
if (format == PIPE_FORMAT_NONE)
format = nir_deref_instr_get_variable(deref)->data.image.format;
const unsigned int num_dst_components = intrin->def.num_components;
const enum glsl_sampler_dim sampler_dim = nir_intrinsic_image_dim(intrin);
enum nak_su_ga_offset_mode offset_mode;
nir_def *addr, *is_oob;
if (sampler_dim != GLSL_SAMPLER_DIM_BUF) {
compute_image_address(b, intrin, sampler_dim, &addr, &is_oob);
offset_mode = NAK_SUGA_OFF_MODE_U8;
} else {
compute_buffer_address(b, intrin, sampler_dim, &addr, &is_oob);
offset_mode = NAK_SUGA_OFF_MODE_U32;
}
switch (intrin->intrinsic) {
case nir_intrinsic_image_deref_load: {
// .format intrinsic index is only used to pass how many bits to store
nir_def *fmt = load_su_info(b, deref, format_info);
nir_def *new_ssa = nir_suldga_nv(b, num_dst_components, addr, fmt,
is_oob,
.format = format,
.access = nir_intrinsic_access(intrin),
.flags = offset_mode);
nir_intrinsic_instr *parent = nir_def_as_intrinsic(new_ssa);
new_ssa = lower_formatted_image_load(b, parent, format);
nir_def_rewrite_uses(&intrin->def, new_ssa);
break;
}
case nir_intrinsic_image_deref_store: {
nir_def *fmt = load_su_info(b, deref, format_info);
nir_sustga_nv(b, addr, fmt, is_oob, intrin->src[3].ssa,
.access = nir_intrinsic_access(intrin),
.flags = offset_mode);
break;
}
case nir_intrinsic_image_deref_atomic:
case nir_intrinsic_image_deref_atomic_swap: {
nir_atomic_op atomic_op = nir_intrinsic_atomic_op(intrin);
// suldga and sustga expect address as [low_8, high_32]
// while global_atomic expects a 64-bit address
nir_def *full_addr = compute_address_from_ga_offset(b, addr,
format,
offset_mode);
const unsigned bit_size = (format == PIPE_FORMAT_R64_UINT ||
format == PIPE_FORMAT_R64_SINT) ? 64 : 32;
nir_def *res, *res_ib, *res_oob;
nir_push_if(b, nir_inot(b, is_oob));
if (intrin->intrinsic == nir_intrinsic_image_deref_atomic) {
res_ib = nir_global_atomic(b, bit_size, full_addr,
intrin->src[3].ssa,
.atomic_op = atomic_op);
} else {
res_ib = nir_global_atomic_swap(b, bit_size, full_addr,
intrin->src[3].ssa,
intrin->src[4].ssa,
.atomic_op = atomic_op);
}
nir_push_else(b, NULL); {
res_oob = nir_imm_intN_t(b, 0, bit_size);
}
nir_pop_if(b, NULL);
res = nir_if_phi(b, res_ib, res_oob);
nir_def_rewrite_uses(&intrin->def, res);
break;
}
default:
UNREACHABLE("Unknown image intrinsic");
}
}
static void
lower_image_size(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_instr_remove(&intrin->instr);
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
const unsigned dim = sampler_dim_len(nir_intrinsic_image_dim(intrin));
const bool is_array = nir_intrinsic_image_array(intrin);
const unsigned cdim = dim + is_array;
assert(cdim <= 3);
nir_def *comps[3];
nir_def *is_null = nir_ieq_imm(b, load_su_info(b, deref, addr_shifted8),
0);
nir_def *one_if_present = nir_bcsel(b, is_null, nir_imm_int(b, 0),
nir_imm_int(b, 1));
// In descriptors we don't really have the size, but the clamp
// since it's inclusive, it is size - 1.
// Also, clamp is a bitfield, we need to extract the lower 16 bits.
// We can do both operations (extraction and addition) with an imadsp.
// To handle null descriptors, the addition accumulator of the imadsp
// is 1 only for non-null descriptors.
for (int i = 0; i < cdim; i++) {
nir_def *clamp = load_su_info_clamp(b, deref, i);
const struct nak_nir_imadsp_flags flags = {
.src0 = NAK_IMAD_TYPE_U16_LO,
.src1 = NAK_IMAD_TYPE_U24,
.src2 = NAK_IMAD_TYPE_U16_LO,
.params_from_src1 = false,
};
comps[i] = nir_imadsp_nv(b, clamp, one_if_present,
one_if_present,
.flags = NAK_AS_U32(flags));
}
// Clamp has multi-sampling already lowered, we need to de-lower it.
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS) {
nir_def *ms_w_log2, *ms_h_log2;
load_sample_size(b, deref, &ms_w_log2, &ms_h_log2);
comps[0] = nir_ishr(b, comps[0], ms_w_log2);
comps[1] = nir_ishr(b, comps[1], ms_h_log2);
}
nir_def_rewrite_uses(&intrin->def, nir_vec(b, comps, cdim));
}
static void
lower_buffer_size(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_instr_remove(&intrin->instr);
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
nir_def *num_elems = load_su_info(b, deref, clamp_x);
nir_def_rewrite_uses(&intrin->def, num_elems);
}
static void
lower_image_samples(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_instr_remove(&intrin->instr);
nir_deref_instr *deref = nir_src_as_deref(intrin->src[0]);
nir_def *sw_log2, *sh_log2;
load_sample_size(b, deref, &sw_log2, &sh_log2);
nir_def *samples_log2 = nir_iadd(b, sw_log2, sh_log2);
nir_def *samples = nir_ishl(b, nir_imm_int(b, 1), samples_log2);
// Handle null descriptors
nir_def *addr = load_su_info(b, deref, addr_shifted8);
nir_def *is_null = nir_ieq_imm(b, addr, 0);
samples = nir_bcsel(b, is_null, nir_imm_int(b, 0), samples);
nir_def_rewrite_uses(&intrin->def, samples);
}
static bool
lower_image_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
void *_data)
{
switch (intrin->intrinsic) {
case nir_intrinsic_image_deref_load:
case nir_intrinsic_image_deref_store:
case nir_intrinsic_image_deref_atomic:
case nir_intrinsic_image_deref_atomic_swap:
lower_image_access(b, intrin);
return true;
case nir_intrinsic_image_deref_size:
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_BUF) {
lower_buffer_size(b, intrin);
} else {
lower_image_size(b, intrin);
}
return true;
case nir_intrinsic_image_deref_samples:
lower_image_samples(b, intrin);
return true;
default:
return false;
}
}
bool
nak_nir_lower_image_addrs(nir_shader *nir,
const struct nak_compiler *nak)
{
return nir_shader_intrinsics_pass(nir, lower_image_intrin,
nir_metadata_none,
(void *)nak);
}
@@ -0,0 +1,586 @@
/*
* Copyright © 2026 Valve Corporation.
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
static bool
lower_mesh_io_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
const struct lower_mesh_intrinsics_ctx *ctx)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *vtx = NULL, *offset = NULL, *data = NULL;
switch (intrin->intrinsic) {
case nir_intrinsic_load_per_vertex_output:
case nir_intrinsic_load_per_primitive_output:
vtx = intrin->src[0].ssa;
offset = intrin->src[1].ssa;
break;
case nir_intrinsic_store_per_vertex_output:
case nir_intrinsic_store_per_primitive_output:
data = intrin->src[0].ssa;
vtx = intrin->src[1].ssa;
offset = intrin->src[2].ssa;
break;
default:
UNREACHABLE("unknown intrinsic");
}
const bool is_per_primitive = intrin->intrinsic == nir_intrinsic_load_per_primitive_output ||
intrin->intrinsic == nir_intrinsic_store_per_primitive_output;
const bool is_store = data != NULL;
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
const bool is_primitive_indices = sem.location == VARYING_SLOT_PRIMITIVE_INDICES;
const struct nak_nir_isbe_flags flags = {
.access = is_primitive_indices ? NAK_ISBE_ACCESS_MAP : NAK_ISBE_ACCESS_ATTR,
.output = true,
.skew = !is_primitive_indices,
.per_primitive = is_per_primitive,
};
uint32_t base_addr =
nak_varying_mesh_skew_attr_addr(ctx->nak, sem.location) +
4 * nir_intrinsic_component(intrin);
uint32_t range;
if (nir_src_is_const(nir_src_for_ssa(offset))) {
uint32_t const_offset = nir_src_as_uint(nir_src_for_ssa(offset));
/* Tighten the range */
base_addr += const_offset * 16;
range = 4 * intrin->num_components;
if (const_offset != 0)
offset = nir_imm_int(b, 0);
} else {
/* Offsets from NIR are in vec4's */
offset = nir_imul_imm(b, offset, 16);
range = (sem.num_slots - 1) * 16 + intrin->num_components * 4;
}
nir_def *isbe_offset;
uint32_t stride;
if (is_primitive_indices) {
const uint32_t vertices_per_prim = mesa_vertices_per_prim(b->shader->info.mesh.primitive_type);
/* Indices are 8 bits on hardware */
isbe_offset = nir_iadd(b, offset, nir_iadd_imm(b, nir_imul_imm(b, vtx, vertices_per_prim), 4));
stride = 1;
} else {
uint16_t skew_attr_offset = nak_mesh_skew_offset(ctx, sem.location, base_addr, is_per_primitive);
nir_def *skew_start_offset;
uint16_t skew_group_size;
if (is_per_primitive) {
skew_start_offset = nir_imm_int(b, nak_mesh_skew_vert_total_size(ctx));
skew_group_size = nak_mesh_skew_prim_group_size(ctx);
} else {
skew_start_offset = nir_imm_int(b, 0);
skew_group_size = nak_mesh_skew_vert_group_size(ctx);
}
/* Readjust offset to take into account SKEW groups */
nir_def *offset_ajusted = nir_imul_imm(b, offset, NAK_MESH_SKEW_GROUP_COUNT);
skew_start_offset = nir_iadd(b, skew_start_offset, nir_imul_imm(b, nir_udiv_imm(b, vtx, 32), skew_group_size));
isbe_offset = nir_iadd(b, nir_iadd_imm(b, nir_iadd(b, nir_imul_imm(b, nir_imod_imm(b, vtx, 32), 4),
skew_start_offset),
skew_attr_offset),
offset_ajusted);
stride = 4 * NAK_MESH_SKEW_GROUP_COUNT;
}
if (is_store) {
u_foreach_bit(c, nir_intrinsic_write_mask(intrin)) {
nir_def *c_offset = nir_iadd_imm(b, isbe_offset, c * stride);
nir_def *c_data = nir_channel(b, data, c);
/* Handle indices conversion */
if (is_primitive_indices)
c_data = nir_u2u8(b, c_data);
nir_isbewr_nv(b, c_data, c_offset, .range_base = base_addr,
.range = range, .flags = NAK_AS_U32(flags));
}
} else {
const uint8_t bit_size = is_primitive_indices ? 8 : intrin->def.bit_size;
nir_def *comps[NIR_MAX_VEC_COMPONENTS];
for (uint32_t c = 0; c < intrin->num_components; c++) {
nir_def *c_offset = nir_iadd_imm(b, isbe_offset, c * stride);
nir_def *c_data =
nir_isberd_nv(b, bit_size, c_offset, .range_base = base_addr,
.range = range, .flags = NAK_AS_U32(flags));
/* Handle indices conversion */
if (is_primitive_indices)
c_data = nir_u2u32(b, c_data);
comps[c] = c_data;
}
nir_def *dst = nir_vec(b, comps, intrin->num_components);
nir_def_rewrite_uses(&intrin->def, dst);
}
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_set_vertex_and_primitive_count(nir_builder *b,
nir_intrinsic_instr *intrin)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *primitive_count = intrin->src[1].ssa;
nir_def *offset = nir_imm_int(b, 0x3);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_MAP,
.output = true,
.skew = false,
.per_primitive = false,
};
nir_isbewr_nv(b, primitive_count, offset,
.flags = NAK_AS_U32(flags));
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_load_workgroup_index(nir_builder *b,
nir_intrinsic_instr *intrin,
bool from_skew)
{
nir_function_impl *impl = nir_shader_get_entrypoint(b->shader);
/* We need to make sure that this is read before any writes to allow ISBE
* space sharing optimisation to happen */
b->cursor = nir_before_impl(impl);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = false,
.skew = from_skew,
.per_primitive = false,
};
nir_def *dst = nir_isberd_nv(b, 32, nir_imm_int(b, 0),
.range_base = NAK_ATTR_VERTEX_ID,
.range = 4,
.flags = NAK_AS_U32(flags));
nir_def_rewrite_uses(&intrin->def, dst);
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_load_num_workgroups(nir_builder *b, nir_intrinsic_instr *intrin)
{
/* If we are here, we have a task shader */
b->cursor = nir_before_instr(&intrin->instr);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = false,
.skew = false,
.per_primitive = false,
};
nir_def *x =
nir_isberd_nv(b, 32, nir_imm_int(b, 0x8), .flags = NAK_AS_U32(flags),
.access = ACCESS_CAN_REORDER);
nir_def *y =
nir_isberd_nv(b, 32, nir_imm_int(b, 0xC), .flags = NAK_AS_U32(flags),
.access = ACCESS_CAN_REORDER);
nir_def *z =
nir_isberd_nv(b, 32, nir_imm_int(b, 0x10), .flags = NAK_AS_U32(flags),
.access = ACCESS_CAN_REORDER);
nir_def *dst = nir_vec3(b, x, y, z);
nir_def_rewrite_uses(&intrin->def, dst);
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_load_shared(nir_builder *b, nir_intrinsic_instr *intrin,
uint32_t base_offset)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *offset = intrin->src[0].ssa;
const uint8_t bit_size = intrin->def.bit_size;
assert(bit_size == 32 && intrin->def.num_components == 1);
const uint32_t base = nir_intrinsic_base(intrin);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = true,
.skew = false,
.per_primitive = false,
};
offset = nir_iadd_imm(b, offset, base_offset + base);
nir_def *dst = nir_isberd_nv(b, 32, offset, .flags = NAK_AS_U32(flags));
nir_def_rewrite_uses(&intrin->def, dst);
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_store_shared(nir_builder *b, nir_intrinsic_instr *intrin,
uint32_t base_offset)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *value = intrin->src[0].ssa;
nir_def *offset = intrin->src[1].ssa;
const uint8_t bit_size = value->bit_size;
assert(bit_size == 32 &&
nir_intrinsic_write_mask(intrin) == nir_component_mask(1));
const uint32_t base = nir_intrinsic_base(intrin);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = true,
.skew = false,
.per_primitive = false,
};
offset = nir_iadd_imm(b, offset, base_offset + base);
nir_isbewr_nv(b, value, offset, .flags = NAK_AS_U32(flags));
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_load_task_payload(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *offset = intrin->src[0].ssa;
const uint8_t bit_size = intrin->def.bit_size;
assert(bit_size == 32 && intrin->def.num_components == 1);
const uint32_t base = nir_intrinsic_base(intrin);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = false,
.skew = false,
.per_primitive = false,
};
offset = nir_iadd_imm(b, offset, base);
nir_def *dst = nir_isberd_nv(b, 32, offset, .flags = NAK_AS_U32(flags),
.access = ACCESS_CAN_REORDER);
nir_def_rewrite_uses(&intrin->def, dst);
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_mesh_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
void *cb_data)
{
const struct lower_mesh_intrinsics_ctx *ctx = cb_data;
/* Shared memory is after attributes on mesh shaders */
const uint32_t shared_memory_base = nak_mesh_skew_total_size(ctx);
assert(shared_memory_base % 0x80 == 0);
switch (intrin->intrinsic) {
case nir_intrinsic_load_per_vertex_output:
case nir_intrinsic_load_per_primitive_output:
case nir_intrinsic_store_per_vertex_output:
case nir_intrinsic_store_per_primitive_output:
return lower_mesh_io_intrin(b, intrin, ctx);
case nir_intrinsic_set_vertex_and_primitive_count:
return lower_set_vertex_and_primitive_count(b, intrin);
case nir_intrinsic_load_workgroup_index:
return lower_load_workgroup_index(b, intrin, !ctx->has_task_shader);
case nir_intrinsic_load_num_workgroups:
return lower_load_num_workgroups(b, intrin);
case nir_intrinsic_load_shared:
return lower_load_shared(b, intrin, shared_memory_base);
case nir_intrinsic_store_shared:
return lower_store_shared(b, intrin, shared_memory_base);
case nir_intrinsic_load_task_payload:
return lower_load_task_payload(b, intrin);
case nir_intrinsic_shared_atomic:
case nir_intrinsic_shared_atomic_swap:
UNREACHABLE(
"Should have been lowered by nak_nir_lower_mesh_stages_shared_atomics");
default:
return false;
}
}
struct lower_emulated_attributes_state {
uint32_t viewport_shared_offset;
uint32_t cullprimitive_shared_offset;
};
static bool
lower_emulated_attributes_intrin(nir_builder *b, nir_intrinsic_instr *intrin,
void *_data)
{
const struct lower_emulated_attributes_state *state = _data;
nir_def *vtx = NULL, *offset = NULL;
switch (intrin->intrinsic) {
case nir_intrinsic_load_per_primitive_output:
vtx = intrin->src[0].ssa;
offset = intrin->src[1].ssa;
break;
case nir_intrinsic_store_per_primitive_output:
vtx = intrin->src[1].ssa;
offset = intrin->src[2].ssa;
break;
default:
return false;
}
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
if (sem.location != VARYING_SLOT_VIEWPORT &&
sem.location != VARYING_SLOT_CULL_PRIMITIVE)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *shared_offset = nir_iadd_imm(b, offset, nir_intrinsic_base(intrin));
shared_offset = nir_iadd(b, shared_offset, nir_imul_imm(b, vtx, 4));
if (sem.location == VARYING_SLOT_CULL_PRIMITIVE)
shared_offset =
nir_iadd_imm(b, shared_offset, state->cullprimitive_shared_offset);
else
shared_offset =
nir_iadd_imm(b, shared_offset, state->viewport_shared_offset);
if (intrin->intrinsic == nir_intrinsic_store_per_primitive_output) {
nir_def *data = intrin->src[0].ssa;
switch (sem.location) {
case VARYING_SLOT_VIEWPORT:
/* In case of Viewport, the data needs to be translated to a proper
* mask value to map to ViewportMask */
data = nir_ishl(b, nir_imm_int(b, 1), data);
break;
case VARYING_SLOT_CULL_PRIMITIVE:
/* In case of CullPrimitive, the data is already a 32-bit value so no
* translation is needed */
break;
default:
UNREACHABLE("Should never happen");
}
nir_store_shared(b, data, shared_offset);
nir_instr_remove(&intrin->instr);
} else {
/* Reading back isn't allowed by VK_EXT_mesh_shader but allowed by
* VK_NV_mesh_shader. We support readback for completeness and in case we
* add support for NV specific extension in the future */
nir_def *data = nir_load_shared(b, 1, 32, shared_offset);
switch (sem.location) {
case VARYING_SLOT_VIEWPORT:
/* In case of Viewport, find the first index that is set. */
data = nir_find_lsb(b, data);
break;
case VARYING_SLOT_CULL_PRIMITIVE:
/* In case of CullPrimitive, we check if no bits are set */
data = nir_ine_imm(b, data, 0);
break;
default:
UNREACHABLE("Should never happen");
}
nir_def_replace(&intrin->def, data);
}
return true;
}
bool
nak_nir_lower_mesh_emulated_attributes(nir_shader *nir)
{
if (nir->info.stage != MESA_SHADER_MESH)
return false;
/* Only apply this pass when really needed */
if ((nir->info.per_primitive_outputs &
(VARYING_BIT_CULL_PRIMITIVE | VARYING_BIT_VIEWPORT)) == 0)
return false;
/* If we are here, we need to emulate Viewport / CullPrimitive with the
* ViewportMask. This means if we need to always keep a shadow copy of the
* ViewportMask and CullPrimitive in shared memory and write the actual
* ViewportMask at the end of the shader. */
bool progress = false;
/* Reserve space for the Viewport and CullPrimitive shadow copies */
uint32_t shared_memory_offset = nir->info.shared_size;
nir->info.shared_size += 8 * nir->info.mesh.max_primitives_out;
struct lower_emulated_attributes_state state = {
.viewport_shared_offset = shared_memory_offset,
.cullprimitive_shared_offset =
shared_memory_offset + 4 * nir->info.mesh.max_primitives_out,
};
/* First we lower things to shared memory */
progress |= nir_shader_intrinsics_pass(nir, lower_emulated_attributes_intrin,
nir_metadata_control_flow, &state);
/* Finally, we ensure that the shared region is init at the start of the
* shader and we add primitive writes at the end of the shader to write the
* real value depending on the culling state.*/
if (progress) {
nir_function_impl *impl = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_at(nir_before_impl(impl));
nir_def *zero = nir_imm_int(&b, 0);
nir_def *viewport_default = nir_imm_int(&b, 1 << 0);
nir_def *lane_id =
nak_nir_load_sysval(&b, NAK_SV_LANE_ID, ACCESS_CAN_REORDER);
nir_push_if(&b, nir_ieq(&b, lane_id, zero));
{
for (uint32_t i = 0; i < nir->info.mesh.max_primitives_out; i++) {
nir_store_shared(
&b, viewport_default,
nir_imm_int(&b, state.viewport_shared_offset + i * 4));
nir_store_shared(
&b, zero,
nir_imm_int(&b, state.cullprimitive_shared_offset + i * 4));
}
}
nir_pop_if(&b, NULL);
nir_barrier(&b, SCOPE_WORKGROUP, SCOPE_WORKGROUP, NIR_MEMORY_ACQ_REL,
nir_var_mem_shared);
b = nir_builder_at(nir_after_impl(impl));
nir_barrier(&b, SCOPE_WORKGROUP, SCOPE_WORKGROUP, NIR_MEMORY_ACQ_REL,
nir_var_mem_shared);
nir_push_if(&b, nir_ieq(&b, lane_id, zero));
{
for (uint32_t i = 0; i < nir->info.mesh.max_primitives_out; i++) {
nir_def *viewport_mask = nir_load_shared(
&b, 1, 32,
nir_imm_int(&b, state.viewport_shared_offset + i * 4));
nir_def *cull_primitive = nir_load_shared(
&b, 1, 32,
nir_imm_int(&b, state.cullprimitive_shared_offset + i * 4));
viewport_mask = nir_bcsel(&b, nir_ine_imm(&b, cull_primitive, 0),
zero, viewport_mask);
nir_store_per_primitive_output(
&b, viewport_mask, nir_imm_int(&b, i), zero, .base = 0,
.src_type = nir_type_uint32,
.io_semantics = (nir_io_semantics){
.location = VARYING_SLOT_VIEWPORT_MASK,
.num_slots = 1,
});
}
}
nir_pop_if(&b, NULL);
}
return progress;
}
bool
nak_nir_lower_mesh_intrinsics(nir_shader *nir,
struct lower_mesh_intrinsics_ctx *ctx)
{
return nir_shader_intrinsics_pass(
nir, lower_mesh_intrin, nir_metadata_block_index | nir_metadata_dominance,
ctx);
}
static bool
lower_launch_mesh_workgroups(nir_builder *b, nir_intrinsic_instr *intrin)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *dim = intrin->src[0].ssa;
nir_def *x = nir_channel(b, dim, 0);
nir_def *y = nir_channel(b, dim, 1);
nir_def *z = nir_channel(b, dim, 2);
nir_def *task_count = nir_imul(b, nir_imul(b, x, y), z);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_ATTR,
.output = true,
.skew = false,
.per_primitive = false,
};
nir_isbewr_nv(b, task_count, nir_imm_int(b, 0x4),
.flags = NAK_AS_U32(flags));
nir_isbewr_nv(b, x, nir_imm_int(b, 0x8), .flags = NAK_AS_U32(flags));
nir_isbewr_nv(b, y, nir_imm_int(b, 0xC), .flags = NAK_AS_U32(flags));
nir_isbewr_nv(b, z, nir_imm_int(b, 0x10), .flags = NAK_AS_U32(flags));
nir_instr_remove(&intrin->instr);
return true;
}
static bool
lower_task_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
void *cb_data)
{
switch (intrin->intrinsic) {
case nir_intrinsic_load_shared:
return lower_load_shared(b, intrin, 0);
case nir_intrinsic_store_shared:
return lower_store_shared(b, intrin, 0);
case nir_intrinsic_load_workgroup_index:
return lower_load_workgroup_index(b, intrin, true);
case nir_intrinsic_launch_mesh_workgroups:
return lower_launch_mesh_workgroups(b, intrin);
case nir_intrinsic_shared_atomic:
case nir_intrinsic_shared_atomic_swap:
UNREACHABLE(
"Should have been lowered by nak_nir_lower_mesh_stages_shared_atomics");
case nir_intrinsic_load_task_payload:
case nir_intrinsic_store_task_payload:
case nir_intrinsic_task_payload_atomic:
case nir_intrinsic_task_payload_atomic_swap:
UNREACHABLE("Should have been lowered by nvk_nir_lower_task_shader");
default:
return false;
}
}
bool
nak_nir_lower_task_intrinsics(nir_shader *nir)
{
return nir_shader_intrinsics_pass(nir, lower_task_intrin,
nir_metadata_block_index |
nir_metadata_dominance,
NULL);
}
@@ -0,0 +1,518 @@
/*
* Copyright © 2024 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
#include "nir_vla.h"
/*
* This pass relies on nak_nir_mark_lcssa_invariants being run first, because it
* assumes that convergent values used in convergent control flow can be
* allocated to uniform registers. See the example in that file for more
* details.
*/
static void
lower_ldcx_to_global(nir_builder *b, nir_intrinsic_instr *load,
const struct nak_compiler *nak)
{
assert(load->intrinsic == nir_intrinsic_ldcx_nv);
b->cursor = nir_before_instr(&load->instr);
nir_def *handle = load->src[0].ssa;
assert(handle->bit_size == 64);
nir_def *offset = load->src[1].ssa;
assert(offset->bit_size == 32);
nir_def *addr;
nir_def *size;
if (nak->sm >= 100) {
/* Base address shifted by 6 is the bottom 51 bits */
nir_def *addr_6_lo = nir_unpack_64_2x32_split_x(b, handle);
nir_def *addr_6_hi = nir_iand_imm(b, nir_unpack_64_2x32_split_y(b, handle),
BITFIELD_MASK(19));
nir_def *addr_6 = nir_pack_64_2x32_split(b, addr_6_lo, addr_6_hi);
addr = nir_ishl_imm(b, addr_6, 6);
/* Size shifted by 2 is the top 13 bits */
size = nir_unpack_64_2x32_split_y(b, handle);
size = nir_ishl_imm(b, nir_ushr_imm(b, size, 19), 4);
} else {
/* Base address shifted by 4 is the bottom 45 bits */
nir_def *addr_4_lo = nir_unpack_64_2x32_split_x(b, handle);
nir_def *addr_4_hi = nir_iand_imm(b, nir_unpack_64_2x32_split_y(b, handle),
BITFIELD_MASK(13));
nir_def *addr_4 = nir_pack_64_2x32_split(b, addr_4_lo, addr_4_hi);
addr = nir_ishl_imm(b, addr_4, 4);
/* Size shifted by 2 is the top 19 bits */
size = nir_unpack_64_2x32_split_y(b, handle);
size = nir_ishl_imm(b, nir_ushr_imm(b, size, 13), 4);
}
/* At this point we can assume the offset is aligned so we only need a
* simple less-than check here.
*/
nir_def *cond = nir_ilt(b, offset, size);
nir_def *zero_addr = nir_imm_zero(b, addr->num_components,
addr->bit_size);
nir_def *val = nir_load_global_nv(b,
load->def.num_components, load->def.bit_size,
nir_iadd(b, addr, nir_u2u64(b, offset)),
zero_addr, cond,
.align_mul = nir_intrinsic_align_mul(load),
.align_offset = nir_intrinsic_align_offset(load),
.access = ACCESS_CAN_REORDER,
.base = nir_intrinsic_base(load));
nir_def_replace(&load->def, val);
}
static bool
lower_all_ldcx_to_global_intrin(nir_builder *b, nir_intrinsic_instr *load,
void *data)
{
if (load->intrinsic != nir_intrinsic_ldcx_nv)
return false;
lower_ldcx_to_global(b, load, data);
return true;
}
struct non_uniform_section {
nir_block *pred;
nir_block *succ;
struct hash_table *live_handles;
};
static void
non_uniform_section_init(struct non_uniform_section *nus,
nir_block *pred, nir_block *succ)
{
*nus = (struct non_uniform_section) {
.pred = pred,
.succ = succ,
.live_handles = _mesa_pointer_hash_table_create(NULL),
};
}
static void
non_uniform_section_finish(struct non_uniform_section *nus)
{
_mesa_hash_table_destroy(nus->live_handles, NULL);
}
static void
add_live_handle(nir_def *handle, struct non_uniform_section *nus)
{
uint32_t hash = _mesa_hash_pointer(handle);
struct hash_entry *entry =
_mesa_hash_table_search_pre_hashed(nus->live_handles, hash, handle);
if (entry == NULL) {
_mesa_hash_table_insert_pre_hashed(nus->live_handles, hash, handle,
(void *)(uintptr_t)1);
} else {
entry->data = (void *)(((uintptr_t)entry->data) + 1);
}
}
static bool
def_needs_hoist(nir_def *def, nir_block *target)
{
return nir_def_block(def)->index > target->index;
}
static bool
can_hoist_def(nir_def *def, nir_block *target)
{
if (!def_needs_hoist(def, target))
return true;
nir_instr *instr = nir_def_instr(def);
switch (instr->type) {
case nir_instr_type_alu: {
nir_alu_instr *alu = nir_instr_as_alu(instr);
for (uint8_t i = 0; i < nir_op_infos[alu->op].num_inputs; i++) {
if (!can_hoist_def(alu->src[i].src.ssa, target))
return false;
}
return true;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic != nir_intrinsic_ldc_nv &&
intrin->intrinsic != nir_intrinsic_ldcx_nv)
return false;
for (uint8_t i = 0; i < 2; i++) {
if (!can_hoist_def(intrin->src[i].ssa, target))
return false;
}
return true;
}
case nir_instr_type_load_const:
return true;
default:
return false;
}
}
static bool
hoist_def(nir_def *def, nir_block *target)
{
if (!def_needs_hoist(def, target))
return false;
nir_instr *instr = nir_def_instr(def);
switch (instr->type) {
case nir_instr_type_alu: {
nir_alu_instr *alu = nir_instr_as_alu(instr);
for (uint8_t i = 0; i < nir_op_infos[alu->op].num_inputs; i++)
hoist_def(alu->src[i].src.ssa, target);
break;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic != nir_intrinsic_ldc_nv &&
intrin->intrinsic != nir_intrinsic_ldcx_nv)
break;
for (uint8_t i = 0; i < 2; i++)
hoist_def(intrin->src[i].ssa, target);
break;
}
case nir_instr_type_load_const:
break;
default:
UNREACHABLE("Cannot hoist instruction");
}
nir_instr_remove(instr);
nir_instr_insert(nir_after_block(target), instr);
return true;
}
static bool
try_hoist_ldcx_handles_block(nir_block *block, struct non_uniform_section *nus)
{
bool progress = false;
nir_foreach_instr_safe(instr, block) {
switch (instr->type) {
case nir_instr_type_alu: {
/* ALU instructions may directly consume cbuf values so mark
* any ldcx_nv sources as also used.
*/
nir_alu_instr *alu = nir_instr_as_alu(instr);
for (uint8_t i = 0; i < nir_op_infos[alu->op].num_inputs; i++) {
nir_instr *src_instr = nir_def_instr(alu->src[i].src.ssa);
if (src_instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *src_intrin =
nir_instr_as_intrinsic(src_instr);
if (src_intrin->intrinsic != nir_intrinsic_ldcx_nv)
continue;
if (nir_intrinsic_access(src_intrin) & ACCESS_NON_UNIFORM)
continue;
/* If it really is a uniform handle, it should have been
* hoistd by now.
*/
nir_def *handle = src_intrin->src[0].ssa;
if (def_needs_hoist(handle, nus->pred))
continue;
add_live_handle(handle, nus);
}
break;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *load = nir_instr_as_intrinsic(instr);
if (load->intrinsic != nir_intrinsic_ldcx_nv)
break;
if (nir_intrinsic_access(load) & ACCESS_NON_UNIFORM)
break;
nir_def *handle = load->src[0].ssa;
if (can_hoist_def(handle, nus->pred)) {
progress |= hoist_def(handle, nus->pred);
add_live_handle(handle, nus);
}
break;
}
default:
break;
}
}
return progress;
}
struct handle {
nir_def *handle;
uint64_t sort_key;
};
static int
cmp_handles(const void *_a, const void *_b)
{
const struct handle *a = _a, *b = _b;
if (a->sort_key < b->sort_key)
return -1;
else if (a->sort_key > b->sort_key)
return 1;
else
return 0;
}
static bool
sort_and_mark_live_handles(nir_builder *b, struct non_uniform_section *nus)
{
/* There are 63 uniform registers and each handle takes 2 */
static const unsigned max_live_handles = 31;
const unsigned num_handles = nus->live_handles->entries;
if (num_handles == 0)
return false;
NIR_VLA(struct handle, handles, num_handles);
unsigned i = 0;
hash_table_foreach(nus->live_handles, entry) {
nir_def *handle = (nir_def *)entry->key;
uint64_t uses = (uintptr_t)entry->data;
handles[i++] = (struct handle) {
.handle = handle,
.sort_key = (uses << 32) | handle->index,
};
}
_mesa_hash_table_clear(nus->live_handles, NULL);
qsort(handles, num_handles, sizeof(*handles), cmp_handles);
b->cursor = nir_after_block(nus->pred);
for (unsigned i = 0; i < num_handles && i < max_live_handles; i++) {
nir_def *handle = handles[i].handle;
if (handle->divergent)
handle = nir_as_uniform(b, handles[i].handle);
nir_pin_cx_handle_nv(b, handle);
_mesa_hash_table_insert(nus->live_handles, handle, handle);
_mesa_hash_table_insert(nus->live_handles, handles[i].handle, handle);
}
b->cursor = nir_before_block_after_phis(nus->succ);
for (unsigned i = 0; i < num_handles && i < max_live_handles; i++)
nir_unpin_cx_handle_nv(b, handles[i].handle);
return true;
}
static bool
try_remat_ldcx_alu_use(nir_builder *b, nir_alu_instr *alu, uint8_t src_idx,
struct non_uniform_section *nus)
{
nir_instr *src_instr = nir_def_instr(alu->src[src_idx].src.ssa);
if (src_instr->type != nir_instr_type_intrinsic)
return false;
nir_intrinsic_instr *load = nir_instr_as_intrinsic(src_instr);
if (load->intrinsic != nir_intrinsic_ldcx_nv)
return false;
/* If it's already in the same block, there's nothing to do */
if (load->instr.block == alu->instr.block)
return false;
nir_def *handle = load->src[0].ssa;
/* Only constant offset can be used directly by ALU ops */
if (!nir_src_is_const(load->src[1]))
return false;
struct hash_entry *entry =
_mesa_hash_table_search(nus->live_handles, handle);
if (entry == NULL)
return false;
nir_intrinsic_instr *new_load =
nir_instr_as_intrinsic(nir_instr_clone(b->shader, &load->instr));
nir_instr_insert(nir_before_instr(&alu->instr), &new_load->instr);
nir_foreach_use_safe(use, &load->def) {
if (nir_src_use_instr(use)->type == nir_instr_type_alu &&
nir_src_use_instr(use)->block == alu->instr.block)
nir_src_rewrite(use, &new_load->def);
}
return true;
}
static bool
lower_ldcx_block(nir_builder *b, nir_block *block,
struct non_uniform_section *nus,
const struct nak_compiler *nak)
{
bool progress = false;
nir_foreach_instr_safe(instr, block) {
switch (instr->type) {
case nir_instr_type_alu: {
nir_alu_instr *alu = nir_instr_as_alu(instr);
if (nus != NULL) {
/* In non-uniform control-flow, NAK can't copy-prop cbuf values
* across blocks. Re-materialize cbufs that are used by ALU
* instructions in the same block.
*/
for (uint8_t i = 0; i < nir_op_infos[alu->op].num_inputs; i++)
progress |= try_remat_ldcx_alu_use(b, alu, i, nus);
}
break;
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *load = nir_instr_as_intrinsic(instr);
if (load->intrinsic != nir_intrinsic_ldcx_nv)
break;
nir_def *handle = load->src[0].ssa;
if (nus == NULL) {
/* Uniform control-flow */
if (nir_intrinsic_access(load) & ACCESS_NON_UNIFORM) {
lower_ldcx_to_global(b, load, nak);
progress = true;
} else if (handle->divergent) {
b->cursor = nir_before_instr(&load->instr);
nir_def *ugpr = nir_as_uniform(b, handle);
nir_src_rewrite(&load->src[0], ugpr);
progress = true;
}
} else {
struct hash_entry *entry =
_mesa_hash_table_search(nus->live_handles, handle);
if (entry != NULL) {
nir_def *ugpr = entry->data;
assert(!ugpr->divergent);
nir_src_rewrite(&load->src[0], ugpr);
} else {
lower_ldcx_to_global(b, load, nak);
}
progress = true;
}
break;
}
default:
break;
}
}
return progress;
}
static bool
lower_non_uniform_cf_node(nir_builder *b, nir_cf_node *node,
nir_block *pred, nir_block *succ,
const struct nak_compiler *nak)
{
bool progress = false;
struct non_uniform_section nus;
non_uniform_section_init(&nus, pred, succ);
/* This also marks any uniform handles it finds as live */
nir_foreach_block_in_cf_node(block, node)
progress |= try_hoist_ldcx_handles_block(block, &nus);
progress |= sort_and_mark_live_handles(b, &nus);
nir_foreach_block_in_cf_node_safe(block, node)
progress |= lower_ldcx_block(b, block, &nus, nak);
non_uniform_section_finish(&nus);
return progress;
}
static bool
lower_cf_list(nir_builder *b, struct exec_list *cf_list,
const struct nak_compiler *nak)
{
bool progress = false;
nir_block *block = NULL;
foreach_list_typed_safe(nir_cf_node, node, node, cf_list) {
switch (node->type) {
case nir_cf_node_block:
block = nir_cf_node_as_block(node);
progress |= lower_ldcx_block(b, block, NULL, nak);
continue;
case nir_cf_node_if: {
nir_if *nif = nir_cf_node_as_if(node);
if (nir_src_is_divergent(&nif->condition)) {
nir_block *succ = nir_cf_node_as_block(nir_cf_node_next(node));
progress |= lower_non_uniform_cf_node(b, node, block, succ, nak);
} else {
progress |= lower_cf_list(b, &nif->then_list, nak);
progress |= lower_cf_list(b, &nif->else_list, nak);
}
break;
}
case nir_cf_node_loop: {
nir_loop *loop = nir_cf_node_as_loop(node);
if (nir_loop_is_divergent(loop)) {
nir_block *succ = nir_cf_node_as_block(nir_cf_node_next(node));
progress |= lower_non_uniform_cf_node(b, node, block, succ, nak);
} else {
progress |= lower_cf_list(b, &loop->body, nak);
progress |= lower_cf_list(b, &loop->continue_list, nak);
}
break;
}
default:
UNREACHABLE("Unknown CF node type");
}
}
return progress;
}
bool
nak_nir_lower_non_uniform_ldcx(nir_shader *nir,
const struct nak_compiler *nak)
{
/* If we don't have UGPRs, lower all of them. */
if (nak_debug_no_ugpr()) {
return nir_shader_intrinsics_pass(nir, lower_all_ldcx_to_global_intrin,
nir_metadata_none, (void *)nak);
}
/* Real functions are going to make hash of this */
nir_function_impl *impl = nir_shader_get_entrypoint(nir);
nir_builder b = nir_builder_create(impl);
/* We use block indices to determine when something is a predecessor */
nir_metadata_require(impl, nir_metadata_block_index | nir_metadata_divergence);
bool progress = lower_cf_list(&b, &impl->body, nak);
return nir_progress(progress, impl, nir_metadata_none);
}
@@ -0,0 +1,272 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
static nir_def *
cluster_mask(nir_builder *b, unsigned cluster_size)
{
nir_def *mask = nir_ballot(b, 1, 32, nir_imm_true(b));
if (cluster_size < 32) {
nir_def *idx = nir_load_subgroup_invocation(b);
nir_def *cluster = nir_iand_imm(b, idx, ~(uint64_t)(cluster_size - 1));
nir_def *cluster_mask = nir_imm_int(b, BITFIELD_MASK(cluster_size));
cluster_mask = nir_ishl(b, cluster_mask, cluster);
mask = nir_iand(b, mask, cluster_mask);
}
return mask;
}
static nir_def *
build_scan_bool(nir_builder *b, nir_intrinsic_op op, nir_op red_op,
nir_def *data, unsigned cluster_size)
{
/* Handle a couple of special cases first */
if (op == nir_intrinsic_reduce && cluster_size == 32) {
switch (red_op) {
case nir_op_iand:
return nir_vote_all(b, 1, data);
case nir_op_ior:
return nir_vote_any(b, 1, data);
case nir_op_ixor:
/* The generic path is fine */
break;
default:
UNREACHABLE("Unsupported boolean reduction op");
}
}
nir_def *mask = cluster_mask(b, cluster_size);
switch (op) {
case nir_intrinsic_exclusive_scan:
mask = nir_iand(b, mask, nir_load_subgroup_lt_mask(b, 1, 32));
break;
case nir_intrinsic_inclusive_scan:
mask = nir_iand(b, mask, nir_load_subgroup_le_mask(b, 1, 32));
break;
case nir_intrinsic_reduce:
break;
default:
UNREACHABLE("Unsupported scan/reduce op");
}
data = nir_ballot(b, 1, 32, data);
switch (red_op) {
case nir_op_iand:
return nir_ieq_imm(b, nir_iand(b, nir_inot(b, data), mask), 0);
case nir_op_ior:
return nir_ine_imm(b, nir_iand(b, data, mask), 0);
case nir_op_ixor: {
nir_def *count = nir_bit_count(b, nir_iand(b, data, mask));
return nir_ine_imm(b, nir_iand_imm(b, count, 1), 0);
}
default:
UNREACHABLE("Unsupported boolean reduction op");
}
}
static nir_def *
build_identity(nir_builder *b, unsigned bit_size, nir_op op)
{
nir_const_value ident_const = nir_alu_binop_identity(op, bit_size);
return nir_build_imm(b, 1, bit_size, &ident_const);
}
/* Implementation of scan/reduce that assumes a full subgroup */
static nir_def *
build_scan_full(nir_builder *b, nir_intrinsic_op op, nir_op red_op,
nir_def *data, unsigned cluster_size)
{
switch (op) {
case nir_intrinsic_exclusive_scan:
case nir_intrinsic_inclusive_scan: {
for (unsigned i = 1; i < cluster_size; i *= 2) {
nir_def *idx = nir_load_subgroup_invocation(b);
nir_def *has_buddy = nir_ige_imm(b, idx, i);
nir_def *buddy_data = nir_shuffle_up(b, data, nir_imm_int(b, i));
nir_def *accum = nir_build_alu2(b, red_op, data, buddy_data);
data = nir_bcsel(b, has_buddy, accum, data);
}
if (op == nir_intrinsic_exclusive_scan) {
/* For exclusive scans, we need to shift one more time and fill in the
* bottom channel with identity.
*/
assert(cluster_size == 32);
nir_def *idx = nir_load_subgroup_invocation(b);
nir_def *has_buddy = nir_ige_imm(b, idx, 1);
nir_def *buddy_data = nir_shuffle_up(b, data, nir_imm_int(b, 1));
nir_def *identity = build_identity(b, data->bit_size, red_op);
data = nir_bcsel(b, has_buddy, buddy_data, identity);
}
return data;
}
case nir_intrinsic_reduce: {
for (unsigned i = 1; i < cluster_size; i *= 2) {
nir_def *buddy_data = nir_shuffle_xor(b, data, nir_imm_int(b, i));
data = nir_build_alu2(b, red_op, data, buddy_data);
}
return data;
}
default:
UNREACHABLE("Unsupported scan/reduce op");
}
}
/* Fully generic implementation of scan/reduce that takes a mask */
static nir_def *
build_scan_reduce(nir_builder *b, nir_intrinsic_op op, nir_op red_op,
nir_def *data, nir_def *mask, unsigned max_mask_bits)
{
nir_def *lt_mask = nir_load_subgroup_lt_mask(b, 1, 32);
/* Mask of all channels whose values we need to accumulate. Our own value
* is already in accum, if inclusive, thanks to the initialization above.
* We only need to consider lower indexed invocations.
*/
nir_def *remaining = nir_iand(b, mask, lt_mask);
for (unsigned i = 1; i < max_mask_bits; i *= 2) {
/* At each step, our buddy channel is the first channel we have yet to
* take into account in the accumulator.
*/
nir_def *has_buddy = nir_ine_imm(b, remaining, 0);
nir_def *buddy = nir_ufind_msb(b, remaining);
/* Accumulate with our buddy channel, if any */
nir_def *buddy_data = nir_shuffle(b, data, buddy);
nir_def *accum = nir_build_alu2(b, red_op, data, buddy_data);
data = nir_bcsel(b, has_buddy, accum, data);
/* We just took into account everything in our buddy's accumulator from
* the previous step. The only things remaining are whatever channels
* were remaining for our buddy.
*/
nir_def *buddy_remaining = nir_shuffle(b, remaining, buddy);
remaining = nir_bcsel(b, has_buddy, buddy_remaining, nir_imm_int(b, 0));
}
switch (op) {
case nir_intrinsic_exclusive_scan: {
/* For exclusive scans, we need to shift one more time and fill in the
* bottom channel with identity.
*
* Some of this will get CSE'd with the first step but that's okay. The
* code is cleaner this way.
*/
nir_def *lower = nir_iand(b, mask, lt_mask);
nir_def *has_buddy = nir_ine_imm(b, lower, 0);
nir_def *buddy = nir_ufind_msb(b, lower);
nir_def *buddy_data = nir_shuffle(b, data, buddy);
nir_def *identity = build_identity(b, data->bit_size, red_op);
return nir_bcsel(b, has_buddy, buddy_data, identity);
}
case nir_intrinsic_inclusive_scan:
return data;
case nir_intrinsic_reduce: {
/* For reductions, we need to take the top value of the scan */
nir_def *idx = nir_ufind_msb(b, mask);
return nir_shuffle(b, data, idx);
}
default:
UNREACHABLE("Unsupported scan/reduce op");
}
}
static bool
nak_nir_lower_scan_reduce_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
void *_nak)
{
const struct nak_compiler *nak = (const struct nak_compiler *) _nak;
switch (intrin->intrinsic) {
case nir_intrinsic_exclusive_scan:
case nir_intrinsic_inclusive_scan:
case nir_intrinsic_reduce:
break;
default:
return false;
}
const nir_op red_op = nir_intrinsic_reduction_op(intrin);
/* Grab the cluster size, defaulting to 32 */
unsigned cluster_size = 32;
if (nir_intrinsic_has_cluster_size(intrin)) {
cluster_size = nir_intrinsic_cluster_size(intrin);
if (cluster_size == 0 || cluster_size > 32)
cluster_size = 32;
}
b->cursor = nir_before_instr(&intrin->instr);
nir_def *data;
if (cluster_size == 1) {
/* Simple case where we're not actually doing any reducing at all. */
assert(intrin->intrinsic == nir_intrinsic_reduce);
data = intrin->src[0].ssa;
} else if (intrin->intrinsic == nir_intrinsic_reduce &&
nak->sm >= 80 &&
red_op != nir_op_imul &&
nir_op_infos[red_op].output_type != nir_type_float &&
intrin->src[0].ssa->bit_size == 32 &&
cluster_size == 32 &&
!nak_block_is_divergent(intrin->instr.block)) {
/* TODO: We could probably also use REDUX for the non-uniform case if we
* were allowed to write uregs from non-uniform control flow.
*/
return false;
} else if (intrin->src[0].ssa->bit_size == 1) {
data = build_scan_bool(b, intrin->intrinsic, red_op,
intrin->src[0].ssa, cluster_size);
} else {
/* First, we need a mask of all invocations to be included in the
* reduction or scan. For trivial cluster sizes, that's just the mask
* of enabled channels.
*/
nir_def *mask = cluster_mask(b, cluster_size);
nir_def *full, *partial;
nir_push_if(b, nir_ieq_imm(b, mask, -1));
{
full = build_scan_full(b, intrin->intrinsic, red_op,
intrin->src[0].ssa, cluster_size);
}
nir_push_else(b, NULL);
{
partial = build_scan_reduce(b, intrin->intrinsic, red_op,
intrin->src[0].ssa, mask, cluster_size);
}
nir_pop_if(b, NULL);
data = nir_if_phi(b, full, partial);
}
nir_def_replace(&intrin->def, data);
return true;
}
bool
nak_nir_lower_scan_reduce(nir_shader *nir, const struct nak_compiler *nak)
{
return nir_shader_intrinsics_pass(nir, nak_nir_lower_scan_reduce_intrin,
nir_metadata_none, (void*) nak);
}
@@ -0,0 +1,187 @@
/*
* Copyright © 2026 Valve Corporation.
* Copyright © 2025 Lorenzo Rossi
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir.h"
#include "nir_builder.h"
/*
* Convert atomic arithmetic to regular arithmetic along with mutex locks.
*
* eg:
* atomicAdd(addr, 1) ->
*
* uint expected = a[0];
* bool success = false;
* do {
* data, is_locked = load_locked(a[0])
* if (is_locked) {
* data = data + 1;
* success = store_and_unlock(&a[0], data);
* }
* } while (!success);
*
* special_case cmp_exc and exc.
*/
static nir_def *
lower_atomic_op(nir_builder *b, nir_intrinsic_instr *intr, nir_def *loaded)
{
// Assume we have the lock, the previous value is in loaded and we must
// compute the value to store in the address.
// to_store = op(loaded, data)
nir_def *data = intr->src[1].ssa;
nir_def *to_store;
switch (nir_intrinsic_atomic_op(intr)) {
case nir_atomic_op_imin:
case nir_atomic_op_umin:
case nir_atomic_op_imax:
case nir_atomic_op_umax:
case nir_atomic_op_iand:
case nir_atomic_op_ior:
case nir_atomic_op_ixor:
case nir_atomic_op_fadd:
case nir_atomic_op_fmin:
case nir_atomic_op_fmax:
case nir_atomic_op_iadd: {
to_store = nir_build_alu2(
b, nir_atomic_op_to_alu(nir_intrinsic_atomic_op(intr)), loaded, data);
nir_alu_instr *alu = nir_def_as_alu(to_store);
alu->fp_math_ctrl = nir_op_valid_fp_math_ctrl(alu->op, nir_fp_no_fast_math);
break;
}
case nir_atomic_op_xchg: {
// op(loaded, data) = data
to_store = data;
break;
}
case nir_atomic_op_cmpxchg: {
// op(loaded, src1, src2) = loaded == src1 ? src2 : loaded;
nir_def *new_data = intr->src[2].ssa;
to_store = nir_bcsel(b, nir_ieq(b, loaded, data), new_data, loaded);
break;
}
case nir_atomic_op_fcmpxchg: /* TODO: shared atomic floats */
default:
UNREACHABLE("Invalid intrinsic");
}
return to_store;
}
static nir_def *
build_kepler_atomic(nir_builder *b, nir_intrinsic_instr *intr)
{
// TODO: this is currently compiled down to ~20 instructions while
// CUDA can optimize the same code to only ~5.
nir_def *loaded_data;
nir_def *addr = intr->src[0].ssa;
nir_loop *loop = nir_push_loop(b);
{
nir_def *load = nir_load_shared_lock_nv(b, intr->def.bit_size, addr);
loaded_data = nir_channel(b, load, 0);
nir_def *is_locked = nir_u2u32(b, nir_channel(b, load, 1));
nir_if *nif = nir_push_if(b, nir_ine_imm(b, is_locked, 0));
{
nir_def *new_data = lower_atomic_op(b, intr, loaded_data);
nir_def *success = nir_store_shared_unlock_nv(b, 32, new_data, addr);
nir_break_if(b, nir_ine_imm(b, success, 0));
}
nir_pop_if(b, nif);
}
nir_pop_loop(b, loop);
return loaded_data;
}
static bool
nak_nir_lower_kepler_atomics_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
UNUSED void *_data)
{
if (intrin->intrinsic != nir_intrinsic_shared_atomic &&
intrin->intrinsic != nir_intrinsic_shared_atomic_swap)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def_replace(&intrin->def, build_kepler_atomic(b, intrin));
return true;
}
bool
nak_nir_lower_kepler_shared_atomics(nir_shader *nir)
{
return nir_shader_intrinsics_pass(nir, nak_nir_lower_kepler_atomics_intrin,
nir_metadata_none, NULL);
}
static nir_def *
build_mesh_atomic(nir_builder *b, nir_intrinsic_instr *intrin)
{
nir_def *current_value;
nir_def *offset = intrin->src[0].ssa;
nir_def *current_invocation = nir_load_subgroup_invocation(b);
/* Basic spin lock implementation */
nir_loop *loop = nir_push_loop(b);
{
/* First we check what active threads match our offset value */
nir_def *active_thread_mask = nir_match_any_nv(b, 32, offset);
/* Then we elect a thread to work in this loop iteration */
nir_def *elected_thread = nir_ufind_msb(b, active_thread_mask);
/* Check if the current invocation won and if so do the operation */
nir_if *if_body =
nir_push_if(b, nir_ieq(b, elected_thread, current_invocation));
{
current_value = nir_load_shared(b, 1, intrin->def.bit_size, offset,
.base = nir_intrinsic_base(intrin));
nir_def *new_value = lower_atomic_op(b, intrin, current_value);
nir_store_shared(b, new_value, offset,
.base = nir_intrinsic_base(intrin));
nir_jump(b, nir_jump_break);
}
nir_pop_if(b, if_body);
}
nir_pop_loop(b, loop);
return current_value;
}
static bool
nak_nir_lower_mesh_stages_shared_atomics_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
UNUSED void *_data)
{
if (intrin->intrinsic != nir_intrinsic_shared_atomic &&
intrin->intrinsic != nir_intrinsic_shared_atomic_swap)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def_replace(&intrin->def, build_mesh_atomic(b, intrin));
return true;
}
bool
nak_nir_lower_mesh_stages_shared_atomics(nir_shader *nir)
{
if (!mesa_shader_stage_is_mesh(nir->info.stage))
return false;
/* The local workgroup should have been lowered to a single subgroup. */
ASSERTED uint16_t wg_sz = nir->info.workgroup_size[0] *
nir->info.workgroup_size[1] *
nir->info.workgroup_size[2];
assert(wg_sz <= NAK_SUBGROUP_SIZE);
return nir_shader_intrinsics_pass(
nir, nak_nir_lower_mesh_stages_shared_atomics_intrin, nir_metadata_none,
NULL);
}
+939
View File
@@ -0,0 +1,939 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
#include "nir_format_convert.h"
#include "util/u_math.h"
static bool
has_null_descriptors(const struct nak_compiler *nak) {
/* We only have "real" null descriptors on Volta+ */
return nak->sm >= 70;
}
static bool
has_cbuf_tex(const struct nak_compiler *nak) {
/* TODO: Figure out how bound textures work on blackwell */
return nak->sm >= 70 && nak->sm < 100;
}
static bool
tex_handle_as_cbuf(nir_def *tex_h, uint32_t *cbuf_out)
{
if (!nir_def_is_intrinsic(tex_h))
return false;
nir_intrinsic_instr *intrin = nir_def_as_intrinsic(tex_h);
if (intrin->intrinsic != nir_intrinsic_ldc_nv)
return false;
if (!nir_src_is_const(intrin->src[1]))
return false;
uint32_t idx = nir_src_as_uint(intrin->src[0]);
uint32_t offset = nir_src_as_uint(intrin->src[1]);
assert(idx < (1 << 5) && offset < (1 << 16));
*cbuf_out = (idx << 16) | offset;
return true;
}
static nir_def *
build_txq(nir_builder *b, nir_texop op, nir_def *img_h, nir_def *lod_idx,
bool can_speculate, const struct nak_compiler *nak)
{
uint32_t texture_index = 0;
enum nak_nir_tex_ref_type ref_type = NAK_NIR_TEX_REF_TYPE_BINDLESS;
if (has_cbuf_tex(nak) && tex_handle_as_cbuf(img_h, &texture_index)) {
ref_type = NAK_NIR_TEX_REF_TYPE_CBUF;
img_h = NULL;
}
nir_tex_instr *txq = nir_tex_instr_create(b->shader, 1);
txq->op = op,
/* txq doesn't take any dimension parameters so we just smash in 2D because
* NIR needs us to set something. And using the same dimensionality for
* everything gives us maximum CSE.
*/
txq->sampler_dim = GLSL_SAMPLER_DIM_2D,
txq->is_array = false,
txq->dest_type = nir_type_int32;
txq->can_speculate = can_speculate;
nir_def *src[2] = { NULL, };
unsigned src_comps = 0;
if (img_h != NULL)
src[src_comps++] = img_h;
if (lod_idx != NULL)
src[src_comps++] = lod_idx;
if (src_comps == 0)
src[src_comps++] = nir_imm_int(b, 0);
nir_def *txq_src = nir_vec(b, src, src_comps);
txq->src[0] = (nir_tex_src) {
.src_type = nir_tex_src_backend1,
.src = nir_src_for_ssa(txq_src),
};
txq->texture_index = texture_index;
const struct nak_nir_tex_flags flags = {
.ref_type = ref_type,
};
txq->backend_flags = NAK_AS_U32(flags);
nir_def_init(&txq->instr, &txq->def, 4, 32);
nir_builder_instr_insert(b, &txq->instr);
return &txq->def;
}
static nir_def *
build_txq_levels(nir_builder *b, nir_def *img_h, bool can_speculate,
const struct nak_compiler *nak)
{
nir_def *res = build_txq(b, nir_texop_hdr_dim_nv, img_h, nir_imm_int(b, 0),
can_speculate, nak);
return nir_channel(b, res, 3);
}
static nir_def *
build_img_is_null(nir_builder *b, nir_def *img_h, bool can_speculate,
const struct nak_compiler *nak)
{
/* Prior to Volta, we don't have real NULL descriptors but we can figure
* out if it's null based on the number of levels returned by
* txq.dimension.
*/
return nir_ieq_imm(b, build_txq_levels(b, img_h, can_speculate, nak), 0);
}
static enum glsl_sampler_dim
remap_sampler_dim(enum glsl_sampler_dim dim)
{
switch (dim) {
case GLSL_SAMPLER_DIM_SUBPASS: return GLSL_SAMPLER_DIM_2D;
case GLSL_SAMPLER_DIM_SUBPASS_MS: return GLSL_SAMPLER_DIM_MS;
default: return dim;
}
}
static bool
texop_supports_scalar(nir_tex_instr *tex, const struct nak_compiler *nak)
{
if (nak->sm < 70)
return false;
switch (tex->op) {
case nir_texop_tex:
case nir_texop_tg4:
case nir_texop_txb:
case nir_texop_txf:
case nir_texop_txf_ms:
case nir_texop_txl:
return true;
default:
return false;
}
}
static bool
try_scalarize_tex(nir_tex_instr *tex, const struct nak_compiler *nak,
nir_def **src0, unsigned *src0_comps,
nir_def **src1, unsigned *src1_comps)
{
if (!texop_supports_scalar(tex, nak))
return false;
/* for SM70+ we simply rebalance the sources if there are 4 or less
* TODO: it might be worth to rebalance 3 + 1 or 1 + 3 combinations,
* but so far this seems to be a net negative.
*/
if (nak->sm >= 70 && *src1_comps == 0) {
assert(*src0_comps <= 4);
switch (*src0_comps) {
case 4:
src1[0] = src0[2];
src1[1] = src0[3];
*src0_comps = 2;
*src1_comps = 2;
return true;
case 3:
src1[0] = src0[2];
*src0_comps = 2;
*src1_comps = 1;
return true;
case 2:
src1[0] = src0[1];
*src0_comps = 1;
*src1_comps = 1;
return true;
case 1:
/* Nothing to do */
return true;
default:
UNREACHABLE("unexpected src0 comps");
return false;
}
}
return false;
}
static bool
lower_tex(nir_builder *b, nir_tex_instr *tex, const struct nak_compiler *nak)
{
b->cursor = nir_before_instr(&tex->instr);
nir_def *tex_h = NULL, *samp_h = NULL, *coord = NULL, *ms_idx = NULL;
nir_def *offset = NULL, *lod = NULL, *bias = NULL, *min_lod = NULL;
nir_def *ddx = NULL, *ddy = NULL, *z_cmpr = NULL;
for (unsigned i = 0; i < tex->num_srcs; i++) {
switch (tex->src[i].src_type) {
case nir_tex_src_texture_handle: tex_h = tex->src[i].src.ssa; break;
case nir_tex_src_sampler_handle: samp_h = tex->src[i].src.ssa; break;
case nir_tex_src_coord: coord = tex->src[i].src.ssa; break;
case nir_tex_src_ms_index: ms_idx = tex->src[i].src.ssa; break;
case nir_tex_src_comparator: z_cmpr = tex->src[i].src.ssa; break;
case nir_tex_src_offset: offset = tex->src[i].src.ssa; break;
case nir_tex_src_lod: lod = tex->src[i].src.ssa; break;
case nir_tex_src_bias: bias = tex->src[i].src.ssa; break;
case nir_tex_src_min_lod: min_lod = tex->src[i].src.ssa; break;
case nir_tex_src_ddx: ddx = tex->src[i].src.ssa; break;
case nir_tex_src_ddy: ddy = tex->src[i].src.ssa; break;
default:
UNREACHABLE("Unsupported texture source");
}
/* Remove sources as we walk them. We'll add them back later */
nir_instr_clear_src(&tex->instr, &tex->src[i].src);
}
tex->num_srcs = 0;
/* Combine sampler and texture into one if needed */
if (samp_h != NULL && samp_h != tex_h) {
tex_h = nir_ior(b, nir_iand_imm(b, tex_h, 0x000fffff),
nir_iand_imm(b, samp_h, 0xfff00000));
}
enum nak_nir_tex_ref_type ref_type = NAK_NIR_TEX_REF_TYPE_BINDLESS;
if (has_cbuf_tex(nak) && tex_handle_as_cbuf(tex_h, &tex->texture_index)) {
ref_type = NAK_NIR_TEX_REF_TYPE_CBUF;
tex_h = NULL;
}
/* Array index is treated separately, so pull it off if we have one. */
nir_def *arr_idx = NULL;
unsigned coord_components = tex->coord_components;
if (coord && tex->is_array) {
if (tex->op == nir_texop_lod) {
/* The HW wants an array index. Use zero. */
arr_idx = nir_imm_int(b, 0);
} else {
arr_idx = nir_channel(b, coord, --coord_components);
/* Everything but texelFetch takes a float index
*
* TODO: Use F2I.U32.RNE
*/
if (tex->op != nir_texop_txf && tex->op != nir_texop_txf_ms) {
arr_idx = nir_fadd_imm(b, arr_idx, 0.5);
// TODO: Hardware seems to clamp negative values to zero for us
// in f2u, but we still need this fmax for constant folding.
arr_idx = nir_fmax(b, arr_idx, nir_imm_float(b, 0.0));
arr_idx = nir_f2u32(b, arr_idx);
}
arr_idx = nir_umin(b, arr_idx, nir_imm_int(b, UINT16_MAX));
}
}
enum nak_nir_lod_mode lod_mode = NAK_NIR_LOD_MODE_AUTO;
if (tex->op == nir_texop_txf_ms) {
/* Multisampled textures do not have miplevels */
lod_mode = NAK_NIR_LOD_MODE_ZERO;
lod = NULL; /* We don't need this */
} else if (lod != NULL) {
nir_scalar lod_s = { .def = lod, .comp = 0 };
if (nir_scalar_is_const(lod_s) &&
nir_scalar_as_uint(lod_s) == 0) {
lod_mode = NAK_NIR_LOD_MODE_ZERO;
lod = NULL; /* We don't need this */
} else {
lod_mode = NAK_NIR_LOD_MODE_LOD;
}
} else if (bias != NULL) {
lod_mode = NAK_NIR_LOD_MODE_BIAS;
lod = bias;
}
if (min_lod != NULL) {
switch (lod_mode) {
case NAK_NIR_LOD_MODE_AUTO:
lod_mode = NAK_NIR_LOD_MODE_CLAMP;
break;
case NAK_NIR_LOD_MODE_BIAS:
lod_mode = NAK_NIR_LOD_MODE_BIAS_CLAMP;
break;
default:
UNREACHABLE("Invalid min_lod");
}
min_lod = nir_f2u32(b, nir_fmax(b, nir_fmul_imm(b, min_lod, 256),
nir_imm_float(b, 16)));
}
enum nak_nir_offset_mode offset_mode = NAK_NIR_OFFSET_MODE_NONE;
if (offset != NULL) {
/* For TG4, offsets, are packed into a single 32-bit value with 8 bits
* per component. For all other texture instructions, offsets are
* packed into a single at most 16-bit value with 8 bits per component.
*/
static const unsigned bits4[] = { 4, 4, 4, 4 };
static const unsigned bits8[] = { 8, 8, 8, 8 };
const unsigned *bits = tex->op == nir_texop_tg4 ? bits8 : bits4;
offset = nir_pad_vector_imm_int(b, offset, 0, 4);
offset = nir_format_clamp_sint(b, offset, bits);
offset = nir_format_pack_uint(b, offset, bits, 4);
offset_mode = NAK_NIR_OFFSET_MODE_AOFFI;
} else if (nir_tex_instr_has_explicit_tg4_offsets(tex)) {
uint64_t off_u64 = 0;
for (uint8_t i = 0; i < 8; ++i) {
uint64_t off = (uint8_t)tex->tg4_offsets[i / 2][i % 2];
off_u64 |= off << (i * 8);
}
offset = nir_imm_ivec2(b, off_u64, off_u64 >> 32);
offset_mode = NAK_NIR_OFFSET_MODE_PER_PX;
}
#define PUSH(a, x) do { \
nir_def *val = (x); \
assert(a##_comps < ARRAY_SIZE(a)); \
a[a##_comps++] = val; \
} while(0)
bool scalar = false;
if (nak->sm >= 50) {
nir_def *src0[4] = { NULL, };
nir_def *src1[4] = { NULL, };
unsigned src0_comps = 0, src1_comps = 0;
if (tex->op == nir_texop_txd) {
if (tex_h != NULL)
PUSH(src0, tex_h);
for (uint32_t i = 0; i < coord_components; i++)
PUSH(src0, nir_channel(b, coord, i));
if (offset != NULL) {
nir_def *arr_idx_or_zero = arr_idx ? arr_idx : nir_imm_int(b, 0);
nir_def *arr_off = nir_prmt_nv(b, nir_imm_int(b, 0x1054),
offset, arr_idx_or_zero);
PUSH(src0, arr_off);
} else if (arr_idx != NULL) {
PUSH(src0, arr_idx);
}
assert(ddx->num_components == coord_components);
for (uint32_t i = 0; i < coord_components; i++) {
PUSH(src1, nir_channel(b, ddx, i));
PUSH(src1, nir_channel(b, ddy, i));
}
} else {
if (min_lod != NULL) {
nir_def *arr_idx_or_zero = arr_idx ? arr_idx : nir_imm_int(b, 0);
nir_def *arr_ml = nir_prmt_nv(b, nir_imm_int(b, 0x1054),
min_lod, arr_idx_or_zero);
PUSH(src0, arr_ml);
} else if (arr_idx != NULL) {
PUSH(src0, arr_idx);
}
for (uint32_t i = 0; i < coord_components; i++)
PUSH(src0, nir_channel(b, coord, i));
if (tex_h != NULL)
PUSH(src1, tex_h);
if (lod != NULL)
PUSH(src1, lod);
if (offset_mode == NAK_NIR_OFFSET_MODE_AOFFI) {
PUSH(src1, offset);
} else if (offset_mode == NAK_NIR_OFFSET_MODE_PER_PX) {
PUSH(src1, nir_channel(b, offset, 0));
PUSH(src1, nir_channel(b, offset, 1));
}
if (z_cmpr != NULL)
PUSH(src1, z_cmpr);
if (ms_idx != NULL)
PUSH(src1, ms_idx);
}
scalar = try_scalarize_tex(tex, nak, src0, &src0_comps, src1, &src1_comps);
nir_tex_instr_add_src(tex, nir_tex_src_backend1,
nir_vec(b, src0, src0_comps));
if (src1_comps > 0) {
nir_tex_instr_add_src(tex, nir_tex_src_backend2,
nir_vec(b, src1, src1_comps));
}
} else if (nak->sm >= 30) {
nir_def *src[8] = { NULL, };
unsigned src_comps = 0;
if (tex_h != NULL)
PUSH(src, tex_h);
if (offset != NULL && tex->op == nir_texop_txd) {
nir_def *arr_idx_or_zero = arr_idx ? arr_idx : nir_imm_int(b, 0);
// TODO: This may be backwards?
nir_def *arr_off = nir_prmt_nv(b, nir_imm_int(b, 0x1054),
offset, arr_idx_or_zero);
PUSH(src, arr_off);
} else if (arr_idx != NULL) {
PUSH(src, arr_idx);
}
for (uint32_t i = 0; i < coord_components; i++)
PUSH(src, nir_channel(b, coord, i));
if (ms_idx != NULL)
PUSH(src, ms_idx);
if (lod != NULL)
PUSH(src, lod);
if (tex->op != nir_texop_txd) {
if (offset_mode == NAK_NIR_OFFSET_MODE_AOFFI) {
PUSH(src, offset);
} else if (offset_mode == NAK_NIR_OFFSET_MODE_PER_PX) {
PUSH(src, nir_channel(b, offset, 0));
PUSH(src, nir_channel(b, offset, 1));
}
}
if (z_cmpr != NULL)
PUSH(src, z_cmpr);
if (tex->op == nir_texop_txd) {
assert(ddx->num_components == coord_components);
for (uint32_t i = 0; i < coord_components; i++) {
PUSH(src, nir_channel(b, ddx, i));
PUSH(src, nir_channel(b, ddy, i));
}
}
/* Both sources are vec4s so we need an even multiple of 4 */
while (src_comps % 4)
PUSH(src, nir_undef(b, 1, 32));
nir_tex_instr_add_src(tex, nir_tex_src_backend1,
nir_vec(b, src, 4));
if (src_comps > 4) {
nir_tex_instr_add_src(tex, nir_tex_src_backend2,
nir_vec(b, src + 4, 4));
}
} else {
UNREACHABLE("Unsupported shader model");
}
tex->sampler_dim = remap_sampler_dim(tex->sampler_dim);
struct nak_nir_tex_flags flags = {
.ref_type = ref_type,
.lod_mode = lod_mode,
.offset_mode = offset_mode,
.has_z_cmpr = tex->is_shadow,
.is_sparse = tex->is_sparse,
.nodep = tex->skip_helpers,
.scalar = scalar,
};
STATIC_ASSERT(sizeof(flags) == sizeof(tex->backend_flags));
memcpy(&tex->backend_flags, &flags, sizeof(flags));
if (tex->op == nir_texop_lod) {
b->cursor = nir_after_instr(&tex->instr);
/* The outputs are flipped compared to what NIR expects */
nir_def *abs = nir_channel(b, &tex->def, 1);
nir_def *rel = nir_channel(b, &tex->def, 0);
/* The returned values are not quite what we want:
* (a) convert from s16/u16 to f32
* (b) multiply by 1/256
*
* TODO: We can make this cheaper once we have 16-bit in NAK
*/
abs = nir_u2f32(b, nir_iand_imm(b, abs, 0xffff));
nir_def *shift = nir_imm_int(b, 16);
rel = nir_i2f32(b, nir_ishr(b, nir_ishl(b, rel, shift), shift));
abs = nir_fmul_imm(b, abs, 1.0 / 256.0);
rel = nir_fmul_imm(b, rel, 1.0 / 256.0);
nir_def *res = nir_vec2(b, abs, rel);
if (!has_null_descriptors(nak)) {
nir_def *img_is_null =
build_img_is_null(b, tex_h, tex->can_speculate, nak);
res = nir_bcsel(b, img_is_null, nir_imm_int(b, 0), res);
}
nir_def_rewrite_uses_after(&tex->def, res);
}
return true;
}
static nir_def *
build_txq_samples_raw(nir_builder *b, nir_def *img_h, bool can_speculate,
const struct nak_compiler *nak)
{
nir_def *res = build_txq(b, nir_texop_tex_type_nv, img_h, NULL,
can_speculate, nak);
return nir_channel(b, res, 2);
}
static nir_def *
build_txq_samples(nir_builder *b, nir_def *img_h, bool can_speculate,
const struct nak_compiler *nak)
{
nir_def *res = build_txq_samples_raw(b, img_h, can_speculate, nak);
if (!has_null_descriptors(nak)) {
res = nir_bcsel(b, build_img_is_null(b, img_h, can_speculate, nak),
nir_imm_int(b, 0), res);
}
return res;
}
static nir_def *
build_txq_size(nir_builder *b, unsigned num_components,
nir_def *img_h, nir_def *lod, bool can_speculate,
const struct nak_compiler *nak)
{
if (lod == NULL)
lod = nir_imm_int(b, 0);
nir_def *res = build_txq(b, nir_texop_hdr_dim_nv, img_h, lod,
can_speculate, nak);
res = nir_trim_vector(b, res, num_components);
if (!has_null_descriptors(nak)) {
res = nir_bcsel(b, build_img_is_null(b, img_h, can_speculate, nak),
nir_imm_int(b, 0), res);
}
return res;
}
static bool
lower_txq(nir_builder *b, nir_tex_instr *tex, const struct nak_compiler *nak)
{
b->cursor = nir_before_instr(&tex->instr);
assert(!tex->is_sparse);
nir_def *tex_h = NULL, *lod = NULL;
for (unsigned i = 0; i < tex->num_srcs; i++) {
switch (tex->src[i].src_type) {
case nir_tex_src_texture_handle: tex_h = tex->src[i].src.ssa; break;
case nir_tex_src_sampler_handle: break; /* Ignored */
case nir_tex_src_lod: lod = tex->src[i].src.ssa; break;
default:
UNREACHABLE("Unsupported texture source");
}
}
nir_def *res;
switch (tex->op) {
case nir_texop_txs:
res = build_txq_size(b, tex->def.num_components, tex_h, lod,
tex->can_speculate, nak);
break;
case nir_texop_query_levels:
res = build_txq_levels(b, tex_h, tex->can_speculate, nak);
break;
case nir_texop_texture_samples:
res = build_txq_samples(b, tex_h, tex->can_speculate, nak);
break;
default:
UNREACHABLE("Invalid texture query op");
}
nir_def_replace(&tex->def, res);
return true;
}
static bool
shrink_image_load(nir_builder *b, nir_intrinsic_instr *intrin,
const struct nak_compiler *nak)
{
enum pipe_format format = nir_intrinsic_format(intrin);
nir_component_mask_t color_comps_read =
nir_def_components_read(&intrin->def);
assert(intrin->intrinsic == nir_intrinsic_bindless_image_load ||
intrin->intrinsic == nir_intrinsic_bindless_image_sparse_load);
/* Pick off the sparse resident component (if any) before we do anything
* else. This makes later logic easier.
*/
bool is_sparse = false;
if (intrin->intrinsic == nir_intrinsic_bindless_image_sparse_load) {
unsigned resident_comp = intrin->def.num_components - 1;
if (color_comps_read & BITFIELD_BIT(resident_comp)) {
is_sparse = true;
color_comps_read &= ~BITFIELD_BIT(resident_comp);
} else {
/* If the sparse bit is never used, get rid of it */
intrin->intrinsic = nir_intrinsic_bindless_image_load;
intrin->num_components--;
intrin->def.num_components--;
}
}
if (intrin->def.bit_size == 64) {
assert(format == PIPE_FORMAT_NONE ||
format == PIPE_FORMAT_R64_UINT ||
format == PIPE_FORMAT_R64_SINT);
b->cursor = nir_after_instr(&intrin->instr);
nir_def *data_xy, *data_w, *resident = NULL;
if (color_comps_read & BITFIELD_BIT(3)) {
/* Thanks to descriptor indexing, we need to ensure that null
* descriptor behavior works properly. In particular, normal zero
* reads will return (0, 0, 0, 1) whereas null descriptor reads need
* to return (0, 0, 0, 0). This means we can't blindly extend with
* an alpha component of 1. Instead, we need to trust the hardware
* to extend the original RG32 with z = 0 and w = 1 and copy the w
* value all the way out to 64-bit w value.
*/
assert(intrin->num_components == 4 + is_sparse);
assert(intrin->def.num_components == 4 + is_sparse);
intrin->def.bit_size = 32;
data_xy = nir_channels(b, &intrin->def, 0x3);
data_w = nir_channels(b, &intrin->def, 0x8);
if (is_sparse)
resident = nir_channel(b, &intrin->def, 4);
} else {
intrin->num_components = 2 + is_sparse;
intrin->def.num_components = 2 + is_sparse;
intrin->def.bit_size = 32;
data_xy = nir_channels(b, &intrin->def, 0x3);
data_w = nir_imm_int(b, 0);
if (is_sparse)
resident = nir_channel(b, &intrin->def, 2);
}
nir_def *data;
if (is_sparse) {
data = nir_vec5(b, nir_pack_64_2x32(b, data_xy),
nir_imm_zero(b, 1, 64),
nir_imm_zero(b, 1, 64),
nir_u2u64(b, data_w),
nir_u2u64(b, resident));
} else {
data = nir_vec4(b, nir_pack_64_2x32(b, data_xy),
nir_imm_zero(b, 1, 64),
nir_imm_zero(b, 1, 64),
nir_u2u64(b, data_w));
}
nir_def_rewrite_uses_after(&intrin->def, data);
return true;
}
if (format == PIPE_FORMAT_NONE)
return false;
/* In order for null descriptors to work properly, we don't want to shrink
* loads when the alpha channel is read even if we know the format has
* fewer channels.
*/
if (color_comps_read & BITFIELD_BIT(3))
return false;
const unsigned old_comps = intrin->def.num_components;
unsigned new_comps = util_format_get_nr_components(format);
new_comps = util_next_power_of_two(new_comps);
if (color_comps_read <= BITFIELD_MASK(2))
new_comps = 2;
if (color_comps_read <= BITFIELD_MASK(1))
new_comps = 1;
if (new_comps + is_sparse >= intrin->num_components)
return false;
b->cursor = nir_after_instr(&intrin->instr);
intrin->num_components = new_comps + is_sparse;
intrin->def.num_components = new_comps + is_sparse;
assert(new_comps <= 4);
nir_def *comps[5];
for (unsigned c = 0; c < new_comps; c++)
comps[c] = nir_channel(b, &intrin->def, c);
for (unsigned c = new_comps; c < 3; c++)
comps[c] = nir_imm_intN_t(b, 0, intrin->def.bit_size);
if (new_comps < 4)
comps[3] = nir_imm_intN_t(b, 1, intrin->def.bit_size);
/* The resident bit always goes in the last channel */
if (is_sparse)
comps[old_comps - 1] = nir_channel(b, &intrin->def, new_comps);
nir_def *data = nir_vec(b, comps, old_comps);
nir_def_rewrite_uses_after(&intrin->def, data);
return true;
}
static bool
shrink_image_store(nir_builder *b, nir_intrinsic_instr *intrin,
const struct nak_compiler *nak)
{
enum pipe_format format = nir_intrinsic_format(intrin);
nir_def *data = intrin->src[3].ssa;
if (data->bit_size == 64) {
assert(format == PIPE_FORMAT_NONE ||
format == PIPE_FORMAT_R64_UINT ||
format == PIPE_FORMAT_R64_SINT);
b->cursor = nir_before_instr(&intrin->instr);
/* For 64-bit image ops, we actually want a vec2 */
nir_def *data_vec2 = nir_unpack_64_2x32(b, nir_channel(b, data, 0));
nir_src_rewrite(&intrin->src[3], data_vec2);
intrin->num_components = 2;
return true;
}
if (format == PIPE_FORMAT_NONE)
return false;
unsigned new_comps = util_format_get_nr_components(format);
new_comps = util_next_power_of_two(new_comps);
if (new_comps >= intrin->num_components)
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *trimmed = nir_trim_vector(b, data, new_comps);
nir_src_rewrite(&intrin->src[3], trimmed);
intrin->num_components = new_comps;
return true;
}
static nir_def *
build_px_size_sa_log2(nir_builder *b, nir_def *samples)
{
nir_def *samples_log2 = nir_ufind_msb(b, samples);
/* Map from samples_log2 to pixels per sample (log2):
*
* 0 -> (0, 0)
* 1 -> (1, 0)
* 2 -> (1, 1)
* 3 -> (2, 1)
* 4 -> (2, 2)
*
* so
*
* h_log2 = samples_log2 / 2
* w_log2 = (samples_log2 + 1) / 2 = samples_log2 - h_log2
*/
nir_def *h_log2 = nir_udiv_imm(b, samples_log2, 2);
nir_def *w_log2 = nir_isub(b, samples_log2, h_log2);
return nir_vec2(b, w_log2, h_log2);
}
static bool
lower_msaa_image_access(nir_builder *b, nir_intrinsic_instr *intrin,
const struct nak_compiler *nak)
{
assert(nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS);
b->cursor = nir_before_instr(&intrin->instr);
bool can_speculate = nir_instr_can_speculate(&intrin->instr);
nir_def *img_h = intrin->src[0].ssa;
nir_def *x = nir_channel(b, intrin->src[1].ssa, 0);
nir_def *y = nir_channel(b, intrin->src[1].ssa, 1);
nir_def *z = nir_channel(b, intrin->src[1].ssa, 2);
nir_def *w = nir_channel(b, intrin->src[1].ssa, 3);
nir_def *s = intrin->src[2].ssa;
nir_def *samples = build_txq_samples_raw(b, img_h, can_speculate, nak);
nir_def *px_size_sa_log2 = build_px_size_sa_log2(b, samples);
nir_def *px_w_log2 = nir_channel(b, px_size_sa_log2, 0);
nir_def *px_h_log2 = nir_channel(b, px_size_sa_log2, 1);
/* Compute the x/y offsets
*
* txq.sampler_pos gives us the sample coordinates as a signed 4.12 fixed
* point with x in the bottom 16 bits and y in the top 16 bits.
*/
nir_def *spos_sf = build_txq(b, nir_texop_sample_pos_nv, img_h, s,
can_speculate, nak);
spos_sf = nir_trim_vector(b, spos_sf, 2);
/* Fortunately, the samples are laid out in the supersampled image the same
* as the sample locations, rounded to an integer sample offset. So we
* just have to figure out which samples each of those hits in the 2D grid.
*
* Add 0x0800 to convert from signed 4.12 fixed-point centered around 0 to
* unsigned 4.12 fixed point. Then shift by 12 - px_sz_log2 to divide off
* the extra, leaving an integer offset. It's safe to do it all in one add
* because we know a priori that the low 8 bits of each sample position are
* zero so any overflow in the low 16 bits will just set a 1 in bit 16
* which will get shifted away.
*/
nir_def *spos_uf = nir_iadd_imm(b, spos_sf, 0x08000800);
nir_def *sx = nir_ushr(b, nir_iand_imm(b, spos_uf, 0xffff),
nir_isub_imm(b, 12, px_w_log2));
nir_def *sy = nir_ushr(b, spos_uf, nir_isub_imm(b, 28, px_h_log2));
/* Add in the sample offsets */
x = nir_iadd(b, nir_ishl(b, x, px_w_log2), sx);
y = nir_iadd(b, nir_ishl(b, y, px_h_log2), sy);
/* Smash x negative if s > samples to get OOB behavior */
x = nir_bcsel(b, nir_ult(b, s, samples), x, nir_imm_int(b, -1));
nir_intrinsic_set_image_dim(intrin, GLSL_SAMPLER_DIM_2D);
nir_src_rewrite(&intrin->src[1], nir_vec4(b, x, y, z, w));
nir_src_rewrite(&intrin->src[2], nir_undef(b, 1, 32));
return true;
}
static bool
lower_image_txq(nir_builder *b, nir_intrinsic_instr *intrin,
const struct nak_compiler *nak)
{
b->cursor = nir_before_instr(&intrin->instr);
nir_def *img_h = intrin->src[0].ssa;
bool can_speculate = nir_instr_can_speculate(&intrin->instr);
nir_def *res;
switch (intrin->intrinsic) {
case nir_intrinsic_bindless_image_size:
res = build_txq_size(b, intrin->def.num_components, img_h,
intrin->src[1].ssa /* lod */, can_speculate, nak);
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS) {
/* When NIL sets up the MSAA image descriptor, it uses a width and
* height in samples, rather than pixels because sust/ld/atom ignore
* the sample count and blindly bounds check whatever x/y coordinates
* they're given. This means we need to divide back out the pixel
* size in order to get the size in pixels.
*/
nir_def *samples = build_txq_samples_raw(b, img_h, can_speculate, nak);
nir_def *px_size_sa_log2 = build_px_size_sa_log2(b, samples);
/* The first two coordinates are X/Y. If we have more than that,
* it’s the array index and that isn’t scaled.
*/
px_size_sa_log2 = nir_pad_vector_imm_int(b, px_size_sa_log2, 0,
intrin->def.num_components);
res = nir_ushr(b, res, px_size_sa_log2);
}
break;
case nir_intrinsic_bindless_image_samples:
res = build_txq_samples(b, img_h, can_speculate, nak);
break;
default:
UNREACHABLE("Invalid image query op");
}
nir_def_replace(&intrin->def, res);
return true;
}
static bool
lower_tex_instr(nir_builder *b, nir_instr *instr, void *_data)
{
const struct nak_compiler *nak = _data;
switch (instr->type) {
case nir_instr_type_tex: {
nir_tex_instr *tex = nir_instr_as_tex(instr);
switch (tex->op) {
case nir_texop_tex:
case nir_texop_txb:
case nir_texop_txl:
case nir_texop_txd:
case nir_texop_txf:
case nir_texop_txf_ms:
case nir_texop_tg4:
case nir_texop_lod:
return lower_tex(b, tex, nak);
case nir_texop_txs:
case nir_texop_query_levels:
case nir_texop_texture_samples:
return lower_txq(b, tex, nak);
default:
UNREACHABLE("Unsupported texture instruction");
}
}
case nir_instr_type_intrinsic: {
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
switch (intrin->intrinsic) {
case nir_intrinsic_bindless_image_load:
case nir_intrinsic_bindless_image_sparse_load: {
bool progress = false;
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS)
progress |= lower_msaa_image_access(b, intrin, nak);
progress |= shrink_image_load(b, intrin, nak);
return progress;
}
case nir_intrinsic_bindless_image_store: {
bool progress = false;
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS)
progress |= lower_msaa_image_access(b, intrin, nak);
progress |= shrink_image_store(b, intrin, nak);
return progress;
}
case nir_intrinsic_bindless_image_atomic:
case nir_intrinsic_bindless_image_atomic_swap:
if (nir_intrinsic_image_dim(intrin) == GLSL_SAMPLER_DIM_MS)
return lower_msaa_image_access(b, intrin, nak);
return false;
case nir_intrinsic_bindless_image_size:
case nir_intrinsic_bindless_image_samples:
return lower_image_txq(b, intrin, nak);
default:
return false;
}
}
default:
return false;
}
}
bool
nak_nir_lower_tex(nir_shader *nir, const struct nak_compiler *nak)
{
return nir_shader_instructions_pass(nir, lower_tex_instr,
nir_metadata_control_flow,
(void *)nak);
}
+289
View File
@@ -0,0 +1,289 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
static nir_def *
tess_ctrl_output_vtx(nir_builder *b, nir_def *vtx)
{
/* This is the pattern we see emitted by the blob driver:
*
* S2R R0, SR_LANEID
* S2R R6, SR_INVOCATION_ID
* # R3 is our vertex index
* SULD.P.2D.CTA.R.IGN R3, [R2], 0x1d, 0x0
* IMAD.IADD R5, R0, 0x1, -R6
* IMAD.SHL.U32 R0, R3, 0x4, RZ
* LEA.HI.SX32 R4, R0, R5, 0x1e
* ALD.O R4, a[0x88], R4
*
* Translating the MADs and re-naming registers, this is
*
* %r0 = iadd %lane -%invoc
* %r1 = imul %vtx 0x4
* %r2 = lea.hi.sx32 %r1 %r0 0x1e
* %out = ald.o a[%r2][0x88]
*
* But `lea.hi.sx32 %r1 %r0 0x1e` is just `(%r1 >> (32 - 0x1e)) + %r0`.
* Since %r1 is just `%vtx * 4` and 0x1e is 30, the whole bit on the left
* is `(%vtx * 4) >> 2 = %vtx`, assuming no overflow. So, this means
*
* %r0 = iadd %lane -%invoc
* %r2 = iadd %vtx %r0
* %out = ald.o a[%r2][0x88]
*
* In other words, the hardware actually indexes them by lane index with
* all of the invocations for a given TCS dispatch going in a sequential
* range of lanes. We have to compute the lane index of the requested
* invocation from the invocation index.
*/
nir_def *lane = nak_nir_load_sysval(b, NAK_SV_LANE_ID,
ACCESS_CAN_REORDER);
nir_def *invoc = nak_nir_load_sysval(b, NAK_SV_INVOCATION_ID,
ACCESS_CAN_REORDER);
return nir_iadd(b, lane, nir_iadd(b, vtx, nir_ineg(b, invoc)));
}
static bool
lower_vtg_io_intrin(nir_builder *b,
nir_intrinsic_instr *intrin,
void *cb_data)
{
const struct nak_compiler *nak = cb_data;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *vtx = NULL, *offset = NULL, *data = NULL;
switch (intrin->intrinsic) {
case nir_intrinsic_load_primitive_id:
case nir_intrinsic_load_instance_id:
case nir_intrinsic_load_vertex_id: {
/* These still input loads but they're a special case */
const gl_system_value sysval =
nir_system_value_from_intrinsic(intrin->intrinsic);
const uint32_t addr = nak_sysval_attr_addr(nak, sysval);
nir_def *val = nir_ald_nv(b, 1, nir_imm_int(b, 0), nir_imm_int(b, 0),
.base = addr, .flags = 0,
.range_base = addr, .range = 4,
.access = ACCESS_CAN_REORDER);
nir_def_rewrite_uses(&intrin->def, val);
return true;
}
case nir_intrinsic_load_input:
case nir_intrinsic_load_output:
offset = intrin->src[0].ssa;
break;
case nir_intrinsic_load_per_vertex_input:
vtx = intrin->src[0].ssa;
offset = intrin->src[1].ssa;
break;
case nir_intrinsic_load_per_vertex_output:
if (b->shader->info.stage == MESA_SHADER_TESS_CTRL)
vtx = tess_ctrl_output_vtx(b, intrin->src[0].ssa);
else
vtx = intrin->src[0].ssa;
offset = intrin->src[1].ssa;
break;
case nir_intrinsic_store_output:
data = intrin->src[0].ssa;
offset = intrin->src[1].ssa;
break;
case nir_intrinsic_store_per_vertex_output:
data = intrin->src[0].ssa;
vtx = intrin->src[1].ssa;
offset = intrin->src[2].ssa;
break;
default:
return false;
}
const bool offset_is_const = nir_src_is_const(nir_src_for_ssa(offset));
const bool is_store = data != NULL;
bool is_output;
switch (intrin->intrinsic) {
case nir_intrinsic_load_input:
case nir_intrinsic_load_per_vertex_input:
is_output = false;
break;
case nir_intrinsic_load_output:
case nir_intrinsic_load_per_vertex_output:
case nir_intrinsic_store_output:
case nir_intrinsic_store_per_vertex_output:
is_output = true;
break;
default:
UNREACHABLE("Unknown NIR I/O intrinsic");
}
bool is_patch;
switch (b->shader->info.stage) {
case MESA_SHADER_VERTEX:
case MESA_SHADER_GEOMETRY:
is_patch = false;
break;
case MESA_SHADER_TESS_CTRL:
is_patch = is_output && vtx == NULL;
break;
case MESA_SHADER_TESS_EVAL:
is_patch = !is_output && vtx == NULL;
break;
default:
UNREACHABLE("Unknown shader stage");
}
nir_component_mask_t mask;
if (is_store)
mask = nir_intrinsic_write_mask(intrin);
else
mask = nir_component_mask(intrin->num_components);
if (vtx != NULL && !is_output) {
if (nak->sm >= 50) {
nir_def *info = nak_nir_load_sysval(b, NAK_SV_INVOCATION_INFO,
ACCESS_CAN_REORDER);
nir_def *lo = nir_extract_u8_imm(b, info, 0);
nir_def *hi = nir_extract_u8_imm(b, info, 2);
nir_def *idx = nir_iadd(b, nir_imul(b, lo, hi), vtx);
const struct nak_nir_isbe_flags flags = {
.access = NAK_ISBE_ACCESS_MAP,
.output = false,
.skew = false,
.per_primitive = false,
};
vtx = nir_isberd_nv(b, 8, idx,
.flags = NAK_AS_U32(flags),
.access = ACCESS_CAN_REORDER);
} else {
vtx = nir_vild_nv(b, vtx);
}
}
if (vtx == NULL)
vtx = nir_imm_int(b, 0);
const nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
unsigned component = nir_intrinsic_component(intrin);
uint32_t base_addr;
if (b->shader->info.stage == MESA_SHADER_VERTEX && !is_output)
base_addr = nak_attribute_attr_addr(nak, sem.location);
else
base_addr = nak_varying_attr_addr(nak, sem.location);
base_addr += 4 * component;
uint32_t range;
if (offset_is_const) {
unsigned const_offset = nir_src_as_uint(nir_src_for_ssa(offset));
/* Tighten the range */
base_addr += const_offset * 16;
range = 4 * intrin->num_components;
if (const_offset != 0)
offset = nir_imm_int(b, 0);
} else {
/* Offsets from NIR are in vec4's */
offset = nir_imul_imm(b, offset, 16);
range = (sem.num_slots - 1) * 16 + intrin->num_components * 4;
}
const struct nak_nir_attr_io_flags flags = {
.output = is_output,
.patch = is_patch,
.phys = !offset_is_const && !is_patch,
};
nir_def *dst_comps[NIR_MAX_VEC_COMPONENTS];
while (mask) {
const unsigned c = ffs(mask) - 1;
unsigned comps = ffs(~(mask >> c)) - 1;
assert(comps > 0);
unsigned c_addr = base_addr + 4 * c;
/* vec2 has to be vec2 aligned, vec3/4 have to be vec4 aligned. We
* don't have actual alignment information on these intrinsics but we
* can assume that the indirect offset (if any) is a multiple of 16 so
* we don't need to worry about that and can just look at c_addr.
*/
comps = MIN2(comps, 4);
if (c_addr & 0xf)
comps = MIN2(comps, 2);
if (c_addr & 0x7)
comps = 1;
assert(!(c_addr & 0x3));
/* Vector load/store with non-constant offsets don't work pre-Maxwell.
* They encode fine and they don't throw any shader exceptions but the
* seem to get stuck in the hardware and we get context timeouts.
*/
if (nak->sm < 50 && !offset_is_const)
comps = 1;
nir_def *c_offset = offset;
if (flags.phys) {
/* Physical addressing has to be scalar */
comps = 1;
/* Use al2p to compute a physical address */
c_offset = nir_al2p_nv(b, offset, .base = c_addr,
.flags = NAK_AS_U32(flags));
c_addr = 0;
}
if (is_store) {
nir_def *c_data = nir_channels(b, data, BITFIELD_RANGE(c, comps));
nir_ast_nv(b, c_data, vtx, c_offset,
.base = c_addr,
.flags = NAK_AS_U32(flags),
.range_base = base_addr,
.range = range);
} else {
uint32_t access = flags.output ? 0 : ACCESS_CAN_REORDER;
nir_def *c_data = nir_ald_nv(b, comps, vtx, c_offset,
.base = c_addr,
.flags = NAK_AS_U32(flags),
.range_base = base_addr,
.range = range,
.access = access);
for (unsigned i = 0; i < comps; i++)
dst_comps[c + i] = nir_channel(b, c_data, i);
}
mask &= ~BITFIELD_RANGE(c, comps);
}
if (!is_store) {
nir_def *dst = nir_vec(b, dst_comps, intrin->num_components);
nir_def_rewrite_uses(&intrin->def, dst);
}
nir_instr_remove(&intrin->instr);
return true;
}
bool
nak_nir_lower_vtg_io(nir_shader *nir, const struct nak_compiler *nak)
{
return nir_shader_intrinsics_pass(nir, lower_vtg_io_intrin,
nir_metadata_control_flow,
(void *)nak);
}
@@ -0,0 +1,115 @@
/*
* Copyright 2024 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
/*
* Divergent loops can define convergent values that escape the loop. eg.
*
* div loop {
* con %72 = ...
* ...
* }
* ... = @ldcx_nv (%72, 0x30)
*
* When that happens, we would like to have the opportunity to move the value
* into a uniform register once we reach uniform control flow. This pass allows
* that by inserting nir_intrinsic_as_uniform after the loop-closed phi, which
* becomes r2ur in the backend.
*
* With nir_to_lcssa and this pass, our example becomes something like:
*
* div loop {
* con %72 = ...
* ...
* }
* con %73 = phi(%72, %72)
* con %74 = @as_uniform(%73)
* ... = @ldcx_nv (%74, 0x30)
*
* nir_to_lcssa(nir, false, false) and nir_divergence_analysis must be run
* before this pass.
*/
static bool
lower_block(nir_builder *b, nir_block *block)
{
bool progress = false;
b->cursor = nir_after_phis(block);
nir_foreach_phi_safe(phi, block) {
if (phi->def.divergent) {
continue;
}
nir_def* x = nir_as_uniform(b, &phi->def);
x->divergent = false;
nir_def_rewrite_uses_after(&phi->def, x);
progress = true;
}
return progress;
}
static bool
lower_cf_list(nir_builder *b, struct exec_list *cf_list)
{
bool progress = false;
foreach_list_typed_safe(nir_cf_node, node, node, cf_list) {
switch (node->type) {
case nir_cf_node_block:
break;
case nir_cf_node_if: {
nir_if *nif = nir_cf_node_as_if(node);
if (nir_src_is_divergent(&nif->condition)) {
nir_block *succ = nir_cf_node_as_block(nir_cf_node_next(node));
progress |= lower_block(b, succ);
} else {
progress |= lower_cf_list(b, &nif->then_list);
progress |= lower_cf_list(b, &nif->else_list);
}
break;
}
case nir_cf_node_loop: {
nir_loop *loop = nir_cf_node_as_loop(node);
if (nir_loop_is_divergent(loop)) {
nir_block *succ = nir_cf_node_as_block(nir_cf_node_next(node));
progress |= lower_block(b, succ);
} else {
progress |= lower_cf_list(b, &loop->body);
progress |= lower_cf_list(b, &loop->continue_list);
}
break;
}
default:
UNREACHABLE("Unknown CF node type");
}
}
return progress;
}
bool
nak_nir_mark_lcssa_invariants(nir_shader *shader)
{
bool progress = false;
nir_foreach_function_impl(impl, shader) {
nir_builder b = nir_builder_create(impl);
nir_metadata_require(impl, nir_metadata_divergence);
bool impl_progress = lower_cf_list(&b, &impl->body);
progress |= nir_progress(impl_progress, impl, nir_metadata_control_flow);
}
return progress;
}
@@ -0,0 +1,103 @@
/*
* Copyright © 2025 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "nak_private.h"
#include "nir_builder.h"
#include "util/hash_table.h"
struct remat_ctx {
struct hash_table *remap;
nir_block *block;
nir_builder b;
};
static bool
rematerialize_load_const(nir_src *src, void *_ctx)
{
struct remat_ctx *ctx = _ctx;
if (!nir_src_is_const(*src))
return true;
struct hash_entry *entry = _mesa_hash_table_search(ctx->remap, src->ssa);
if (entry != NULL) {
nir_src_rewrite(src, entry->data);
return true;
}
nir_load_const_instr *old_lc =
nir_def_as_load_const(src->ssa);
nir_load_const_instr *new_lc =
nir_instr_as_load_const(nir_instr_clone(ctx->b.shader, &old_lc->instr));
nir_builder_instr_insert(&ctx->b, &new_lc->instr);
_mesa_hash_table_insert(ctx->remap, &old_lc->def, &new_lc->def);
nir_src_rewrite(src, &new_lc->def);
return true;
}
static bool
rematerialize_load_const_impl(nir_function_impl *impl)
{
struct remat_ctx ctx = {
.remap = _mesa_pointer_hash_table_create(NULL),
.b = nir_builder_create(impl),
};
bool progress = false;
nir_foreach_block(block, impl) {
_mesa_hash_table_clear(ctx.remap, NULL);
ctx.block = block;
nir_foreach_instr(instr, block) {
if (instr->type == nir_instr_type_phi)
continue;
ctx.b.cursor = nir_before_instr(instr);
nir_foreach_src(instr, rematerialize_load_const, &ctx);
}
ctx.b.cursor = nir_after_block_before_jump(block);
for (unsigned i = 0; i < ARRAY_SIZE(block->successors); i++) {
nir_block *succ = block->successors[i];
if (succ == NULL)
continue;
nir_foreach_instr(instr, succ) {
if (instr->type != nir_instr_type_phi)
break;
nir_phi_instr *phi = nir_instr_as_phi(instr);
nir_foreach_phi_src(src, phi) {
if (src->pred != block)
continue;
rematerialize_load_const(&src->src, &ctx);
}
}
}
if (ctx.remap->entries > 0)
progress = true;
}
_mesa_hash_table_destroy(ctx.remap, NULL);
return nir_progress(progress, impl, nir_metadata_control_flow);
}
bool
nak_nir_rematerialize_load_const(nir_shader *nir)
{
bool progress = false;
nir_foreach_function_impl(impl, nir)
progress |= rematerialize_load_const_impl(impl);
return progress;
}
+461
View File
@@ -0,0 +1,461 @@
/*
* Copyright © 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef NAK_PRIVATE_H
#define NAK_PRIVATE_H
#include "nak.h"
#include "nir.h"
#include "nv_device_info.h"
#ifdef __cplusplus
extern "C" {
#endif
bool nak_should_print_nir(void);
bool nak_debug_no_ugpr(void);
struct nak_compiler {
uint8_t sm;
uint8_t warps_per_sm;
struct nir_shader_compiler_options nir_options;
};
enum ENUM_PACKED nak_attr {
/* System values A */
NAK_ATTR_TESS_LOD_LEFT = 0x000,
NAK_ATTR_TESS_LOD_RIGHT = 0x004,
NAK_ATTR_TESS_LOD_BOTTOM = 0x008,
NAK_ATTR_TESS_LOD_TOP = 0x00c,
NAK_ATTR_TESS_LOD = NAK_ATTR_TESS_LOD_LEFT,
NAK_ATTR_TESS_INTERRIOR_U = 0x010,
NAK_ATTR_TESS_INTERRIOR_V = 0x014,
NAK_ATTR_TESS_INTERRIOR = NAK_ATTR_TESS_INTERRIOR_U,
/* Patch attributes */
NAK_ATTR_PATCH_START = 0x020,
/* System values B? */
NAK_ATTR_VPRS_TABLE_INDEX = 0x5c,
/* System values B */
NAK_ATTR_PRIMITIVE_ID = 0x060,
NAK_ATTR_RT_ARRAY_INDEX = 0x064,
NAK_ATTR_VIEWPORT_INDEX = 0x068,
NAK_ATTR_POINT_SIZE = 0x06c,
NAK_ATTR_POSITION_X = 0x070,
NAK_ATTR_POSITION_Y = 0x074,
NAK_ATTR_POSITION_Z = 0x078,
NAK_ATTR_POSITION_W = 0x07c,
NAK_ATTR_POSITION = NAK_ATTR_POSITION_X,
/* Generic attributes */
NAK_ATTR_GENERIC_START = 0x080,
/* System values C */
NAK_ATTR_CLIP_CULL_DIST_0 = 0x2c0,
NAK_ATTR_CLIP_CULL_DIST_1 = 0x2c4,
NAK_ATTR_CLIP_CULL_DIST_2 = 0x2c8,
NAK_ATTR_CLIP_CULL_DIST_3 = 0x2cc,
NAK_ATTR_CLIP_CULL_DIST_4 = 0x2d0,
NAK_ATTR_CLIP_CULL_DIST_5 = 0x2d4,
NAK_ATTR_CLIP_CULL_DIST_6 = 0x2d8,
NAK_ATTR_CLIP_CULL_DIST_7 = 0x2dc,
NAK_ATTR_CLIP_CULL_DIST = NAK_ATTR_CLIP_CULL_DIST_0,
NAK_ATTR_POINT_SPRITE_S = 0x2e0,
NAK_ATTR_POINT_SPRITE_T = 0x2e4,
NAK_ATTR_POINT_SPRITE = NAK_ATTR_POINT_SPRITE_S,
NAK_ATTR_FOG_COORD = 0x2e8,
/* Reserved 0x2ec */
NAK_ATTR_TESS_COORD_X = 0x2f0,
NAK_ATTR_TESS_COORD_Y = 0x2f4,
NAK_ATTR_TESS_COORD = NAK_ATTR_TESS_COORD_X,
NAK_ATTR_INSTANCE_ID = 0x2f8,
NAK_ATTR_VERTEX_ID = 0x2fc,
/* System values D */
NAK_ATTR_VIEWPORT_MASK = 0x3a0,
NAK_ATTR_BARY_COORD_NO_PERSP_X = 0x3a8,
NAK_ATTR_BARY_COORD_NO_PERSP_Y = 0x3ac,
NAK_ATTR_BARY_COORD_NO_PERSP_Z = 0x3b0,
NAK_ATTR_BARY_COORD_NO_PERSP = NAK_ATTR_BARY_COORD_NO_PERSP_X,
NAK_ATTR_BARY_COORD_X = 0x3b4,
NAK_ATTR_BARY_COORD_Y = 0x3b8,
NAK_ATTR_BARY_COORD_Z = 0x3bc,
NAK_ATTR_BARY_COORD = NAK_ATTR_BARY_COORD_X,
NAK_ATTR_SPH_END = NAK_ATTR_BARY_COORD_Z + 4,
/* Not in SPH */
NAK_ATTR_FRONT_FACE = 0x3fc,
};
static inline uint16_t
nak_attribute_attr_addr(UNUSED const struct nak_compiler *nak,
gl_vert_attrib attrib)
{
assert(attrib >= VERT_ATTRIB_GENERIC0);
return NAK_ATTR_GENERIC_START + (attrib - VERT_ATTRIB_GENERIC0) * 0x10;
}
uint16_t nak_varying_attr_addr(const struct nak_compiler *nak,
gl_varying_slot slot);
uint16_t nak_varying_mesh_skew_attr_addr(const struct nak_compiler *nak,
gl_varying_slot slot);
uint16_t nak_sysval_attr_addr(const struct nak_compiler *nak,
gl_system_value sysval);
enum ENUM_PACKED nak_sv {
NAK_SV_LANE_ID = 0x00,
NAK_SV_VIRTCFG = 0x02,
NAK_SV_VIRTID = 0x03,
NAK_SV_PRIM_TYPE = 0x10,
NAK_SV_INVOCATION_ID = 0x11,
NAK_SV_THREAD_KILL = 0x13,
NAK_SV_INVOCATION_INFO = 0x1d,
NAK_SV_COMBINED_TID = 0x20,
NAK_SV_TID_X = 0x21,
NAK_SV_TID_Y = 0x22,
NAK_SV_TID_Z = 0x23,
NAK_SV_TID = NAK_SV_TID_X,
NAK_SV_CTAID_X = 0x25,
NAK_SV_CTAID_Y = 0x26,
NAK_SV_CTAID_Z = 0x27,
NAK_SV_CTAID = NAK_SV_CTAID_X,
NAK_SV_LANEMASK_EQ = 0x38,
NAK_SV_LANEMASK_LT = 0x39,
NAK_SV_LANEMASK_LE = 0x3a,
NAK_SV_LANEMASK_GT = 0x3b,
NAK_SV_LANEMASK_GE = 0x3c,
NAK_SV_CLOCK_LO = 0x50,
NAK_SV_CLOCK_HI = 0x51,
NAK_SV_CLOCK = NAK_SV_CLOCK_LO,
NAK_SV_VARIABLE_RATE = 0x84,
};
bool nak_nir_workgroup_has_one_subgroup(const nir_shader *nir);
nir_def *nak_nir_load_sysval(nir_builder *b, enum nak_sv idx,
enum gl_access_qualifier access);
struct nak_xfb_info
nak_xfb_from_nir(const struct nak_compiler *nak,
const struct nir_xfb_info *nir_xfb);
enum nak_nir_tex_ref_type {
/** Indicates that this is a bindless texture */
NAK_NIR_TEX_REF_TYPE_BINDLESS,
/** Indicates that this is a bound texture
*
* The binding index provided in texture_index.
*/
NAK_NIR_TEX_REF_TYPE_BOUND,
/** Indicates that this is a cbuf texture
*
* texture_index is (idx << 16) | offset.
*/
NAK_NIR_TEX_REF_TYPE_CBUF,
};
enum nak_nir_lod_mode {
NAK_NIR_LOD_MODE_AUTO = 0,
NAK_NIR_LOD_MODE_ZERO,
NAK_NIR_LOD_MODE_BIAS,
NAK_NIR_LOD_MODE_LOD,
NAK_NIR_LOD_MODE_CLAMP,
NAK_NIR_LOD_MODE_BIAS_CLAMP,
};
enum nak_nir_offset_mode {
NAK_NIR_OFFSET_MODE_NONE = 0,
NAK_NIR_OFFSET_MODE_AOFFI,
NAK_NIR_OFFSET_MODE_PER_PX,
};
PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct nak_nir_tex_flags {
enum nak_nir_tex_ref_type ref_type:2;
enum nak_nir_lod_mode lod_mode:3;
enum nak_nir_offset_mode offset_mode:2;
bool has_z_cmpr:1;
bool is_sparse:1;
bool nodep:1;
bool scalar:1;
uint32_t pad:21;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct nak_nir_tex_flags) == 4,
"nak_nir_tex_flags has no holes");
#define NAK_AS_U32(x) ({\
static_assert(sizeof(x) == 4, "x must be 4 bytes"); \
uint32_t _u; \
memcpy(&_u, &(x), 4); \
_u; \
})
bool nak_nir_lower_scan_reduce(nir_shader *shader, const struct nak_compiler *nak);
bool nak_nir_lower_tex(nir_shader *nir, const struct nak_compiler *nak);
bool nak_nir_lower_gs_intrinsics(nir_shader *shader);
bool nak_nir_lower_algebraic_late(nir_shader *nir, const struct nak_compiler *nak);
bool nak_nir_lower_kepler_shared_atomics(nir_shader *shader);
bool nak_nir_lower_mesh_stages_shared_atomics(nir_shader *nir);
struct nak_nir_attr_io_flags {
bool output : 1;
bool patch : 1;
bool phys : 1;
uint32_t pad:29;
};
enum nak_suclamp_mode {
NAK_SUCLAMP_MODE_STORED_DESCRIPTOR,
NAK_SUCLAMP_MODE_PITCH_LINEAR,
NAK_SUCLAMP_MODE_BLOCK_LINEAR,
};
enum nak_suclamp_round {
NAK_SUCLAMP_ROUND_R1,
NAK_SUCLAMP_ROUND_R2,
NAK_SUCLAMP_ROUND_R4,
NAK_SUCLAMP_ROUND_R8,
NAK_SUCLAMP_ROUND_R16,
};
struct nak_nir_suclamp_flags {
enum nak_suclamp_mode mode : 2;
enum nak_suclamp_round round : 3;
bool is_s32 : 1;
bool is_2d : 1;
uint32_t pad:25;
};
enum nak_su_ga_offset_mode {
NAK_SUGA_OFF_MODE_U32,
NAK_SUGA_OFF_MODE_S32,
NAK_SUGA_OFF_MODE_U8,
NAK_SUGA_OFF_MODE_S8,
};
enum nak_imad_src_type {// 3 bits
NAK_IMAD_TYPE_U32,
NAK_IMAD_TYPE_U24,
NAK_IMAD_TYPE_U16_LO,
NAK_IMAD_TYPE_U16_HI,
NAK_IMAD_TYPE_S32,
NAK_IMAD_TYPE_S24,
NAK_IMAD_TYPE_S16_LO,
NAK_IMAD_TYPE_S16_HI,
};
struct nak_nir_imadsp_flags {
enum nak_imad_src_type src0 : 3;
enum nak_imad_src_type src1 : 3;
enum nak_imad_src_type src2 : 3;
bool params_from_src1 : 1;
uint32_t pad:22;
};
bool nak_nir_lower_vtg_io(nir_shader *nir, const struct nak_compiler *nak);
enum nak_isbe_access {
NAK_ISBE_ACCESS_MAP,
NAK_ISBE_ACCESS_PATCH,
NAK_ISBE_ACCESS_PRIM,
NAK_ISBE_ACCESS_ATTR,
};
struct nak_nir_isbe_flags {
enum nak_isbe_access access : 2;
bool output : 1;
bool skew : 1;
bool per_primitive : 1;
uint32_t pad : 27;
};
struct lower_mesh_intrinsics_ctx {
const struct nak_compiler *nak;
uint32_t max_vertices_out;
uint32_t max_primitives_out;
bool has_task_shader;
BITSET_DECLARE(skew_vert_attr_used, NAK_ATTR_SPH_END);
BITSET_DECLARE(skew_prim_attr_used, NAK_ATTR_SPH_END);
};
#define NAK_MESH_SKEW_GROUP_COUNT 32
static uint32_t
nak_mesh_skew_attr_used_index(uint32_t base_addr)
{
assert(base_addr < NAK_ATTR_SPH_END);
return base_addr / 4;
}
static uint32_t
nak_mesh_skew_vert_group_size(const struct lower_mesh_intrinsics_ctx *ctx)
{
return BITSET_COUNT(ctx->skew_vert_attr_used) * 4 * NAK_MESH_SKEW_GROUP_COUNT;
}
static uint32_t
nak_mesh_skew_vert_total_size(const struct lower_mesh_intrinsics_ctx *ctx)
{
return nak_mesh_skew_vert_group_size(ctx) * DIV_ROUND_UP(ctx->max_vertices_out, NAK_MESH_SKEW_GROUP_COUNT);
}
static uint32_t
nak_mesh_skew_prim_group_size(const struct lower_mesh_intrinsics_ctx *ctx)
{
return BITSET_COUNT(ctx->skew_prim_attr_used) * 4 * NAK_MESH_SKEW_GROUP_COUNT;
}
static uint32_t
nak_mesh_skew_prim_total_size(const struct lower_mesh_intrinsics_ctx *ctx)
{
return nak_mesh_skew_prim_group_size(ctx) * DIV_ROUND_UP(ctx->max_primitives_out, NAK_MESH_SKEW_GROUP_COUNT);
}
static uint32_t
nak_mesh_skew_total_size(const struct lower_mesh_intrinsics_ctx *ctx)
{
return nak_mesh_skew_vert_total_size(ctx) + nak_mesh_skew_prim_total_size(ctx);
}
static uint32_t
nak_mesh_skew_offset(const struct lower_mesh_intrinsics_ctx *ctx,
gl_varying_slot slot,
uint32_t base_addr,
bool per_primitive)
{
const uint32_t bit_idx = nak_mesh_skew_attr_used_index(base_addr);
uint32_t bit_count;
if (per_primitive)
bit_count = BITSET_PREFIX_SUM(ctx->skew_prim_attr_used, bit_idx);
else
bit_count = BITSET_PREFIX_SUM(ctx->skew_vert_attr_used, bit_idx);
uint32_t size = bit_count * 4 * NAK_MESH_SKEW_GROUP_COUNT;
return size;
}
bool nak_nir_lower_mesh_emulated_attributes(nir_shader *nir);
bool nak_nir_lower_mesh_intrinsics(nir_shader *nir, struct lower_mesh_intrinsics_ctx *ctx);
bool nak_nir_lower_task_intrinsics(nir_shader *nir);
enum nak_interp_mode {
NAK_INTERP_MODE_PERSPECTIVE,
NAK_INTERP_MODE_SCREEN_LINEAR,
NAK_INTERP_MODE_CONSTANT,
};
enum nak_interp_freq {
NAK_INTERP_FREQ_PASS,
NAK_INTERP_FREQ_PASS_MUL_W,
NAK_INTERP_FREQ_CONSTANT,
NAK_INTERP_FREQ_STATE,
};
enum nak_interp_loc {
NAK_INTERP_LOC_DEFAULT,
NAK_INTERP_LOC_CENTROID,
NAK_INTERP_LOC_OFFSET,
};
struct nak_nir_ipa_flags {
enum nak_interp_mode interp_mode:2;
enum nak_interp_freq interp_freq:2;
enum nak_interp_loc interp_loc:2;
uint32_t pad:26;
};
enum nak_cmat_type {
NAK_CMAT_TYPE_M8N8K16_INT,
NAK_CMAT_TYPE_M16N8K16_INT,
NAK_CMAT_TYPE_M16N8K32_INT,
NAK_CMAT_TYPE_M16N8K8_FLOAT,
NAK_CMAT_TYPE_M16N8K16_FLOAT,
/* Software emulated cmat layouts
*
* Those aren't supported as a single native *MMA invocation on any hardware,
* so in order to support those we execute multiple *MMA instructions with a
* register layout defined by us.
*/
NAK_CMAT_TYPE_M16N16K32_INT_SW,
NAK_CMAT_TYPE_M16N16K16_FLOAT_SW,
};
struct nak_nir_cmat_mul_add_flags {
enum nak_cmat_type cmat_type:3;
enum glsl_base_type a_type:5;
enum glsl_base_type b_type:5;
bool sat:1;
uint32_t pad:18;
};
bool nak_nir_lower_fs_inputs(nir_shader *nir,
const struct nak_compiler *nak,
const struct nak_fs_key *fs_key);
enum nak_fs_out {
NAK_FS_OUT_COLOR0 = 0x00,
NAK_FS_OUT_COLOR1 = 0x10,
NAK_FS_OUT_COLOR2 = 0x20,
NAK_FS_OUT_COLOR3 = 0x30,
NAK_FS_OUT_COLOR4 = 0x40,
NAK_FS_OUT_COLOR5 = 0x50,
NAK_FS_OUT_COLOR6 = 0x60,
NAK_FS_OUT_COLOR7 = 0x70,
NAK_FS_OUT_SAMPLE_MASK = 0x80,
NAK_FS_OUT_DEPTH = 0x84,
};
#define NAK_FS_OUT_COLOR(n) (NAK_FS_OUT_COLOR0 + (n) * 16)
static inline const struct nak_constant_offset_info*
nak_const_offsets(const struct nak_compiler* nak, bool is_graphics)
{
if (nak->sm >= 75 && is_graphics) {
return &nak_const_offsets_turing_graphics;
} else {
return &nak_const_offsets_base;
}
}
bool nak_nir_rematerialize_load_const(nir_shader *nir);
bool nak_nir_mark_lcssa_invariants(nir_shader *nir);
bool nak_nir_lower_non_uniform_ldcx(nir_shader *nir, const struct nak_compiler *nak);
bool nak_nir_add_barriers(nir_shader *nir, const struct nak_compiler *nak);
bool nak_nir_lower_cf(nir_shader *nir);
bool nak_nir_lower_cmat(nir_shader *shader, const struct nak_compiler *nak);
/**
* Check if, for nak's purposes, a block is divergent
*
* Note that this differs from block->divergent because in nir's terms, the
* start block of a loop with divergent continues can be non-divergent but nak
* will always consider this divergent. This matters because nak does not allow
* writing uregs from these blocks.
*/
bool nak_block_is_divergent(const nir_block *block);
void nak_optimize_nir(nir_shader *nir, const struct nak_compiler *nak);
#ifdef __cplusplus
}
#endif
#endif /* NAK_PRIVATE */
+28
View File
@@ -0,0 +1,28 @@
### nouveau_noop backend
This implements the minimum of nouveau in order to make shader-db work.
The submit ioctl is stubbed out to not execute anything.
Export `MESA_LOADER_DRIVER_OVERRIDE=nouveau
LD_PRELOAD=$prefix/lib/libnouveau_noop_drm_shim.so`.
By default, GK110 is exposed. The chip can be selected with an environment
variable like `NOUVEAU_CHIPSET=f0`. Some chips of note with the appropriate
chipset ids:
| ID | Chip Name | Sample Marketing Name | Comment |
| --- | --------- | --------------------- | ------- |
| 30 | NV30 | GeForce FX 5500 | DX9 |
| 40 | NV40 | GeForce 6800 | DX9c, better control flow |
| 50 | G80 | GeForce 8800 GTS | SM 1.0, 128 regs |
| 84 | G84 | GeForce 8600 GS | SM 1.1 |
| a0 | GT200 | GeForce GTX 280 | SM 1.2 + fp64 |
| a3 | GT215 | GeForce GT 240 | DX10.1 ops |
| c0 | GF100 | GeForce GTX 480 | SM 2.0, 64 regs |
| e4 | GK104 | GeForce GTX 680 | SM 3.0, 64 regs |
| f0 | GK110 | GeForce GTX 780 | SM 3.5, 256 regs |
| 117 | GM107 | GeForce GTX 750 | SM 5.0 |
| 124 | GM204 | GeForce GTX 980 | SM 5.2 |
| 134 | GP104 | GeForce GTX 1080 | SM 6.0 |
| 140 | GV100 | TITAN V | SM 7.0 |
| 162 | TU102 | GeForce RTX 2080 | SM 7.5 |
+12
View File
@@ -0,0 +1,12 @@
# Copyright © 2021 Ilia Mirkin
# SPDX-License-Identifier: MIT
libnouveau_noop_drm_shim = shared_library(
'nouveau_noop_drm_shim',
'nouveau_noop.c',
include_directories: [inc_include, inc_src],
c_args: [ cc.get_supported_arguments('-Wno-gnu-variable-sized-type-not-at-end') ],
dependencies: dep_drm_shim,
gnu_symbol_visibility : 'hidden',
install : true,
)
+492
View File
@@ -0,0 +1,492 @@
/*
* Copyright © 2021 Ilia Mirkin
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include "drm-uapi/nouveau_drm.h"
#include "nouveau/nvif/ioctl.h"
#include "nouveau/nvif/cl0080.h"
#include "drm-shim/drm_shim.h"
#include "util/os_misc.h"
#include "util/u_math.h"
#include "../../gallium/drivers/nouveau/nv_object.xml.h"
bool drm_shim_driver_prefers_first_render_node = true;
struct nouveau_device {
uint64_t next_offset;
};
static struct nouveau_device nouveau = {
.next_offset = 0x1000,
};
struct nouveau_shim_bo {
struct shim_bo base;
uint64_t offset;
};
static struct nouveau_shim_bo *
nouveau_shim_bo(struct shim_bo *bo)
{
return (struct nouveau_shim_bo *)bo;
}
struct nouveau_device_info {
uint32_t chip_id;
};
static struct nouveau_device_info device_info;
static int
nouveau_ioctl_noop(int fd, unsigned long request, void *arg)
{
return 0;
}
static int
nouveau_ioctl_gem_new(int fd, unsigned long request, void *arg)
{
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
struct drm_nouveau_gem_new *create = arg;
struct nouveau_shim_bo *bo = calloc(1, sizeof(*bo));
drm_shim_bo_init(&bo->base, create->info.size);
assert(ULONG_MAX - nouveau.next_offset > create->info.size);
create->info.handle = drm_shim_bo_get_handle(shim_fd, &bo->base);
create->info.map_handle = drm_shim_bo_get_mmap_offset(shim_fd, &bo->base);
if (create->align != 0)
nouveau.next_offset = align64(nouveau.next_offset, create->align);
create->info.offset = nouveau.next_offset;
nouveau.next_offset += create->info.size;
bo->offset = create->info.offset;
drm_shim_bo_put(&bo->base);
return 0;
}
static int
nouveau_ioctl_gem_info(int fd, unsigned long request, void *arg)
{
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
struct drm_nouveau_gem_info *info = arg;
struct nouveau_shim_bo *bo =
nouveau_shim_bo(drm_shim_bo_lookup(shim_fd, info->handle));
info->map_handle = drm_shim_bo_get_mmap_offset(shim_fd, &bo->base);
info->offset = bo->offset;
info->size = bo->base.size;
drm_shim_bo_put(&bo->base);
return 0;
}
static int
nouveau_ioctl_gem_pushbuf(int fd, unsigned long request, void *arg)
{
struct drm_nouveau_gem_pushbuf *submit = arg;
submit->vram_available = 3ULL << 30;
submit->gart_available = 1ULL << 40;
return 0;
}
static int
nouveau_ioctl_channel_alloc(int fd, unsigned long request, void *arg)
{
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
struct drm_nouveau_channel_alloc *alloc = arg;
if (device_info.chip_id == 0x50 || device_info.chip_id >= 0x80)
alloc->pushbuf_domains = NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART;
else
alloc->pushbuf_domains = NOUVEAU_GEM_DOMAIN_GART;
/* NOTE: this will get leaked since we don't handle the channel
* free. However only one channel is created per screen, so impact should
* be limited. */
struct nouveau_shim_bo *notify = calloc(1, sizeof(*notify));
drm_shim_bo_init(&notify->base, 0x1000);
notify->offset = nouveau.next_offset;
nouveau.next_offset += 0x1000;
alloc->notifier_handle = drm_shim_bo_get_handle(shim_fd, &notify->base);
drm_shim_bo_put(&notify->base);
return 0;
}
static int
nouveau_ioctl_get_param(int fd, unsigned long request, void *arg)
{
struct drm_nouveau_getparam *gp = arg;
switch (gp->param) {
case NOUVEAU_GETPARAM_CHIPSET_ID:
gp->value = device_info.chip_id;
return 0;
case NOUVEAU_GETPARAM_PCI_VENDOR:
gp->value = 0x10de;
return 0;
case NOUVEAU_GETPARAM_PCI_DEVICE:
gp->value = 0x1004;
return 0;
case NOUVEAU_GETPARAM_BUS_TYPE:
gp->value = 2 /* NV_PCIE */;
return 0;
case NOUVEAU_GETPARAM_FB_SIZE:
gp->value = 3ULL << 30;
return 0;
case NOUVEAU_GETPARAM_AGP_SIZE:
gp->value = 1ULL << 40;
return 0;
case NOUVEAU_GETPARAM_PTIMER_TIME:
gp->value = 0;
return 0;
case NOUVEAU_GETPARAM_HAS_BO_USAGE:
gp->value = 1;
return 0;
case NOUVEAU_GETPARAM_GRAPH_UNITS:
gp->value = 0x01000101;
return 0;
case NOUVEAU_GETPARAM_EXEC_PUSH_MAX:
gp->value = 510;
return 0;
case NOUVEAU_GETPARAM_VRAM_BAR_SIZE:
gp->value = 1L << 34;
return 0;
case NOUVEAU_GETPARAM_VRAM_USED:
gp->value = 4096;
return 0;
case NOUVEAU_GETPARAM_HAS_VMA_TILEMODE:
gp->value = 1;
return 0;
default:
fprintf(stderr, "Unknown DRM_IOCTL_NOUVEAU_GETPARAM %llu\n",
(long long unsigned)gp->param);
return -1;
}
}
static int
nouveau_ioctl_nvif(int fd, unsigned long request, void *arg)
{
struct {
struct nvif_ioctl_v0 ioctl;
} *args = arg;
switch (args->ioctl.type) {
case NVIF_IOCTL_V0_MTHD: {
struct {
struct nvif_ioctl_v0 ioctl;
struct nvif_ioctl_mthd_v0 mthd;
} *mthd = (void *)args;
switch (mthd->mthd.method) {
case NV_DEVICE_V0_INFO: {
struct nv_device_info_v0 *info = (void *)&mthd->mthd.data;
info->chipset = device_info.chip_id;
info->platform = NV_DEVICE_INFO_V0_PCIE;
/* make something up */
info->ram_user = 3ULL << 30;
break;
}
default:
break;
}
break;
}
case NVIF_IOCTL_V0_SCLASS: {
struct {
struct nvif_ioctl_v0 ioctl;
struct nvif_ioctl_sclass_v0 sclass;
} *sclass = (void *)args;
if (sclass->sclass.count == 0) {
sclass->sclass.count = device_info.chip_id >= 0xe0 ? 4 : 3;
return 0;
}
int idx = 0;
/* m2mf */
switch (device_info.chip_id & ~0xf) {
case 0x170:
case 0x160:
case 0x140:
case 0x130:
case 0x120:
case 0x110:
case 0x100:
case 0xf0:
sclass->sclass.oclass[idx].oclass = NVF0_P2MF_CLASS;
break;
case 0xe0:
sclass->sclass.oclass[idx].oclass = NVE4_P2MF_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = NVC0_M2MF_CLASS;
break;
}
sclass->sclass.oclass[idx].minver = -1;
sclass->sclass.oclass[idx].maxver = -1;
idx++;
if (device_info.chip_id >= 0xe0) {
switch (device_info.chip_id & ~0xf) {
case 0x170:
sclass->sclass.oclass[idx].oclass = AMPERE_DMA_COPY_A;
break;
case 0x160:
sclass->sclass.oclass[idx].oclass = TURING_DMA_COPY_A;
break;
case 0x140:
sclass->sclass.oclass[idx].oclass = VOLTA_DMA_COPY_A;
break;
case 0x130:
sclass->sclass.oclass[idx].oclass = PASCAL_DMA_COPY_A;
break;
case 0x120:
case 0x110:
sclass->sclass.oclass[idx].oclass = MAXWELL_DMA_COPY_A;
break;
case 0x100:
case 0xf0:
case 0xe0:
sclass->sclass.oclass[idx].oclass = KEPLER_DMA_COPY_A;
break;
}
sclass->sclass.oclass[idx].minver = -1;
sclass->sclass.oclass[idx].maxver = -1;
idx++;
}
/* 2d */
if (device_info.chip_id >= 0x50) {
if (device_info.chip_id <= 0xa0)
sclass->sclass.oclass[idx].oclass = NV50_2D_CLASS;
else
sclass->sclass.oclass[idx].oclass = NVC0_2D_CLASS;
sclass->sclass.oclass[idx].minver = -1;
sclass->sclass.oclass[idx].maxver = -1;
idx++;
}
/* 3d */
switch (device_info.chip_id & ~0xf) {
case 0x170:
sclass->sclass.oclass[idx].oclass = GA102_3D_CLASS;
break;
case 0x160:
sclass->sclass.oclass[idx].oclass = TU102_3D_CLASS;
break;
case 0x140:
sclass->sclass.oclass[idx].oclass = GV100_3D_CLASS;
break;
case 0x130:
switch (device_info.chip_id) {
case 0x130:
case 0x13b:
sclass->sclass.oclass[idx].oclass = GP100_3D_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = GP102_3D_CLASS;
break;
}
break;
case 0x120:
sclass->sclass.oclass[idx].oclass = GM200_3D_CLASS;
break;
case 0x110:
sclass->sclass.oclass[idx].oclass = GM107_3D_CLASS;
break;
case 0x100:
case 0xf0:
sclass->sclass.oclass[idx].oclass = NVF0_3D_CLASS;
break;
case 0xe0:
switch (device_info.chip_id) {
case 0xea:
sclass->sclass.oclass[idx].oclass = NVEA_3D_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = NVE4_3D_CLASS;
break;
}
break;
case 0xd0:
sclass->sclass.oclass[idx].oclass = NVC8_3D_CLASS;
break;
default:
case 0xc0:
switch (device_info.chip_id) {
case 0xc8:
sclass->sclass.oclass[idx].oclass = NVC8_3D_CLASS;
break;
case 0xc1:
sclass->sclass.oclass[idx].oclass = NVC1_3D_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = NVC0_3D_CLASS;
break;
}
break;
}
sclass->sclass.oclass[idx].minver = -1;
sclass->sclass.oclass[idx].maxver = -1;
idx++;
switch (device_info.chip_id & ~0xf) {
case 0x170:
sclass->sclass.oclass[idx].oclass = GA102_COMPUTE_CLASS;
break;
case 0x160:
sclass->sclass.oclass[idx].oclass = TU102_COMPUTE_CLASS;
break;
case 0x140:
sclass->sclass.oclass[idx].oclass = GV100_COMPUTE_CLASS;
break;
case 0x130:
switch (device_info.chip_id) {
case 0x130:
case 0x13b:
sclass->sclass.oclass[idx].oclass = GP100_COMPUTE_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = GP104_COMPUTE_CLASS;
break;
}
break;
case 0x120:
sclass->sclass.oclass[idx].oclass = GM200_COMPUTE_CLASS;
break;
case 0x110:
sclass->sclass.oclass[idx].oclass = GM107_COMPUTE_CLASS;
break;
case 0x100:
case 0xf0:
sclass->sclass.oclass[idx].oclass = NVF0_COMPUTE_CLASS;
break;
case 0xe0:
sclass->sclass.oclass[idx].oclass = NVE4_COMPUTE_CLASS;
break;
default:
sclass->sclass.oclass[idx].oclass = NVC0_COMPUTE_CLASS;
break;
}
sclass->sclass.oclass[idx].minver = -1;
sclass->sclass.oclass[idx].maxver = -1;
break;
}
default:
break;
}
return 0;
}
static ioctl_fn_t driver_ioctls[] = {
[DRM_NOUVEAU_GETPARAM] = nouveau_ioctl_get_param,
[DRM_NOUVEAU_NVIF] = nouveau_ioctl_nvif,
[DRM_NOUVEAU_CHANNEL_ALLOC] = nouveau_ioctl_channel_alloc,
[DRM_NOUVEAU_CHANNEL_FREE] = nouveau_ioctl_noop,
[DRM_NOUVEAU_GROBJ_ALLOC] = nouveau_ioctl_noop,
[DRM_NOUVEAU_NOTIFIEROBJ_ALLOC] = nouveau_ioctl_noop,
[DRM_NOUVEAU_GPUOBJ_FREE] = nouveau_ioctl_noop,
[DRM_NOUVEAU_GEM_NEW] = nouveau_ioctl_gem_new,
[DRM_NOUVEAU_GEM_PUSHBUF] = nouveau_ioctl_gem_pushbuf,
[DRM_NOUVEAU_GEM_CPU_PREP] = nouveau_ioctl_noop,
[DRM_NOUVEAU_GEM_INFO] = nouveau_ioctl_gem_info,
[DRM_NOUVEAU_GEM_CPU_FINI] = nouveau_ioctl_gem_info,
[DRM_NOUVEAU_VM_INIT] = nouveau_ioctl_noop,
[DRM_NOUVEAU_VM_BIND] = nouveau_ioctl_noop,
[DRM_NOUVEAU_EXEC] = nouveau_ioctl_noop,
[DRM_NOUVEAU_GET_ZCULL_INFO] = nouveau_ioctl_noop,
};
static void
nouveau_driver_get_device_info(void)
{
const char *env = os_get_option("NOUVEAU_CHIPSET");
if (!env) {
device_info.chip_id = 0xf0;
return;
}
device_info.chip_id = strtol(env, NULL, 16);
}
void
drm_shim_driver_init(void)
{
shim_device.bus_type = DRM_BUS_PCI;
shim_device.driver_name = "nouveau";
shim_device.driver_ioctls = driver_ioctls;
shim_device.driver_ioctl_count = ARRAY_SIZE(driver_ioctls);
shim_device.version_major = 1;
shim_device.version_minor = 3;
shim_device.version_patchlevel = 1;
nouveau_driver_get_device_info();
/* Ask userspace to consider all fences completed. */
os_set_option("NOUVEAU_DISABLE_FENCES", "true", true);
/* nothing looks at the pci id, so fix it to a GTX 780 */
static const char uevent_content[] =
"DRIVER=nouveau\n"
"PCI_CLASS=30000\n"
"PCI_ID=10de:1004\n"
"PCI_SUBSYS_ID=1028:075B\n"
"PCI_SLOT_NAME=0000:01:00.0\n"
"MODALIAS=pci:v000010ded00005916sv00001028sd0000075Bbc03sc00i00\n";
drm_shim_override_file(uevent_content,
"/sys/dev/char/%d:%d/device/uevent",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x0\n",
"/sys/dev/char/%d:%d/device/revision",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x10de",
"/sys/dev/char/%d:%d/device/vendor",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x10de",
"/sys/devices/pci0000:00/0000:01:00.0/vendor");
drm_shim_override_file("0x1004",
"/sys/dev/char/%d:%d/device/device",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x1004",
"/sys/devices/pci0000:00/0000:01:00.0/device");
drm_shim_override_file("0x1234",
"/sys/dev/char/%d:%d/device/subsystem_vendor",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x1234",
"/sys/devices/pci0000:00/0000:01:00.0/subsystem_vendor");
drm_shim_override_file("0x1234",
"/sys/dev/char/%d:%d/device/subsystem_device",
DRM_MAJOR, render_node_minor);
drm_shim_override_file("0x1234",
"/sys/devices/pci0000:00/0000:01:00.0/subsystem_device");
}
+1
View File
@@ -0,0 +1 @@
inc_nouveau_drm = include_directories('.')
+45
View File
@@ -0,0 +1,45 @@
#ifndef __NVIF_CL0080_H__
#define __NVIF_CL0080_H__
struct nv_device_v0 {
__u8 version;
__u8 pad01[7];
__u64 device; /* device identifier, ~0 for client default */
};
#define NV_DEVICE_V0_INFO 0x00
#define NV_DEVICE_V0_TIME 0x01
struct nv_device_info_v0 {
__u8 version;
#define NV_DEVICE_INFO_V0_IGP 0x00
#define NV_DEVICE_INFO_V0_PCI 0x01
#define NV_DEVICE_INFO_V0_AGP 0x02
#define NV_DEVICE_INFO_V0_PCIE 0x03
#define NV_DEVICE_INFO_V0_SOC 0x04
__u8 platform;
__u16 chipset; /* from NV_PMC_BOOT_0 */
__u8 revision; /* from NV_PMC_BOOT_0 */
#define NV_DEVICE_INFO_V0_TNT 0x01
#define NV_DEVICE_INFO_V0_CELSIUS 0x02
#define NV_DEVICE_INFO_V0_KELVIN 0x03
#define NV_DEVICE_INFO_V0_RANKINE 0x04
#define NV_DEVICE_INFO_V0_CURIE 0x05
#define NV_DEVICE_INFO_V0_TESLA 0x06
#define NV_DEVICE_INFO_V0_FERMI 0x07
#define NV_DEVICE_INFO_V0_KEPLER 0x08
#define NV_DEVICE_INFO_V0_MAXWELL 0x09
__u8 family;
__u8 pad06[2];
__u64 ram_size;
__u64 ram_user;
char chip[16];
char name[64];
};
struct nv_device_time_v0 {
__u8 version;
__u8 pad01[7];
__u64 time;
};
#endif
+141
View File
@@ -0,0 +1,141 @@
#ifndef __NVIF_CLASS_H__
#define __NVIF_CLASS_H__
/* these class numbers are made up by us, and not nvidia-assigned */
#define NVIF_CLASS_CONTROL /* if0001.h */ -1
#define NVIF_CLASS_PERFMON /* if0002.h */ -2
#define NVIF_CLASS_PERFDOM /* if0003.h */ -3
#define NVIF_CLASS_SW_NV04 /* if0004.h */ -4
#define NVIF_CLASS_SW_NV10 /* if0005.h */ -5
#define NVIF_CLASS_SW_NV50 /* if0005.h */ -6
#define NVIF_CLASS_SW_GF100 /* if0005.h */ -7
/* the below match nvidia-assigned (either in hw, or sw) class numbers */
#define NV_DEVICE /* cl0080.h */ 0x00000080
#define NV_DMA_FROM_MEMORY /* cl0002.h */ 0x00000002
#define NV_DMA_TO_MEMORY /* cl0002.h */ 0x00000003
#define NV_DMA_IN_MEMORY /* cl0002.h */ 0x0000003d
#define FERMI_TWOD_A 0x0000902d
#define FERMI_MEMORY_TO_MEMORY_FORMAT_A 0x00009039
#define KEPLER_INLINE_TO_MEMORY_A 0x0000a040
#define KEPLER_INLINE_TO_MEMORY_B 0x0000a140
#define NV04_DISP /* cl0046.h */ 0x00000046
#define NV03_CHANNEL_DMA /* cl506b.h */ 0x0000006b
#define NV10_CHANNEL_DMA /* cl506b.h */ 0x0000006e
#define NV17_CHANNEL_DMA /* cl506b.h */ 0x0000176e
#define NV40_CHANNEL_DMA /* cl506b.h */ 0x0000406e
#define NV50_CHANNEL_DMA /* cl506e.h */ 0x0000506e
#define G82_CHANNEL_DMA /* cl826e.h */ 0x0000826e
#define NV50_CHANNEL_GPFIFO /* cl506f.h */ 0x0000506f
#define G82_CHANNEL_GPFIFO /* cl826f.h */ 0x0000826f
#define FERMI_CHANNEL_GPFIFO /* cl906f.h */ 0x0000906f
#define KEPLER_CHANNEL_GPFIFO_A /* cla06f.h */ 0x0000a06f
#define MAXWELL_CHANNEL_GPFIFO_A /* cla06f.h */ 0x0000b06f
#define NV50_DISP /* cl5070.h */ 0x00005070
#define G82_DISP /* cl5070.h */ 0x00008270
#define GT200_DISP /* cl5070.h */ 0x00008370
#define GT214_DISP /* cl5070.h */ 0x00008570
#define GT206_DISP /* cl5070.h */ 0x00008870
#define GF110_DISP /* cl5070.h */ 0x00009070
#define GK104_DISP /* cl5070.h */ 0x00009170
#define GK110_DISP /* cl5070.h */ 0x00009270
#define GM107_DISP /* cl5070.h */ 0x00009470
#define GM204_DISP /* cl5070.h */ 0x00009570
#define NV31_MPEG 0x00003174
#define G82_MPEG 0x00008274
#define NV74_VP2 0x00007476
#define NV50_DISP_CURSOR /* cl507a.h */ 0x0000507a
#define G82_DISP_CURSOR /* cl507a.h */ 0x0000827a
#define GT214_DISP_CURSOR /* cl507a.h */ 0x0000857a
#define GF110_DISP_CURSOR /* cl507a.h */ 0x0000907a
#define GK104_DISP_CURSOR /* cl507a.h */ 0x0000917a
#define NV50_DISP_OVERLAY /* cl507b.h */ 0x0000507b
#define G82_DISP_OVERLAY /* cl507b.h */ 0x0000827b
#define GT214_DISP_OVERLAY /* cl507b.h */ 0x0000857b
#define GF110_DISP_OVERLAY /* cl507b.h */ 0x0000907b
#define GK104_DISP_OVERLAY /* cl507b.h */ 0x0000917b
#define NV50_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000507c
#define G82_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000827c
#define GT200_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000837c
#define GT214_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000857c
#define GF110_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000907c
#define GK104_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000917c
#define GK110_DISP_BASE_CHANNEL_DMA /* cl507c.h */ 0x0000927c
#define NV50_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000507d
#define G82_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000827d
#define GT200_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000837d
#define GT214_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000857d
#define GT206_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000887d
#define GF110_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000907d
#define GK104_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000917d
#define GK110_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000927d
#define GM107_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000947d
#define GM204_DISP_CORE_CHANNEL_DMA /* cl507d.h */ 0x0000957d
#define NV50_DISP_OVERLAY_CHANNEL_DMA /* cl507e.h */ 0x0000507e
#define G82_DISP_OVERLAY_CHANNEL_DMA /* cl507e.h */ 0x0000827e
#define GT200_DISP_OVERLAY_CHANNEL_DMA /* cl507e.h */ 0x0000837e
#define GT214_DISP_OVERLAY_CHANNEL_DMA /* cl507e.h */ 0x0000857e
#define GF110_DISP_OVERLAY_CONTROL_DMA /* cl507e.h */ 0x0000907e
#define GK104_DISP_OVERLAY_CONTROL_DMA /* cl507e.h */ 0x0000917e
#define FERMI_A /* cl9097.h */ 0x00009097
#define FERMI_B /* cl9097.h */ 0x00009197
#define FERMI_C /* cl9097.h */ 0x00009297
#define KEPLER_A /* cl9097.h */ 0x0000a097
#define KEPLER_B /* cl9097.h */ 0x0000a197
#define KEPLER_C /* cl9097.h */ 0x0000a297
#define MAXWELL_A /* cl9097.h */ 0x0000b097
#define MAXWELL_B /* cl9097.h */ 0x0000b197
#define NV74_BSP 0x000074b0
#define GT212_MSVLD 0x000085b1
#define IGT21A_MSVLD 0x000086b1
#define G98_MSVLD 0x000088b1
#define GF100_MSVLD 0x000090b1
#define GK104_MSVLD 0x000095b1
#define GT212_MSPDEC 0x000085b2
#define G98_MSPDEC 0x000088b2
#define GF100_MSPDEC 0x000090b2
#define GK104_MSPDEC 0x000095b2
#define GT212_MSPPP 0x000085b3
#define G98_MSPPP 0x000088b3
#define GF100_MSPPP 0x000090b3
#define G98_SEC 0x000088b4
#define GT212_DMA 0x000085b5
#define FERMI_DMA 0x000090b5
#define KEPLER_DMA_COPY_A 0x0000a0b5
#define MAXWELL_DMA_COPY_A 0x0000b0b5
#define FERMI_DECOMPRESS 0x000090b8
#define FERMI_COMPUTE_A 0x000090c0
#define FERMI_COMPUTE_B 0x000091c0
#define KEPLER_COMPUTE_A 0x0000a0c0
#define KEPLER_COMPUTE_B 0x0000a1c0
#define MAXWELL_COMPUTE_A 0x0000b0c0
#define MAXWELL_COMPUTE_B 0x0000b1c0
#define NV74_CIPHER 0x000074c1
#endif
+132
View File
@@ -0,0 +1,132 @@
#ifndef __NVIF_IOCTL_H__
#define __NVIF_IOCTL_H__
#define NVIF_VERSION_LATEST 0x0000000000000000ULL
struct nvif_ioctl_v0 {
__u8 version;
#define NVIF_IOCTL_V0_NOP 0x00
#define NVIF_IOCTL_V0_SCLASS 0x01
#define NVIF_IOCTL_V0_NEW 0x02
#define NVIF_IOCTL_V0_DEL 0x03
#define NVIF_IOCTL_V0_MTHD 0x04
#define NVIF_IOCTL_V0_RD 0x05
#define NVIF_IOCTL_V0_WR 0x06
#define NVIF_IOCTL_V0_MAP 0x07
#define NVIF_IOCTL_V0_UNMAP 0x08
#define NVIF_IOCTL_V0_NTFY_NEW 0x09
#define NVIF_IOCTL_V0_NTFY_DEL 0x0a
#define NVIF_IOCTL_V0_NTFY_GET 0x0b
#define NVIF_IOCTL_V0_NTFY_PUT 0x0c
__u8 type;
__u8 pad02[4];
#define NVIF_IOCTL_V0_OWNER_NVIF 0x00
#define NVIF_IOCTL_V0_OWNER_ANY 0xff
__u8 owner;
#define NVIF_IOCTL_V0_ROUTE_NVIF 0x00
#define NVIF_IOCTL_V0_ROUTE_HIDDEN 0xff
__u8 route;
__u64 token;
__u64 object;
__u8 data[]; /* ioctl data (below) */
};
struct nvif_ioctl_nop_v0 {
__u64 version;
};
struct nvif_ioctl_sclass_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 count;
__u8 pad02[6];
struct nvif_ioctl_sclass_oclass_v0 {
__s32 oclass;
__s16 minver;
__s16 maxver;
} oclass[];
};
struct nvif_ioctl_new_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 pad01[6];
__u8 route;
__u64 token;
__u64 object;
__u32 handle;
__s32 oclass;
__u8 data[]; /* class data (class.h) */
};
struct nvif_ioctl_del {
};
struct nvif_ioctl_rd_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 size;
__u8 pad02[2];
__u32 data;
__u64 addr;
};
struct nvif_ioctl_wr_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 size;
__u8 pad02[2];
__u32 data;
__u64 addr;
};
struct nvif_ioctl_map_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 pad01[3];
__u32 length;
__u64 handle;
};
struct nvif_ioctl_unmap {
};
struct nvif_ioctl_ntfy_new_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 event;
__u8 index;
__u8 pad03[5];
__u8 data[]; /* event request data (event.h) */
};
struct nvif_ioctl_ntfy_del_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 index;
__u8 pad02[6];
};
struct nvif_ioctl_ntfy_get_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 index;
__u8 pad02[6];
};
struct nvif_ioctl_ntfy_put_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 index;
__u8 pad02[6];
};
struct nvif_ioctl_mthd_v0 {
/* nvif_ioctl ... */
__u8 version;
__u8 method;
__u8 pad02[6];
__u8 data[]; /* method data (class.h) */
};
#endif
Loaded 100 of 12511 files, more files were not shown because too many files have changed in this diff. Show more