mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-11-01 19:28:02 +03:00
init
This commit is contained in:
12511 files changed
+6156641
No files matched your search
@@ -0,0 +1,8 @@
|
||||
|
||||
BasedOnStyle: InheritParentConfig
|
||||
DisableFormat: false
|
||||
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
ColumnLimit: 80
|
||||
BreakStringLiterals: false
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
@@ -0,0 +1,19 @@
|
||||
# In current VKCTS 1.4.4.0
|
||||
dEQP-VK.reconvergence.terminate_invocation.bit_count,Fail
|
||||
|
||||
dEQP-VK.sparse_resources.buffer.misc.null_address_read,Fail
|
||||
dEQP-VK.sparse_resources.buffer.misc.null_address_read_descriptors,Fail
|
||||
dEQP-VK.sparse_resources.buffer.misc.null_address_read_local_inv_idx,Fail
|
||||
dEQP-VK.sparse_resources.buffer.misc.null_address_read_local_inv_idx_descriptors,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.read_write.sparse_residency_non_resident_strict,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.sparse_residency.non_resident_buffer_strict_copy_dest_alloc_none_1024,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.sparse_residency.non_resident_buffer_strict_copy_dest_alloc_none_16777216,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.sparse_residency.non_resident_buffer_strict_copy_dest_alloc_none_65536,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.sparse_residency.non_resident_buffer_strict_copy_dest_alloc_partial_16777216,Fail
|
||||
dEQP-VK.sparse_resources.buffer.ssbo.sparse_residency.non_resident_buffer_strict_copy_dest_alloc_partial_65536,Fail
|
||||
|
||||
# VKCTS 1.4.4.2 -> 1.4.5.3 uprev
|
||||
dEQP-VK.compute.shader_object_binary.basic.copy_ssbo_bounds,Fail
|
||||
dEQP-VK.compute.shader_object_spirv.basic.copy_ssbo_bounds,Fail
|
||||
dEQP-VK.shader_object.tessellation.hlsl.point_mode,Fail
|
||||
dEQP-VK.wsi.xcb.present_timing.basic.timing_queue,Fail
|
||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
||||
# https://gitlab.freedesktop.org/mesa/mesa/-/issues/15116
|
||||
dEQP-VK.wsi.xlib.maintenance1.present_modes.*
|
||||
dEQP-VK.wsi.xlib.maintenance1.present_fence.fifo.*
|
||||
@@ -0,0 +1,3 @@
|
||||
# CTS for subgroups with softfp64 is extremely slow and causes timeouts.
|
||||
KHR-Single-GL46.subgroups.*double.*
|
||||
KHR-Single-GL46.subgroups.*dvec[234]*
|
||||
@@ -0,0 +1,57 @@
|
||||
# full OpenGL ES test set
|
||||
[[deqp]]
|
||||
deqp = "/deqp-gles/external/openglcts/modules/glcts"
|
||||
caselists = [
|
||||
"/deqp-gles/mustpass/gles2-main.txt",
|
||||
"/deqp-gles/mustpass/gles3-main.txt",
|
||||
"/deqp-gles/mustpass/gles31-main.txt",
|
||||
"/deqp-gles/mustpass/gles2-khr-main.txt",
|
||||
"/deqp-gles/mustpass/gles3-khr-main.txt",
|
||||
"/deqp-gles/mustpass/gles31-khr-main.txt",
|
||||
]
|
||||
deqp_args = [
|
||||
"--deqp-surface-width=256", "--deqp-surface-height=256",
|
||||
"--deqp-surface-type=pbuffer", "--deqp-visibility=hidden",
|
||||
"--deqp-gl-config-name=rgba8888d24s8ms0",
|
||||
]
|
||||
renderer_check = "Apple M[12]"
|
||||
tests_per_group = 1000
|
||||
version_check = "GL ES 3.2.*git"
|
||||
|
||||
# Note that KHR-GL* test sets include all tests from the previous
|
||||
# version, so we only need to run one test list (unlike dEQP-GLES,
|
||||
# where the test sets are separate).
|
||||
[[deqp]]
|
||||
deqp = "/deqp-gl/external/openglcts/modules/glcts"
|
||||
caselists = [
|
||||
"/deqp-gl/mustpass/gl46-main.txt",
|
||||
]
|
||||
deqp_args = [
|
||||
"--deqp-surface-width=256", "--deqp-surface-height=256",
|
||||
"--deqp-surface-type=pbuffer", "--deqp-visibility=hidden",
|
||||
"--deqp-gl-config-name=rgba8888d24s8ms0",
|
||||
]
|
||||
renderer_check = "Apple M[12]"
|
||||
tests_per_group = 1000
|
||||
|
||||
# wayland
|
||||
[[deqp]]
|
||||
deqp = "/deqp-gles/modules/egl/deqp-egl-wayland"
|
||||
caselists = ["/deqp-gles/mustpass/egl-main.txt"]
|
||||
deqp_args = [
|
||||
"--deqp-surface-width=256", "--deqp-surface-height=256",
|
||||
"--deqp-surface-type=pbuffer", "--deqp-visibility=hidden",
|
||||
"--deqp-gl-config-name=rgba8888d24s8ms0",
|
||||
]
|
||||
prefix = "wayland-"
|
||||
|
||||
# x11
|
||||
[[deqp]]
|
||||
deqp = "/deqp-gles/modules/egl/deqp-egl-x11"
|
||||
caselists = ["/deqp-gles/mustpass/egl-main.txt"]
|
||||
deqp_args = [
|
||||
"--deqp-surface-width=256", "--deqp-surface-height=256",
|
||||
"--deqp-surface-type=pbuffer", "--deqp-visibility=hidden",
|
||||
"--deqp-gl-config-name=rgba8888d24s8ms0",
|
||||
]
|
||||
prefix = "x11-"
|
||||
@@ -0,0 +1,5 @@
|
||||
[[deqp]]
|
||||
deqp = "/deqp-vk/external/vulkancts/modules/vulkan/deqp-vk"
|
||||
caselists = ["/deqp-vk/mustpass/vk-main.txt"]
|
||||
tests_per_group = 10000
|
||||
renderer_check = "Apple M[12]"
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* Copyright 2020 Intel Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "asahi_clc.h"
|
||||
#include "asahi/compiler/agx_compile.h"
|
||||
#include "asahi/compiler/agx_nir.h"
|
||||
#include "compiler/glsl_types.h"
|
||||
#include "compiler/spirv/nir_spirv.h"
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_precompiled.h"
|
||||
#include "shader_enums.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "util/macros.h"
|
||||
#include <sys/mman.h>
|
||||
|
||||
const char *targets[] = {"g13g", "g13x"};
|
||||
|
||||
#define foreach_target(target) \
|
||||
for (const char **target = &targets[0]; \
|
||||
target < &targets[ARRAY_SIZE(targets)]; ++target)
|
||||
|
||||
static const struct spirv_to_nir_options spirv_options = {
|
||||
.environment = NIR_SPIRV_OPENCL,
|
||||
.shared_addr_format = nir_address_format_62bit_generic,
|
||||
.global_addr_format = nir_address_format_62bit_generic,
|
||||
.temp_addr_format = nir_address_format_62bit_generic,
|
||||
.constant_addr_format = nir_address_format_64bit_global,
|
||||
.create_library = true,
|
||||
.printf = true,
|
||||
};
|
||||
|
||||
/* Standard optimization loop */
|
||||
static void
|
||||
optimize(nir_shader *nir)
|
||||
{
|
||||
bool progress;
|
||||
do {
|
||||
progress = false;
|
||||
|
||||
NIR_PASS(progress, nir, nir_split_var_copies);
|
||||
NIR_PASS(progress, nir, nir_split_struct_vars, nir_var_function_temp);
|
||||
NIR_PASS(progress, nir, nir_lower_var_copies);
|
||||
NIR_PASS(progress, nir, nir_lower_vars_to_ssa);
|
||||
|
||||
NIR_PASS(progress, nir, nir_opt_copy_prop);
|
||||
NIR_PASS(progress, nir, nir_opt_remove_phis);
|
||||
NIR_PASS(progress, nir, nir_lower_all_phis_to_scalar);
|
||||
NIR_PASS(progress, nir, nir_opt_dce);
|
||||
NIR_PASS(progress, nir, nir_opt_dead_cf);
|
||||
NIR_PASS(progress, nir, nir_opt_cse);
|
||||
|
||||
nir_opt_peephole_select_options peephole_select_options = {
|
||||
.limit = 64,
|
||||
.expensive_alu_ok = true,
|
||||
};
|
||||
NIR_PASS(progress, nir, nir_opt_peephole_select,
|
||||
&peephole_select_options);
|
||||
NIR_PASS(progress, nir, nir_opt_phi_precision);
|
||||
NIR_PASS(progress, nir, nir_opt_algebraic);
|
||||
NIR_PASS(progress, nir, nir_opt_constant_folding);
|
||||
|
||||
NIR_PASS(progress, nir, nir_opt_deref);
|
||||
NIR_PASS(progress, nir, nir_opt_copy_prop_vars);
|
||||
NIR_PASS(progress, nir, nir_opt_undef);
|
||||
NIR_PASS(progress, nir, nir_lower_undef_to_zero);
|
||||
|
||||
NIR_PASS(progress, nir, nir_opt_shrink_vectors, true);
|
||||
NIR_PASS(progress, nir, nir_opt_loop_unroll);
|
||||
|
||||
} while (progress);
|
||||
}
|
||||
|
||||
static nir_shader *
|
||||
compile(void *memctx, const uint32_t *spirv, size_t spirv_size)
|
||||
{
|
||||
const nir_shader_compiler_options *nir_options = &agx_nir_options;
|
||||
|
||||
assert(spirv_size % 4 == 0);
|
||||
nir_shader *nir =
|
||||
spirv_to_nir(spirv, spirv_size / 4, NULL, MESA_SHADER_KERNEL, "library",
|
||||
&spirv_options, nir_options);
|
||||
nir_validate_shader(nir, "after spirv_to_nir");
|
||||
ralloc_steal(memctx, nir);
|
||||
|
||||
nir_fixup_is_exported(nir);
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_system_values);
|
||||
NIR_PASS(_, nir, nir_lower_calls_to_builtins);
|
||||
|
||||
nir_lower_compute_system_values_options cs = {.global_id_is_32bit = true};
|
||||
NIR_PASS(_, nir, nir_lower_compute_system_values, &cs);
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_printf,
|
||||
&(const struct nir_lower_printf_options){
|
||||
.hash_format_strings = true,
|
||||
});
|
||||
|
||||
/* We have to lower away local constant initializers right before we
|
||||
* inline functions. That way they get properly initialized at the top
|
||||
* of the function and not at the top of its caller.
|
||||
*/
|
||||
NIR_PASS(_, nir, nir_lower_variable_initializers, nir_var_function_temp);
|
||||
NIR_PASS(_, nir, nir_lower_returns);
|
||||
NIR_PASS(_, nir, nir_inline_functions);
|
||||
nir_remove_non_exported(nir);
|
||||
NIR_PASS(_, nir, nir_opt_copy_prop);
|
||||
NIR_PASS(_, nir, nir_opt_deref);
|
||||
|
||||
/* We can't deal with constant data, get rid of it */
|
||||
nir_lower_constant_to_temp(nir);
|
||||
|
||||
/* We can go ahead and lower the rest of the constant initializers. We do
|
||||
* this here so that nir_remove_dead_variables and split_per_member_structs
|
||||
* below see the corresponding stores.
|
||||
*/
|
||||
NIR_PASS(_, nir, nir_lower_variable_initializers, ~0);
|
||||
|
||||
/* LLVM loves take advantage of the fact that vec3s in OpenCL are 16B
|
||||
* aligned and so it can just read/write them as vec4s. This results in a
|
||||
* LOT of vec4->vec3 casts on loads and stores. One solution to this
|
||||
* problem is to get rid of all vec3 variables.
|
||||
*/
|
||||
NIR_PASS(_, nir, nir_lower_vec3_to_vec4,
|
||||
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
|
||||
nir_var_mem_global | nir_var_mem_constant);
|
||||
|
||||
/* We assign explicit types early so that the optimizer can take advantage
|
||||
* of that information and hopefully get rid of some of our memcpys.
|
||||
*/
|
||||
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
|
||||
nir_var_uniform | nir_var_shader_temp | nir_var_function_temp |
|
||||
nir_var_mem_shared | nir_var_mem_global,
|
||||
glsl_get_cl_type_size_align);
|
||||
|
||||
optimize(nir);
|
||||
|
||||
NIR_PASS(_, nir, nir_remove_dead_variables, nir_var_all, NULL);
|
||||
|
||||
/* Lower again, this time after dead-variables to get more compact variable
|
||||
* layouts.
|
||||
*/
|
||||
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
|
||||
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
|
||||
nir_var_mem_global | nir_var_mem_constant,
|
||||
glsl_get_cl_type_size_align);
|
||||
assert(nir->constant_data_size == 0);
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_memcpy);
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_mem_constant,
|
||||
nir_address_format_64bit_global);
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_uniform,
|
||||
nir_address_format_32bit_offset_as_64bit);
|
||||
|
||||
/* Note: we cannot lower explicit I/O here, because we need derefs in tact
|
||||
* for function calls into the library to work.
|
||||
*/
|
||||
|
||||
NIR_PASS(_, nir, nir_lower_convert_alu_types, NULL);
|
||||
NIR_PASS(_, nir, nir_opt_if, 0);
|
||||
NIR_PASS(_, nir, nir_opt_idiv_const, 16);
|
||||
|
||||
NIR_PASS(_, nir, agx_nir_lower_texture_early, false /* support_lod_bias */);
|
||||
NIR_PASS(_, nir, agx_nir_lower_texture);
|
||||
NIR_PASS(_, nir, agx_nir_lower_multisampled_image_store);
|
||||
|
||||
optimize(nir);
|
||||
|
||||
return nir;
|
||||
}
|
||||
|
||||
static void
|
||||
print_shader(FILE *fp, const char *name, const char *suffix, uint32_t variant,
|
||||
struct agx_shader_part *p)
|
||||
{
|
||||
struct agx_precompiled_kernel_info info = agx_compact_kernel_info(&p->info);
|
||||
size_t sz_B = sizeof(info) + p->info.binary_size;
|
||||
size_t sz_el = DIV_ROUND_UP(sz_B, 4);
|
||||
uint32_t *mem = calloc(sz_el, 4);
|
||||
|
||||
memcpy(mem, &info, sizeof(info));
|
||||
memcpy((uint8_t *)mem + sizeof(info), p->binary, p->info.binary_size);
|
||||
|
||||
nir_precomp_print_blob(fp, name, suffix, variant, mem, sz_B, true);
|
||||
free(mem);
|
||||
}
|
||||
|
||||
static bool
|
||||
gather_atomic_info(nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
bool *any = data;
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
case nir_intrinsic_global_atomic:
|
||||
case nir_intrinsic_global_atomic_agx:
|
||||
case nir_intrinsic_deref_atomic:
|
||||
case nir_intrinsic_global_atomic_swap:
|
||||
case nir_intrinsic_global_atomic_swap_agx:
|
||||
case nir_intrinsic_deref_atomic_swap:
|
||||
*any = true;
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* G13X variants are only compiled when atomics are used */
|
||||
static const char *
|
||||
remap_variant(nir_function *func, unsigned variant, const char *target)
|
||||
{
|
||||
/* pass_flags is only 32-bit */
|
||||
assert(variant < 32 && "maximum # of variants");
|
||||
|
||||
bool has_atomic = func->pass_flags & BITFIELD_BIT(variant);
|
||||
|
||||
if (!has_atomic && !strcmp(target, "g13x"))
|
||||
return "g13g";
|
||||
else
|
||||
return target;
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
load_kernel_input(nir_builder *b, unsigned num_components, unsigned bit_size,
|
||||
unsigned offset_B)
|
||||
{
|
||||
assert((offset_B & 1) == 0 && "half-aligned");
|
||||
return nir_load_preamble(b, num_components, bit_size, .base = offset_B / 2);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 4) {
|
||||
fprintf(stderr, "Usage: %s [input spir-v] [output header] [output C]\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *infile = argv[1];
|
||||
const char *outh_file = argv[2];
|
||||
const char *outc_file = argv[3];
|
||||
|
||||
void *mem_ctx = ralloc_context(NULL);
|
||||
|
||||
int fd = open(infile, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "Failed to open %s\n", infile);
|
||||
ralloc_free(mem_ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
off_t spirv_len = lseek(fd, 0, SEEK_END);
|
||||
const void *spirv_map = mmap(NULL, spirv_len, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
close(fd);
|
||||
if (spirv_map == MAP_FAILED) {
|
||||
fprintf(stderr, "Failed to mmap the file: errno=%d, %s\n", errno,
|
||||
strerror(errno));
|
||||
ralloc_free(mem_ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *fp_h = fopen(outh_file, "w");
|
||||
FILE *fp_c = fopen(outc_file, "w");
|
||||
glsl_type_singleton_init_or_ref();
|
||||
|
||||
nir_precomp_print_header(fp_c, fp_h, "The Asahi Linux Contributors",
|
||||
"libagx_shaders.h");
|
||||
|
||||
nir_shader *nir = compile(mem_ctx, spirv_map, spirv_len);
|
||||
|
||||
/* load_preamble works at 16-bit granularity */
|
||||
struct nir_precomp_opts opt = {.arg_align_B = 2};
|
||||
|
||||
nir_foreach_entrypoint(libfunc, nir) {
|
||||
libfunc->pass_flags = 0;
|
||||
struct nir_precomp_layout layout =
|
||||
nir_precomp_derive_layout(&opt, libfunc);
|
||||
unsigned nr_vars = nir_precomp_nr_variants(libfunc);
|
||||
|
||||
nir_precomp_print_layout_struct(fp_h, &opt, libfunc);
|
||||
|
||||
for (unsigned v = 0; v < nr_vars; ++v) {
|
||||
nir_shader *s = nir_precompiled_build_variant(
|
||||
libfunc, MESA_SHADER_COMPUTE, v, &agx_nir_options, &opt,
|
||||
load_kernel_input);
|
||||
|
||||
nir_link_shader_functions(s, nir);
|
||||
NIR_PASS(_, s, nir_inline_functions);
|
||||
nir_remove_non_entrypoints(s);
|
||||
NIR_PASS(_, s, nir_opt_deref);
|
||||
NIR_PASS(_, s, nir_lower_vars_to_ssa);
|
||||
NIR_PASS(_, s, nir_remove_dead_derefs);
|
||||
NIR_PASS(_, s, nir_remove_dead_variables,
|
||||
nir_var_function_temp | nir_var_shader_temp, NULL);
|
||||
NIR_PASS(_, s, nir_lower_vars_to_explicit_types,
|
||||
nir_var_shader_temp | nir_var_function_temp,
|
||||
glsl_get_cl_type_size_align);
|
||||
|
||||
NIR_PASS(_, s, nir_lower_vars_to_explicit_types, nir_var_mem_shared,
|
||||
glsl_get_cl_type_size_align);
|
||||
|
||||
NIR_PASS(_, s, nir_lower_explicit_io, nir_var_mem_shared,
|
||||
nir_address_format_62bit_generic);
|
||||
|
||||
agx_preprocess_nir(s);
|
||||
|
||||
NIR_PASS(_, s, nir_opt_deref);
|
||||
NIR_PASS(_, s, nir_lower_vars_to_ssa);
|
||||
NIR_PASS(_, s, nir_lower_explicit_io,
|
||||
nir_var_shader_temp | nir_var_function_temp |
|
||||
nir_var_mem_shared | nir_var_mem_global,
|
||||
nir_address_format_62bit_generic);
|
||||
|
||||
bool has_atomic = false;
|
||||
nir_shader_intrinsics_pass(s, gather_atomic_info, nir_metadata_all,
|
||||
&has_atomic);
|
||||
if (has_atomic) {
|
||||
libfunc->pass_flags |= BITFIELD_BIT(v);
|
||||
}
|
||||
|
||||
foreach_target(target)
|
||||
{
|
||||
/* Skip unused variants */
|
||||
if (strcmp(*target, remap_variant(libfunc, v, *target)))
|
||||
continue;
|
||||
|
||||
struct agx_shader_part compiled;
|
||||
bool is_helper = !strcmp(libfunc->name, "libagx_helper");
|
||||
struct agx_shader_key key = {
|
||||
.promote_constants = !is_helper,
|
||||
|
||||
/* Most of the internal programs don't use textures at all, but
|
||||
* the few that do are exclusively bindless, so we want to
|
||||
* promote their access.
|
||||
*/
|
||||
.promote_textures = true,
|
||||
|
||||
.reserved_preamble = layout.size_B / 2,
|
||||
.is_helper = is_helper,
|
||||
};
|
||||
|
||||
if (has_atomic) {
|
||||
key.dev.needs_g13x_coherency =
|
||||
u_tristate_make(!strcmp(*target, "g13x"));
|
||||
}
|
||||
|
||||
nir_shader *clone = nir_shader_clone(NULL, s);
|
||||
agx_compile_shader_nir(clone, &key, &compiled);
|
||||
print_shader(fp_c, libfunc->name, *target, v, &compiled);
|
||||
free(compiled.binary);
|
||||
ralloc_free(clone);
|
||||
|
||||
assert(compiled.info.scratch_size == 0 &&
|
||||
"internal shaders do not spill");
|
||||
|
||||
assert(compiled.info.preamble_scratch_size == 0 &&
|
||||
"internal shader preambles do not spill");
|
||||
}
|
||||
|
||||
ralloc_free(s);
|
||||
}
|
||||
}
|
||||
|
||||
nir_precomp_print_program_enum(fp_h, nir, "libagx");
|
||||
nir_precomp_print_dispatch_macros(fp_h, &opt, nir);
|
||||
|
||||
/* For each target, generate a table mapping programs to binaries */
|
||||
foreach_target(target)
|
||||
{
|
||||
nir_precomp_print_extern_binary_map(fp_h, "libagx", *target);
|
||||
nir_precomp_print_binary_map(fp_c, nir, "libagx", *target, remap_variant);
|
||||
}
|
||||
|
||||
glsl_type_singleton_decref();
|
||||
fclose(fp_c);
|
||||
fclose(fp_h);
|
||||
ralloc_free(mem_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2024 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/* The libagx printf/abort buffer addresses is fixed at compile-time for
|
||||
* simplicity.
|
||||
*/
|
||||
#define LIBAGX_PRINTF_BUFFER_ADDRESS (1ull << 36)
|
||||
#define LIBAGX_PRINTF_BUFFER_SIZE (16384)
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright 2017 Intel Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
if get_option('precomp-compiler') == 'system'
|
||||
prog_asahi_clc = find_program('asahi_clc', native : true)
|
||||
else
|
||||
prog_asahi_clc = executable(
|
||||
'asahi_clc',
|
||||
['asahi_clc.c'],
|
||||
link_with : [libasahi_compiler],
|
||||
include_directories : [inc_include, inc_src],
|
||||
c_args : [pre_args, no_override_init_args],
|
||||
link_args : [ld_args_build_id],
|
||||
dependencies : [idep_vtn, idep_nir, idep_mesautil],
|
||||
# If we can run host binaries directly, just build asahi_clc for the host.
|
||||
# Most commonly this happens when doing a cross compile from an x86_64 build
|
||||
# machine to an x86 host
|
||||
native : not meson.can_run_host_binaries(),
|
||||
install : get_option('install-precomp-compiler'),
|
||||
)
|
||||
endif
|
||||
@@ -0,0 +1,89 @@
|
||||
# Special registers
|
||||
|
||||
`r0l` is the hardware nesting counter.
|
||||
|
||||
`r1` is the hardware link register.
|
||||
|
||||
`r5` and `r6` are preloaded in vertex shaders to the vertex ID and instance ID.
|
||||
|
||||
# ABI
|
||||
|
||||
The following section describes the ABI used by non-monolithic programs.
|
||||
|
||||
## Vertex
|
||||
|
||||
Registers have the following layout at the beginning of the vertex shader
|
||||
(written by the vertex prolog):
|
||||
|
||||
* `r0-r3` and `r7` undefined. This avoids preloading into the nesting counter or
|
||||
having unaligned values. The prolog is free to use these registers as
|
||||
temporaries.
|
||||
* `r4` is the zero-based vertex ID if the vertex shader is running as a hardware
|
||||
compute shader, useful to avoid a redundant special register read in the main
|
||||
shader. Undefined in hardware vertex shaders.
|
||||
* `r5-r6` retain their usual meanings, even if the vertex shader is running as a
|
||||
hardware compute shader. This allows software index fetch code to run in the
|
||||
prolog without contaminating the main shader key.
|
||||
* `r8` onwards contains 128-bit uniform vectors for each attribute.
|
||||
Accommodates 30 attributes without spilling, exceeding the 16 attribute API
|
||||
minimum. For 32 attributes, we will need to use function calls or the stack.
|
||||
|
||||
One useful property is that the GPR usage of the combined program is equal to
|
||||
the GPR usage of the main shader. The prolog cannot write higher registers than
|
||||
read by the main shader.
|
||||
|
||||
Vertex prologs do not have any uniform registers allocated for preamble
|
||||
optimization or constant promotion, as this adds complexity without any
|
||||
legitimate use case.
|
||||
|
||||
For a vertex shader reading $n$ attributes, the following layout is used:
|
||||
|
||||
* The first $n$ 64-bit uniforms are the base addresses of each attribute.
|
||||
* The next $n$ 32-bit uniforms are the associated clamps (sizes). Presently
|
||||
robustness is always used.
|
||||
* The next 2x32-bit uniform is the base vertex and base instance. This must
|
||||
always be reserved because it is unknown at vertex shader compile-time whether
|
||||
any attribute will use instancing. Reserving also the base vertex allows us to
|
||||
push both conveniently with a single USC Uniform word.
|
||||
* The next 16-bit is the draw ID.
|
||||
* For a hardware compute shader, the next 48-bit is padding.
|
||||
* For a hardware compute shader, the next 64-bit uniform is a pointer to the
|
||||
input assembly buffer.
|
||||
|
||||
In total, the first $6n + 5$ 16-bit uniform slots are reserved for a hardware
|
||||
vertex shader, or $6n + 12$ for a hardware compute shader.
|
||||
|
||||
## Fragment
|
||||
|
||||
When sample shading is enabled in a non-monolithic fragment shader, the fragment
|
||||
shader has the following register inputs:
|
||||
|
||||
* `r0l = 0`. This is the hardware nesting counter.
|
||||
* `r1l` is the mask of samples currently being shaded. This usually equals to
|
||||
`1 << sample ID`, for "true" per-sample shading.
|
||||
|
||||
When sample shading is disabled, no register inputs are defined. The fragment
|
||||
prolog (if present) may clobber whatever registers it pleases.
|
||||
|
||||
Registers have the following layout at the end of the fragment shader (read by
|
||||
the fragment epilog):
|
||||
|
||||
* `r0l = 0` if sample shading is enabled. This is implicitly true.
|
||||
* `r1l` preserved if sample shading is enabled.
|
||||
* `r2` and `r3l` contain the emitted depth/stencil respectively, if
|
||||
depth and/or stencil are written by the fragment shader. Depth/stencil writes
|
||||
must be deferred to the epilog for correctness when the epilog can discard
|
||||
(i.e. when alpha-to-coverage is enabled).
|
||||
* `r3h` contains the logically emitted sample mask, if the fragment shader uses
|
||||
forced early tests. This predicates the epilog's stores.
|
||||
* The vec4 of 32-bit registers beginning at `r(4 * (i + 1))` contains the colour
|
||||
output for render target `i`. When dual source blending is enabled, there is
|
||||
only a single render target and the dual source colour is treated as the
|
||||
second render target (registers r8-r11).
|
||||
|
||||
Uniform registers have the following layout:
|
||||
|
||||
* u0_u1: 64-bit render target texture heap
|
||||
* u2...u5: Blend constant
|
||||
* u6_u7: Root descriptor, so we can fetch the 64-bit fragment invocation counter
|
||||
address and (OpenGL only) the 64-bit polygon stipple address
|
||||
@@ -0,0 +1,177 @@
|
||||
template = """/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef _AGX_BUILDER_
|
||||
#define _AGX_BUILDER_
|
||||
|
||||
#include "agx_compiler.h"
|
||||
#include "util/lut.h"
|
||||
|
||||
static inline agx_instr *
|
||||
agx_alloc_instr(agx_builder *b, enum agx_opcode op, uint8_t nr_dests, uint8_t nr_srcs)
|
||||
{
|
||||
size_t size = sizeof(agx_instr);
|
||||
size += sizeof(agx_index) * nr_dests;
|
||||
size += sizeof(agx_index) * nr_srcs;
|
||||
|
||||
agx_instr *I = (agx_instr *) rzalloc_size(b->shader, size);
|
||||
I->dest = (agx_index *) (I + 1);
|
||||
I->src = I->dest + nr_dests;
|
||||
|
||||
I->op = op;
|
||||
I->nr_dests = nr_dests;
|
||||
I->nr_srcs = nr_srcs;
|
||||
return I;
|
||||
}
|
||||
|
||||
% for opcode in opcodes:
|
||||
<%
|
||||
op = opcodes[opcode]
|
||||
dests = op.dests
|
||||
srcs = op.srcs
|
||||
imms = [x for x in op.imms if (x.name != 'scoreboard' or opcode == 'wait')]
|
||||
suffix = "_to" if dests > 0 else ""
|
||||
nr_dests = "nr_dests" if op.variable_dests else str(dests)
|
||||
nr_srcs = "nr_srcs" if op.variable_srcs else str(srcs)
|
||||
%>
|
||||
|
||||
static inline agx_instr *
|
||||
agx_${opcode}${suffix}(agx_builder *b
|
||||
|
||||
% if op.variable_dests:
|
||||
, unsigned nr_dests
|
||||
% endif
|
||||
|
||||
% for dest in range(dests):
|
||||
, agx_index dst${dest}
|
||||
% endfor
|
||||
|
||||
% if op.variable_srcs:
|
||||
, unsigned nr_srcs
|
||||
% endif
|
||||
|
||||
% for src in range(srcs):
|
||||
, agx_index src${src}
|
||||
% endfor
|
||||
|
||||
% for imm in imms:
|
||||
, ${imm.ctype} ${imm.name}
|
||||
% endfor
|
||||
|
||||
) {
|
||||
agx_instr *I = agx_alloc_instr(b, AGX_OPCODE_${opcode.upper()}, ${nr_dests}, ${nr_srcs});
|
||||
|
||||
% for dest in range(dests):
|
||||
I->dest[${dest}] = dst${dest};
|
||||
% endfor
|
||||
|
||||
% for src in range(srcs):
|
||||
I->src[${src}] = src${src};
|
||||
% endfor
|
||||
|
||||
% for imm in imms:
|
||||
I->${imm.name} = ${imm.name};
|
||||
% endfor
|
||||
|
||||
agx_builder_insert(&b->cursor, I);
|
||||
return I;
|
||||
}
|
||||
|
||||
% if dests == 1 and not op.variable_srcs and not op.variable_dests:
|
||||
static inline agx_index
|
||||
agx_${opcode}(agx_builder *b
|
||||
|
||||
% if srcs == 0:
|
||||
, unsigned size
|
||||
% endif
|
||||
|
||||
% for src in range(srcs):
|
||||
, agx_index src${src}
|
||||
% endfor
|
||||
|
||||
% for imm in imms:
|
||||
, ${imm.ctype} ${imm.name}
|
||||
% endfor
|
||||
|
||||
) {
|
||||
<%
|
||||
args = ["tmp"]
|
||||
args += ["src" + str(i) for i in range(srcs)]
|
||||
args += [imm.name for imm in imms]
|
||||
%>
|
||||
% if srcs == 0:
|
||||
agx_index tmp = agx_temp(b->shader, agx_size_for_bits(size));
|
||||
% else:
|
||||
agx_index tmp = agx_temp(b->shader, src0.size);
|
||||
% endif
|
||||
agx_${opcode}_to(b, ${", ".join(args)});
|
||||
return tmp;
|
||||
}
|
||||
% endif
|
||||
|
||||
% endfor
|
||||
|
||||
/* Convenience methods */
|
||||
#define BINOP_BITOP(name, expr) \
|
||||
static inline agx_instr * \
|
||||
agx_## name ##_to(agx_builder *b, agx_index dst0, agx_index src0, agx_index src1) \
|
||||
{ \
|
||||
return agx_bitop_to(b, dst0, src0, src1, UTIL_LUT2(expr)); \
|
||||
} \
|
||||
\
|
||||
static inline agx_index \
|
||||
agx_## name (agx_builder *b, agx_index src0, agx_index src1) \
|
||||
{ \
|
||||
agx_index tmp = agx_temp(b->shader, src0.size); \
|
||||
agx_##name##_to(b, tmp, src0, src1); \
|
||||
return tmp; \
|
||||
}
|
||||
|
||||
BINOP_BITOP(nor, ~(a | b))
|
||||
BINOP_BITOP(andn2, a & ~b)
|
||||
BINOP_BITOP(andn1, ~a & b)
|
||||
BINOP_BITOP(xor, a ^ b)
|
||||
BINOP_BITOP(nand, ~(a & b))
|
||||
BINOP_BITOP(and, a & b)
|
||||
BINOP_BITOP(xnor, ~a ^ b)
|
||||
BINOP_BITOP(orn2, a | ~b)
|
||||
BINOP_BITOP(orn1, ~a | b)
|
||||
BINOP_BITOP(or, a | b)
|
||||
|
||||
#undef BINOP_BITOP
|
||||
|
||||
static inline agx_instr *
|
||||
agx_fmov_to(agx_builder *b, agx_index dst0, agx_index src0)
|
||||
{
|
||||
return agx_fadd_to(b, dst0, src0, agx_negzero());
|
||||
}
|
||||
|
||||
static inline agx_instr *
|
||||
agx_push_exec(agx_builder *b, unsigned n)
|
||||
{
|
||||
return agx_if_fcmp(b, agx_zero(), agx_zero(), n, AGX_FCOND_EQ, false, NULL);
|
||||
}
|
||||
|
||||
static inline agx_instr *
|
||||
agx_ushr_to(agx_builder *b, agx_index dst, agx_index s0, agx_index s1)
|
||||
{
|
||||
return agx_bfeil_to(b, dst, agx_zero(), s0, s1, 0);
|
||||
}
|
||||
|
||||
static inline agx_index
|
||||
agx_ushr(agx_builder *b, agx_index s0, agx_index s1)
|
||||
{
|
||||
agx_index tmp = agx_temp(b->shader, s0.size);
|
||||
agx_ushr_to(b, tmp, s0, s1);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
#endif
|
||||
"""
|
||||
|
||||
from mako.template import Template
|
||||
from agx_opcodes import opcodes
|
||||
|
||||
print(Template(template).render(opcodes=opcodes))
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright 2018-2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "util/shader_stats.h"
|
||||
#include "util/u_dynarray.h"
|
||||
#include "util/u_tristate.h"
|
||||
#include "shader_enums.h"
|
||||
|
||||
struct agx_cf_binding {
|
||||
/* Base coefficient register */
|
||||
uint8_t cf_base;
|
||||
|
||||
/* Slot being bound */
|
||||
gl_varying_slot slot : 8;
|
||||
|
||||
/* First component bound.
|
||||
*
|
||||
* Must be 2 (Z) or 3 (W) if slot == VARYING_SLOT_POS.
|
||||
*/
|
||||
unsigned offset : 2;
|
||||
|
||||
/* Number of components bound */
|
||||
unsigned count : 3;
|
||||
|
||||
/* Is smooth shading enabled? If false, flat shading is used */
|
||||
bool smooth : 1;
|
||||
|
||||
/* Perspective correct interpolation */
|
||||
bool perspective : 1;
|
||||
|
||||
uint8_t pad;
|
||||
};
|
||||
|
||||
/* Conservative bound, * 4 due to offsets (TODO: maybe worth eliminating
|
||||
* coefficient register aliasing?)
|
||||
*/
|
||||
#define AGX_MAX_CF_BINDINGS (VARYING_SLOT_MAX * 4)
|
||||
|
||||
struct agx_varyings_fs {
|
||||
/* Number of coefficient registers used */
|
||||
unsigned nr_cf;
|
||||
|
||||
/* Number of coefficient register bindings */
|
||||
unsigned nr_bindings;
|
||||
|
||||
/* Whether gl_FragCoord.z is read */
|
||||
bool reads_z;
|
||||
|
||||
/* Coefficient register bindings */
|
||||
struct agx_cf_binding bindings[AGX_MAX_CF_BINDINGS];
|
||||
};
|
||||
|
||||
union agx_varyings {
|
||||
struct agx_varyings_fs fs;
|
||||
};
|
||||
|
||||
struct agx_interp_info {
|
||||
/* Bit masks indexed by I/O location of flat and linear varyings */
|
||||
uint64_t flat;
|
||||
uint64_t linear;
|
||||
};
|
||||
static_assert(sizeof(struct agx_interp_info) == 16, "packed");
|
||||
|
||||
struct agx_rodata {
|
||||
/* Offset in the binary */
|
||||
uint32_t offset;
|
||||
|
||||
/* Base uniform to map constants */
|
||||
uint16_t base_uniform;
|
||||
|
||||
/* Number of 16-bit constants to map contiguously there */
|
||||
uint16_t size_16;
|
||||
};
|
||||
|
||||
struct agx_shader_info {
|
||||
mesa_shader_stage stage;
|
||||
uint32_t binary_size;
|
||||
|
||||
union agx_varyings varyings;
|
||||
|
||||
/* Number of uniforms */
|
||||
uint16_t push_count;
|
||||
|
||||
/* Local memory allocation in bytes */
|
||||
uint16_t local_size;
|
||||
|
||||
/* Local imageblock allocation in bytes per thread */
|
||||
uint16_t imageblock_stride;
|
||||
|
||||
/* Scratch memory allocation in bytes for main/preamble respectively */
|
||||
unsigned scratch_size, preamble_scratch_size;
|
||||
|
||||
/* Size in bytes of the main sahder */
|
||||
unsigned main_size;
|
||||
|
||||
/* Does the shader have a preamble? If so, it is at offset preamble_offset.
|
||||
* The main shader is at offset main_offset. The preamble is executed first.
|
||||
*/
|
||||
bool has_preamble;
|
||||
unsigned preamble_offset, main_offset;
|
||||
|
||||
/* Does the shader read the tilebuffer? */
|
||||
bool reads_tib;
|
||||
|
||||
/* Does the shader require early fragment tests? */
|
||||
bool early_fragment_tests;
|
||||
|
||||
/* Does the shader potentially draw to a nonzero viewport? */
|
||||
bool nonzero_viewport;
|
||||
|
||||
/* Does the shader write layer and/or viewport index? Written together */
|
||||
bool writes_layer_viewport;
|
||||
|
||||
/* Does the shader control the sample mask? */
|
||||
bool writes_sample_mask;
|
||||
|
||||
/* Depth layout, never equal to NONE */
|
||||
enum gl_frag_depth_layout depth_layout;
|
||||
|
||||
/* Based only the compiled shader, should tag writes be disabled? This is set
|
||||
* based on what is outputted. Note if rasterizer discard is used, that needs
|
||||
* to disable tag writes regardless of this flag.
|
||||
*/
|
||||
bool tag_write_disable;
|
||||
|
||||
/* Shader is incompatible with triangle merging */
|
||||
bool disable_tri_merging;
|
||||
|
||||
/* Reads draw ID system value */
|
||||
bool uses_draw_id;
|
||||
|
||||
/* Reads base vertex/instance */
|
||||
bool uses_base_param;
|
||||
|
||||
/* Uses txf and hence needs a txf sampler mapped */
|
||||
bool uses_txf;
|
||||
|
||||
/* Potentially uses the sampler heap (conservative) */
|
||||
bool uses_sampler_heap;
|
||||
|
||||
/* Number of texture/sampler state registers pushed by the preamble. */
|
||||
uint8_t texture_state_count, sampler_state_count;
|
||||
|
||||
/* Number of 16-bit registers used by the main shader and preamble
|
||||
* respectively.
|
||||
*/
|
||||
uint16_t nr_gprs, nr_preamble_gprs;
|
||||
|
||||
/* Output mask set during driver lowering */
|
||||
uint64_t outputs;
|
||||
|
||||
/* Workgroup size */
|
||||
uint16_t workgroup_size[3];
|
||||
|
||||
/* There may be constants in the binary. The driver must map these to uniform
|
||||
* registers as specified hre.
|
||||
*/
|
||||
struct agx_rodata rodata;
|
||||
|
||||
struct agx2_stats stats;
|
||||
};
|
||||
|
||||
struct agx_precompiled_kernel_info {
|
||||
uint32_t preamble_offset, main_offset;
|
||||
uint32_t main_size, binary_size;
|
||||
struct agx_rodata rodata;
|
||||
uint16_t nr_gprs, nr_preamble_gprs;
|
||||
uint16_t push_count;
|
||||
uint16_t workgroup_size[3];
|
||||
uint16_t local_size;
|
||||
uint16_t imageblock_stride;
|
||||
bool uses_txf;
|
||||
};
|
||||
|
||||
static inline struct agx_precompiled_kernel_info
|
||||
agx_compact_kernel_info(struct agx_shader_info *info)
|
||||
{
|
||||
assert(info->has_preamble == (info->nr_preamble_gprs > 0));
|
||||
assert(info->texture_state_count <= 8 && "static maximum, no need to plumb");
|
||||
|
||||
return (struct agx_precompiled_kernel_info){
|
||||
.preamble_offset = info->preamble_offset,
|
||||
.main_offset = info->main_offset,
|
||||
.main_size = info->main_size,
|
||||
.binary_size = info->binary_size,
|
||||
.rodata = info->rodata,
|
||||
.nr_gprs = info->nr_gprs,
|
||||
.nr_preamble_gprs = info->nr_preamble_gprs,
|
||||
.push_count = info->push_count,
|
||||
.workgroup_size = {info->workgroup_size[0], info->workgroup_size[1],
|
||||
info->workgroup_size[2]},
|
||||
.local_size = info->local_size,
|
||||
.imageblock_stride = info->imageblock_stride,
|
||||
.uses_txf = info->uses_txf,
|
||||
};
|
||||
}
|
||||
|
||||
struct agx_shader_part {
|
||||
struct agx_shader_info info;
|
||||
void *binary;
|
||||
};
|
||||
|
||||
static inline bool
|
||||
agx_is_shader_empty(struct agx_shader_part *s)
|
||||
{
|
||||
/* Last instruction is a stop, so if there's one instruction, there is
|
||||
* nothing but a stop. The shader is thus empty.
|
||||
*/
|
||||
return (s->info.stats.instrs == 1);
|
||||
}
|
||||
|
||||
#define AGX_MAX_RTS (8)
|
||||
|
||||
enum agx_format {
|
||||
AGX_FORMAT_I8 = 0,
|
||||
AGX_FORMAT_I16 = 1,
|
||||
AGX_FORMAT_I32 = 2,
|
||||
AGX_FORMAT_F16 = 3,
|
||||
AGX_FORMAT_U8NORM = 4,
|
||||
AGX_FORMAT_S8NORM = 5,
|
||||
AGX_FORMAT_U16NORM = 6,
|
||||
AGX_FORMAT_S16NORM = 7,
|
||||
AGX_FORMAT_RGB10A2 = 8,
|
||||
AGX_FORMAT_SRGBA8 = 10,
|
||||
AGX_FORMAT_RG11B10F = 12,
|
||||
AGX_FORMAT_RGB9E5 = 13,
|
||||
|
||||
/* Keep last */
|
||||
AGX_NUM_FORMATS,
|
||||
};
|
||||
|
||||
struct agx_fs_shader_key {
|
||||
/* Normally, access to the tilebuffer must be guarded by appropriate fencing
|
||||
* instructions to ensure correct results in the presence of out-of-order
|
||||
* hardware optimizations. However, specially dispatched clear shaders are
|
||||
* not subject to these conditions and can omit the wait instructions.
|
||||
*
|
||||
* Must (only) be set for special clear shaders.
|
||||
*
|
||||
* Must not be used with sample mask writes (including discards) or
|
||||
* tilebuffer loads (including blending).
|
||||
*/
|
||||
bool ignore_tib_dependencies;
|
||||
|
||||
/* When dynamic sample shading is used, the fragment shader is wrapped in a
|
||||
* loop external to the API shader. This bit indicates that we are compiling
|
||||
* inside the sample loop, meaning the execution nesting counter is already
|
||||
* zero and must be preserved.
|
||||
*/
|
||||
bool inside_sample_loop;
|
||||
|
||||
/* Base coefficient register. 0 for API shaders but nonzero for FS prolog */
|
||||
uint8_t cf_base;
|
||||
};
|
||||
|
||||
struct agx_device_key {
|
||||
/* Does the target GPU need explicit cluster coherency for atomics?
|
||||
* Only used on G13X.
|
||||
*/
|
||||
enum u_tristate needs_g13x_coherency;
|
||||
|
||||
/* Is soft fault enabled? This is technically system-wide policy set by the
|
||||
* kernel, but that's functionally a hardware feature.
|
||||
*/
|
||||
bool soft_fault;
|
||||
};
|
||||
|
||||
struct agx_shader_key {
|
||||
/* Device info */
|
||||
struct agx_device_key dev;
|
||||
|
||||
/* Number of reserved preamble slots at the start */
|
||||
unsigned reserved_preamble;
|
||||
|
||||
/* Whether scratch memory is available in the given shader stage */
|
||||
bool has_scratch;
|
||||
|
||||
/* Whether we're compiling the helper program used for scratch allocation.
|
||||
* This has special register allocation requirements.
|
||||
*/
|
||||
bool is_helper;
|
||||
|
||||
/* Whether the driver supports uploading constants for this shader. If
|
||||
* false, constants will not be promoted to uniforms.
|
||||
*/
|
||||
bool promote_constants;
|
||||
|
||||
/* Similarly whether the driver supports promoting bindless
|
||||
* textures/samplers. Currently this works only if non-bindless
|
||||
* textures/samplers are not used, but none of our drivers mix bindless /
|
||||
* non-bindless usage.
|
||||
*/
|
||||
bool promote_textures;
|
||||
|
||||
/* Set if this is a non-monolithic shader that must be linked with additional
|
||||
* shader parts before the program can be used. This suppresses omission of
|
||||
* `stop` instructions, which the linker must insert instead.
|
||||
*/
|
||||
bool no_stop;
|
||||
|
||||
/* Set if this is a secondary shader part (prolog or epilog). This prevents
|
||||
* the compiler from allocating uniform registers. For example, this turns
|
||||
* off preambles.
|
||||
*/
|
||||
bool secondary;
|
||||
|
||||
union {
|
||||
struct agx_fs_shader_key fs;
|
||||
};
|
||||
};
|
||||
|
||||
uint64_t agx_gather_texcoords(nir_shader *nir);
|
||||
|
||||
void agx_preprocess_nir(nir_shader *nir);
|
||||
bool agx_nir_lower_discard_zs_emit(nir_shader *s);
|
||||
bool agx_nir_lower_sample_mask(nir_shader *s);
|
||||
bool agx_nir_lower_interpolation(nir_shader *s);
|
||||
|
||||
bool agx_nir_lower_cull_distance_vs(struct nir_shader *s);
|
||||
bool agx_nir_lower_cull_distance_fs(struct nir_shader *s,
|
||||
unsigned nr_distances);
|
||||
bool agx_mem_vectorize_cb(unsigned align_mul, unsigned align_offset,
|
||||
unsigned bit_size, unsigned num_components,
|
||||
int64_t hole_size, nir_intrinsic_instr *low,
|
||||
nir_intrinsic_instr *high, void *data);
|
||||
|
||||
void agx_compile_shader_nir(nir_shader *nir, struct agx_shader_key *key,
|
||||
struct agx_shader_part *out);
|
||||
|
||||
struct agx_occupancy {
|
||||
unsigned max_registers;
|
||||
unsigned max_threads;
|
||||
};
|
||||
|
||||
struct agx_occupancy agx_occupancy_for_register_count(unsigned halfregs);
|
||||
unsigned agx_max_registers_for_occupancy(unsigned occupancy);
|
||||
|
||||
static inline unsigned
|
||||
agx_round_registers(unsigned halfregs)
|
||||
{
|
||||
return agx_occupancy_for_register_count(halfregs).max_registers;
|
||||
}
|
||||
|
||||
static const nir_shader_compiler_options agx_nir_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,
|
||||
.lower_flrp16 = true,
|
||||
.lower_flrp32 = true,
|
||||
.lower_fpow = true,
|
||||
.lower_fmod = true,
|
||||
.lower_bitfield_extract8 = true,
|
||||
.lower_bitfield_extract16 = true,
|
||||
.lower_bitfield_insert = true,
|
||||
.lower_ifind_msb = true,
|
||||
.lower_find_lsb = true,
|
||||
.lower_uadd_carry = true,
|
||||
.lower_usub_borrow = true,
|
||||
.lower_fisnormal = true,
|
||||
.lower_scmp = true,
|
||||
.lower_isign = true,
|
||||
.lower_fsign = true,
|
||||
.lower_iabs = true,
|
||||
.lower_fminmax_signed_zero = true,
|
||||
.lower_fdph = true,
|
||||
.lower_ffract = 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_pack_64_2x32 = 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_extract_byte = true,
|
||||
.lower_insert_byte = true,
|
||||
.lower_insert_word = true,
|
||||
.has_cs_global_id = true,
|
||||
.lower_device_index_to_zero = true,
|
||||
.lower_hadd = true,
|
||||
.has_amul = true,
|
||||
.has_isub = true,
|
||||
.has_load_global_bounded = true,
|
||||
.support_16bit_alu = true,
|
||||
.max_unroll_iterations = 32,
|
||||
.lower_uniforms_to_ubo = true,
|
||||
.late_lower_int64 = true,
|
||||
.lower_int64_options =
|
||||
(nir_lower_int64_options) ~(nir_lower_iadd64 | nir_lower_imul_2x32_64),
|
||||
.lower_doubles_options = (nir_lower_doubles_options)(~0),
|
||||
.support_indirect_inputs = BITFIELD_BIT(MESA_SHADER_TESS_CTRL) |
|
||||
BITFIELD_BIT(MESA_SHADER_TESS_EVAL) |
|
||||
BITFIELD_BIT(MESA_SHADER_FRAGMENT),
|
||||
.support_indirect_outputs = (uint8_t)BITFIELD_MASK(MESA_SHADER_STAGES),
|
||||
.max_samples = 4,
|
||||
.lower_fquantize2f16 = true,
|
||||
.compact_arrays = true,
|
||||
.discard_is_demote = true,
|
||||
.scalarize_ddx = true,
|
||||
.io_options = nir_io_always_interpolate_convergent_fs_inputs,
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/**
|
||||
* SSA-based scalar dead code elimination.
|
||||
* This pass assumes that no loop header phis are dead.
|
||||
*/
|
||||
void
|
||||
agx_dce(agx_context *ctx, bool partial)
|
||||
{
|
||||
BITSET_WORD *seen = BITSET_CALLOC(ctx->alloc);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
if (block->loop_header) {
|
||||
agx_foreach_phi_in_block(block, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
BITSET_SET(seen, I->src[s].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_block_rev(ctx, block) {
|
||||
agx_foreach_instr_in_block_safe_rev(block, I) {
|
||||
if (block->loop_header && I->op == AGX_OPCODE_PHI)
|
||||
break;
|
||||
|
||||
bool needed = false;
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
/* Eliminate destinations that are never read, as RA needs to
|
||||
* handle them specially. Visible only for instructions that write
|
||||
* multiple destinations (splits) or that write a destination but
|
||||
* cannot be DCE'd (atomics).
|
||||
*/
|
||||
if (BITSET_TEST(seen, I->dest[d].value)) {
|
||||
needed = true;
|
||||
} else if (partial) {
|
||||
I->dest[d] = agx_null();
|
||||
}
|
||||
}
|
||||
|
||||
if (!needed && agx_opcodes_info[I->op].can_eliminate) {
|
||||
agx_remove_instruction(I);
|
||||
} else {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
BITSET_SET(seen, I->src[s].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(seen);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* Copyright 2020 Collabora Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/macros.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* clang-format off */
|
||||
enum agx_compiler_dbg {
|
||||
/* bit 0 unused */
|
||||
AGX_DBG_SHADERS = BITFIELD_BIT(1),
|
||||
AGX_DBG_SHADERDB = BITFIELD_BIT(2),
|
||||
AGX_DBG_VERBOSE = BITFIELD_BIT(3),
|
||||
AGX_DBG_INTERNAL = BITFIELD_BIT(4),
|
||||
AGX_DBG_NOVALIDATE = BITFIELD_BIT(5),
|
||||
AGX_DBG_NOOPT = BITFIELD_BIT(6),
|
||||
AGX_DBG_WAIT = BITFIELD_BIT(7),
|
||||
AGX_DBG_NOPREAMBLE = BITFIELD_BIT(8),
|
||||
AGX_DBG_DEMAND = BITFIELD_BIT(9),
|
||||
AGX_DBG_NOSCHED = BITFIELD_BIT(10),
|
||||
AGX_DBG_SPILL = BITFIELD_BIT(11),
|
||||
AGX_DBG_NOPROMOTE = BITFIELD_BIT(12),
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
uint64_t agx_get_compiler_debug(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern C */
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_debug.h"
|
||||
|
||||
#define AGX_MAX_PENDING (8)
|
||||
|
||||
/*
|
||||
* Returns whether an instruction is asynchronous and needs a scoreboard slot
|
||||
*/
|
||||
static bool
|
||||
instr_is_async(agx_instr *I)
|
||||
{
|
||||
return agx_opcodes_info[I->op].immediates & AGX_IMMEDIATE_SCOREBOARD;
|
||||
}
|
||||
|
||||
struct slot {
|
||||
/* Set of registers this slot is currently writing */
|
||||
BITSET_DECLARE(writes, AGX_NUM_REGS);
|
||||
|
||||
/* Number of pending messages on this slot. Must not exceed
|
||||
* AGX_MAX_PENDING for correct results.
|
||||
*/
|
||||
uint8_t nr_pending;
|
||||
};
|
||||
|
||||
/*
|
||||
* Insert waits within a block to stall after every async instruction. Useful
|
||||
* for debugging.
|
||||
*/
|
||||
static void
|
||||
agx_insert_waits_trivial(agx_context *ctx, agx_block *block)
|
||||
{
|
||||
agx_foreach_instr_in_block_safe(block, I) {
|
||||
if (instr_is_async(I)) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_after_instr(I));
|
||||
agx_wait(&b, I->scoreboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert waits within a block, assuming scoreboard slots have already been
|
||||
* assigned. This waits for everything at the end of the block, rather than
|
||||
* doing something more intelligent/global. This should be optimized.
|
||||
*
|
||||
* XXX: Do any instructions read their sources asynchronously?
|
||||
*/
|
||||
static void
|
||||
agx_insert_waits_local(agx_context *ctx, agx_block *block)
|
||||
{
|
||||
struct slot slots[2] = {0};
|
||||
|
||||
agx_foreach_instr_in_block_safe(block, I) {
|
||||
uint8_t wait_mask = 0;
|
||||
|
||||
/* Check for read-after-write */
|
||||
agx_foreach_src(I, s) {
|
||||
if (I->src[s].type != AGX_INDEX_REGISTER)
|
||||
continue;
|
||||
|
||||
unsigned nr_read = agx_index_size_16(I->src[s]);
|
||||
for (unsigned slot = 0; slot < ARRAY_SIZE(slots); ++slot) {
|
||||
if (BITSET_TEST_COUNT(slots[slot].writes, I->src[s].value, nr_read))
|
||||
wait_mask |= BITSET_BIT(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for write-after-write */
|
||||
agx_foreach_dest(I, d) {
|
||||
if (I->dest[d].type != AGX_INDEX_REGISTER)
|
||||
continue;
|
||||
|
||||
unsigned nr_writes = agx_index_size_16(I->dest[d]);
|
||||
for (unsigned slot = 0; slot < ARRAY_SIZE(slots); ++slot) {
|
||||
if (BITSET_TEST_COUNT(slots[slot].writes, I->dest[d].value,
|
||||
nr_writes))
|
||||
wait_mask |= BITSET_BIT(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for barriers */
|
||||
if (I->op == AGX_OPCODE_THREADGROUP_BARRIER ||
|
||||
I->op == AGX_OPCODE_MEMORY_BARRIER) {
|
||||
|
||||
for (unsigned slot = 0; slot < ARRAY_SIZE(slots); ++slot) {
|
||||
if (slots[slot].nr_pending)
|
||||
wait_mask |= BITSET_BIT(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to assign a free slot */
|
||||
if (instr_is_async(I)) {
|
||||
for (unsigned slot = 0; slot < ARRAY_SIZE(slots); ++slot) {
|
||||
if (slots[slot].nr_pending == 0) {
|
||||
I->scoreboard = slot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for slot overflow */
|
||||
if (instr_is_async(I) &&
|
||||
slots[I->scoreboard].nr_pending >= AGX_MAX_PENDING)
|
||||
wait_mask |= BITSET_BIT(I->scoreboard);
|
||||
|
||||
/* Insert the appropriate waits, clearing the slots */
|
||||
u_foreach_bit(slot, wait_mask) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
agx_wait(&b, slot);
|
||||
|
||||
BITSET_ZERO(slots[slot].writes);
|
||||
slots[slot].nr_pending = 0;
|
||||
}
|
||||
|
||||
/* Record access */
|
||||
if (instr_is_async(I)) {
|
||||
agx_foreach_dest(I, d) {
|
||||
if (agx_is_null(I->dest[d]))
|
||||
continue;
|
||||
|
||||
assert(I->dest[d].type == AGX_INDEX_REGISTER);
|
||||
BITSET_SET_COUNT(slots[I->scoreboard].writes, I->dest[d].value,
|
||||
agx_index_size_16(I->dest[d]));
|
||||
}
|
||||
|
||||
slots[I->scoreboard].nr_pending++;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are outstanding messages, wait for them. We don't do this for the
|
||||
* exit block, though, since nothing else will execute in the shader so
|
||||
* waiting is pointless.
|
||||
*/
|
||||
if (block != agx_exit_block(ctx)) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_after_block_logical(block));
|
||||
|
||||
for (unsigned slot = 0; slot < ARRAY_SIZE(slots); ++slot) {
|
||||
if (slots[slot].nr_pending)
|
||||
agx_wait(&b, slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign scoreboard slots to asynchronous instructions and insert waits for the
|
||||
* appropriate hazard tracking.
|
||||
*/
|
||||
void
|
||||
agx_insert_waits(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_block(ctx, block) {
|
||||
if (agx_compiler_debug & AGX_DBG_WAIT)
|
||||
agx_insert_waits_trivial(ctx, block);
|
||||
else
|
||||
agx_insert_waits_local(ctx, block);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
bool
|
||||
agx_allows_16bit_immediate(agx_instr *I)
|
||||
{
|
||||
return (I->op == AGX_OPCODE_DEVICE_LOAD) ||
|
||||
(I->op == AGX_OPCODE_DEVICE_STORE) ||
|
||||
(I->op == AGX_OPCODE_STACK_LOAD) ||
|
||||
(I->op == AGX_OPCODE_STACK_STORE) ||
|
||||
(I->op == AGX_OPCODE_UNIFORM_STORE) || (I->op == AGX_OPCODE_ATOMIC) ||
|
||||
(I->op == AGX_OPCODE_PHI) || (I->op == AGX_OPCODE_TEX_STATE_STORE) ||
|
||||
(I->op == AGX_OPCODE_SAMPLER_STATE_STORE);
|
||||
}
|
||||
|
||||
unsigned
|
||||
agx_negate_src_index(agx_instr *I)
|
||||
{
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_IMAD:
|
||||
return 2;
|
||||
case AGX_OPCODE_IADD:
|
||||
return 1;
|
||||
default:
|
||||
UNREACHABLE("not allowed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* Copyright 2019-2020 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/sparse_bitset.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Liveness analysis is a backwards-may dataflow analysis pass. Within a block,
|
||||
* we compute live_out from live_in. The intrablock pass is linear-time. It
|
||||
* returns whether progress was made. */
|
||||
|
||||
/* live_in[s] = GEN[s] + (live_out[s] - KILL[s]) */
|
||||
|
||||
void
|
||||
agx_liveness_ins_update(struct u_sparse_bitset *live, agx_instr *I)
|
||||
{
|
||||
agx_foreach_ssa_dest(I, d)
|
||||
u_sparse_bitset_clear(live, I->dest[d].value);
|
||||
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
/* If the source is not live after this instruction, but becomes live
|
||||
* at this instruction, this is the use that kills the source
|
||||
*/
|
||||
I->src[s].kill = !u_sparse_bitset_test(live, I->src[s].value);
|
||||
u_sparse_bitset_set(live, I->src[s].value);
|
||||
}
|
||||
}
|
||||
|
||||
/* Globally, liveness analysis uses a fixed-point algorithm based on a
|
||||
* worklist. We initialize a work list with the exit block. We iterate the work
|
||||
* list to compute live_in from live_out for each block on the work list,
|
||||
* adding the predecessors of the block to the work list if we made progress.
|
||||
*/
|
||||
|
||||
void
|
||||
agx_compute_liveness(agx_context *ctx)
|
||||
{
|
||||
u_worklist worklist;
|
||||
u_worklist_init(&worklist, ctx->num_blocks, NULL);
|
||||
|
||||
/* Free any previous liveness, and allocate */
|
||||
agx_foreach_block(ctx, block) {
|
||||
u_sparse_bitset_free(&block->live_in);
|
||||
u_sparse_bitset_free(&block->live_out);
|
||||
|
||||
u_sparse_bitset_init(&block->live_in, ctx->alloc, block);
|
||||
u_sparse_bitset_init(&block->live_out, ctx->alloc, block);
|
||||
|
||||
agx_worklist_push_head(&worklist, block);
|
||||
}
|
||||
|
||||
/* Iterate the work list */
|
||||
while (!u_worklist_is_empty(&worklist)) {
|
||||
/* Pop in reverse order since liveness is a backwards pass */
|
||||
agx_block *blk = agx_worklist_pop_head(&worklist);
|
||||
|
||||
/* Update its liveness information */
|
||||
u_sparse_bitset_dup(&blk->live_in, &blk->live_out);
|
||||
|
||||
agx_foreach_instr_in_block_rev(blk, I) {
|
||||
if (I->op != AGX_OPCODE_PHI)
|
||||
agx_liveness_ins_update(&blk->live_in, I);
|
||||
}
|
||||
|
||||
/* Propagate the live in of the successor (blk) to the live out of
|
||||
* predecessors.
|
||||
*
|
||||
* Phi nodes are logically on the control flow edge and act in parallel.
|
||||
* To handle when propagating, we kill writes from phis and make live the
|
||||
* corresponding sources.
|
||||
*/
|
||||
agx_foreach_predecessor(blk, pred) {
|
||||
struct u_sparse_bitset live;
|
||||
u_sparse_bitset_dup(&live, &blk->live_in);
|
||||
|
||||
/* Kill write */
|
||||
agx_foreach_phi_in_block(blk, phi) {
|
||||
assert(phi->dest[0].type == AGX_INDEX_NORMAL);
|
||||
u_sparse_bitset_clear(&live, phi->dest[0].value);
|
||||
}
|
||||
|
||||
/* Make live the corresponding source */
|
||||
agx_foreach_phi_in_block(blk, phi) {
|
||||
agx_index operand = phi->src[agx_predecessor_index(blk, *pred)];
|
||||
if (operand.type == AGX_INDEX_NORMAL) {
|
||||
u_sparse_bitset_set(&live, operand.value);
|
||||
phi->src[agx_predecessor_index(blk, *pred)].kill = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (u_sparse_bitset_merge(&(*pred)->live_out, &live))
|
||||
agx_worklist_push_tail(&worklist, *pred);
|
||||
}
|
||||
}
|
||||
|
||||
u_worklist_fini(&worklist);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/*
|
||||
* Lower 64-bit moves to 32-bit moves. Although there are not 64-bit moves in
|
||||
* the ISA, it is convenient to pretend there are for instruction selection.
|
||||
* They are lowered trivially after register allocation.
|
||||
*
|
||||
* General 64-bit lowering happens in nir_lower_int64.
|
||||
*/
|
||||
static bool
|
||||
lower(agx_builder *b, agx_instr *I)
|
||||
{
|
||||
if (I->op != AGX_OPCODE_MOV && I->op != AGX_OPCODE_MOV_IMM)
|
||||
return false;
|
||||
|
||||
if (I->dest[0].size != AGX_SIZE_64)
|
||||
return false;
|
||||
|
||||
agx_index dest = I->dest[0];
|
||||
dest.size = AGX_SIZE_32;
|
||||
|
||||
if (I->op == AGX_OPCODE_MOV) {
|
||||
assert(I->src[0].type == AGX_INDEX_REGISTER ||
|
||||
I->src[0].type == AGX_INDEX_UNIFORM);
|
||||
assert(I->src[0].size == AGX_SIZE_64);
|
||||
agx_index src = I->src[0];
|
||||
src.size = AGX_SIZE_32;
|
||||
|
||||
/* Low 32-bit */
|
||||
agx_mov_to(b, dest, src);
|
||||
|
||||
/* High 32-bits */
|
||||
dest.value += 2;
|
||||
src.value += 2;
|
||||
agx_mov_to(b, dest, src);
|
||||
} else {
|
||||
/* Low 32-bit */
|
||||
agx_mov_imm_to(b, dest, I->imm & BITFIELD_MASK(32));
|
||||
|
||||
/* High 32-bits */
|
||||
dest.value += 2;
|
||||
agx_mov_imm_to(b, dest, I->imm >> 32);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
agx_lower_64bit_postra(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_instr_global_safe(ctx, I) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
if (lower(&b, I))
|
||||
agx_remove_instruction(I);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
static bool
|
||||
is_shuffle(enum agx_opcode op)
|
||||
{
|
||||
switch (op) {
|
||||
case AGX_OPCODE_SHUFFLE:
|
||||
case AGX_OPCODE_SHUFFLE_UP:
|
||||
case AGX_OPCODE_SHUFFLE_DOWN:
|
||||
case AGX_OPCODE_SHUFFLE_XOR:
|
||||
case AGX_OPCODE_QUAD_SHUFFLE:
|
||||
case AGX_OPCODE_QUAD_SHUFFLE_UP:
|
||||
case AGX_OPCODE_QUAD_SHUFFLE_DOWN:
|
||||
case AGX_OPCODE_QUAD_SHUFFLE_XOR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AGX shuffle instructions read indices to shuffle with from the entire quad
|
||||
* and accumulate them. That means that an inactive thread anywhere in the quad
|
||||
* can make the whole shuffle undefined! To workaround, we reserve a scratch
|
||||
* register (r0h) which we keep zero throughout the program... except for when
|
||||
* actually shuffling, when we copy the shuffle index into r0h for the
|
||||
* operation. This ensures that inactive threads read 0 for their index and
|
||||
* hence do not contribute to the accumulated index.
|
||||
*/
|
||||
void
|
||||
agx_lower_divergent_shuffle(agx_context *ctx)
|
||||
{
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_function(ctx));
|
||||
agx_index scratch = agx_register(1, AGX_SIZE_16);
|
||||
|
||||
assert(ctx->any_quad_divergent_shuffle);
|
||||
agx_mov_imm_to(&b, scratch, 0);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
bool needs_zero = false;
|
||||
|
||||
agx_foreach_instr_in_block_safe(block, I) {
|
||||
if (is_shuffle(I->op) && I->src[1].type == AGX_INDEX_REGISTER) {
|
||||
assert(I->dest[0].value != scratch.value);
|
||||
assert(I->src[0].value != scratch.value);
|
||||
assert(I->src[1].value != scratch.value);
|
||||
|
||||
/* Use scratch register for our input, then zero it at the end of
|
||||
* the block so all inactive threads read zero.
|
||||
*/
|
||||
b.cursor = agx_before_instr(I);
|
||||
agx_mov_to(&b, scratch, I->src[1]);
|
||||
needs_zero = true;
|
||||
|
||||
I->src[1] = scratch;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_zero) {
|
||||
b.cursor = agx_after_block_logical(block);
|
||||
agx_mov_imm_to(&b, scratch, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2021 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
UNUSED static void
|
||||
print_copy(const struct agx_copy *cp)
|
||||
{
|
||||
printf("%sr%u = ", cp->dest_mem ? "m" : "", cp->dest);
|
||||
agx_print_index(cp->src, false, stdout);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
UNUSED static void
|
||||
print_copies(const struct agx_copy *copies, unsigned nr)
|
||||
{
|
||||
printf("[\n");
|
||||
|
||||
for (unsigned i = 0; i < nr; ++i) {
|
||||
printf(" ");
|
||||
print_copy(&copies[i]);
|
||||
}
|
||||
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Emits code for
|
||||
*
|
||||
* for (int i = 0; i < n; ++i)
|
||||
* registers[dests[i]] = registers[srcs[i]];
|
||||
*
|
||||
* ...with all copies happening in parallel.
|
||||
*
|
||||
* That is, emit machine instructions equivalent to a parallel copy. This is
|
||||
* used to lower not only parallel copies but also collects and splits, which
|
||||
* also have parallel copy semantics.
|
||||
*
|
||||
* We only handles register-register copies, not general agx_index sources. This
|
||||
* suffices for its internal use for register allocation.
|
||||
*/
|
||||
|
||||
static void
|
||||
do_copy(agx_builder *b, const struct agx_copy *copy)
|
||||
{
|
||||
agx_index dst = copy->dest_mem
|
||||
? agx_memory_register(copy->dest, copy->src.size)
|
||||
: agx_register(copy->dest, copy->src.size);
|
||||
|
||||
if (copy->dest_mem && copy->src.memory) {
|
||||
/* Memory-memory copies need to be lowered to memory-register and
|
||||
* register-memory, using a reserved scratch register.
|
||||
*/
|
||||
agx_index scratch_reg = agx_register(2, copy->src.size);
|
||||
agx_mov_to(b, scratch_reg, copy->src);
|
||||
agx_mov_to(b, dst, scratch_reg);
|
||||
} else if (copy->src.type == AGX_INDEX_IMMEDIATE) {
|
||||
agx_mov_imm_to(b, dst, copy->src.value);
|
||||
} else {
|
||||
agx_mov_to(b, dst, copy->src);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
do_swap(agx_builder *b, const struct agx_copy *copy)
|
||||
{
|
||||
assert(copy->src.type == AGX_INDEX_REGISTER && "only GPRs are swapped");
|
||||
|
||||
if (copy->dest == copy->src.value)
|
||||
return;
|
||||
|
||||
agx_index x = copy->dest_mem
|
||||
? agx_memory_register(copy->dest, copy->src.size)
|
||||
: agx_register(copy->dest, copy->src.size);
|
||||
agx_index y = copy->src;
|
||||
assert(x.memory == y.memory);
|
||||
|
||||
/* Memory-memory swaps lowered here, GPR swaps lowered later */
|
||||
if (x.memory) {
|
||||
agx_index temp1 = agx_register(4, copy->src.size);
|
||||
agx_index temp2 = agx_register(6, copy->src.size);
|
||||
|
||||
agx_mov_to(b, temp1, x);
|
||||
agx_mov_to(b, temp2, y);
|
||||
agx_mov_to(b, y, temp1);
|
||||
agx_mov_to(b, x, temp2);
|
||||
} else {
|
||||
agx_swap(b, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
struct copy_ctx {
|
||||
/* Number of copies being processed */
|
||||
unsigned entry_count;
|
||||
|
||||
/* For each physreg, the number of pending copy entries that use it as a
|
||||
* source. Once this drops to zero, then the physreg is unblocked and can
|
||||
* be moved to.
|
||||
*/
|
||||
unsigned physreg_use_count[AGX_NUM_MODELED_REGS];
|
||||
|
||||
/* For each physreg, the pending copy_entry that uses it as a dest. */
|
||||
struct agx_copy *physreg_dest[AGX_NUM_MODELED_REGS];
|
||||
|
||||
struct agx_copy entries[AGX_NUM_MODELED_REGS];
|
||||
};
|
||||
|
||||
static bool
|
||||
entry_blocked(struct agx_copy *entry, struct copy_ctx *ctx)
|
||||
{
|
||||
for (unsigned i = 0; i < agx_size_align_16(entry->src.size); i++) {
|
||||
if (ctx->physreg_use_count[entry->dest + i] != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_real(struct agx_copy *entry)
|
||||
{
|
||||
return entry->src.type == AGX_INDEX_REGISTER &&
|
||||
entry->dest_mem == entry->src.memory;
|
||||
}
|
||||
|
||||
/* TODO: Generalize to other bit sizes */
|
||||
static void
|
||||
split_32bit_copy(struct copy_ctx *ctx, struct agx_copy *entry)
|
||||
{
|
||||
assert(!entry->done);
|
||||
assert(is_real(entry));
|
||||
assert(agx_size_align_16(entry->src.size) == 2);
|
||||
struct agx_copy *new_entry = &ctx->entries[ctx->entry_count++];
|
||||
|
||||
new_entry->dest = entry->dest + 1;
|
||||
new_entry->dest_mem = entry->dest_mem;
|
||||
new_entry->src = entry->src;
|
||||
new_entry->src.value += 1;
|
||||
new_entry->done = false;
|
||||
entry->src.size = AGX_SIZE_16;
|
||||
new_entry->src.size = AGX_SIZE_16;
|
||||
ctx->physreg_dest[entry->dest + 1] = new_entry;
|
||||
}
|
||||
|
||||
static void
|
||||
agx_emit_parallel_copies_for_class(agx_builder *b, struct agx_copy *copies,
|
||||
unsigned num_copies, bool cls)
|
||||
{
|
||||
/* First, lower away 64-bit copies to smaller chunks, since we don't have
|
||||
* 64-bit ALU so we always want to split.
|
||||
*/
|
||||
struct agx_copy *copies2 = calloc(sizeof(copies[0]), num_copies * 2);
|
||||
unsigned num_copies2 = 0;
|
||||
|
||||
for (unsigned i = 0; i < num_copies; ++i) {
|
||||
struct agx_copy copy = copies[i];
|
||||
|
||||
/* Filter by class */
|
||||
if (copy.dest_mem != cls)
|
||||
continue;
|
||||
|
||||
assert(copy.dest < AGX_NUM_MODELED_REGS);
|
||||
|
||||
if (copy.src.size == AGX_SIZE_64) {
|
||||
copy.src.size = AGX_SIZE_32;
|
||||
copies2[num_copies2++] = copy;
|
||||
|
||||
if (copy.src.type == AGX_INDEX_IMMEDIATE) {
|
||||
static_assert(sizeof(copy.src.value) * 8 == 32, "known size");
|
||||
copy.src.value = 0;
|
||||
} else {
|
||||
assert(copy.src.type == AGX_INDEX_REGISTER ||
|
||||
copy.src.type == AGX_INDEX_UNIFORM);
|
||||
|
||||
copy.src.value += 2;
|
||||
}
|
||||
|
||||
copy.dest += 2;
|
||||
copies2[num_copies2++] = copy;
|
||||
} else {
|
||||
copies2[num_copies2++] = copy;
|
||||
}
|
||||
}
|
||||
|
||||
copies = copies2;
|
||||
num_copies = num_copies2;
|
||||
|
||||
/* Set up the bookkeeping */
|
||||
struct copy_ctx _ctx = {.entry_count = num_copies};
|
||||
struct copy_ctx *ctx = &_ctx;
|
||||
|
||||
memset(ctx->physreg_dest, 0, sizeof(ctx->physreg_dest));
|
||||
memset(ctx->physreg_use_count, 0, sizeof(ctx->physreg_use_count));
|
||||
|
||||
for (unsigned i = 0; i < ctx->entry_count; i++) {
|
||||
struct agx_copy *entry = &copies[i];
|
||||
|
||||
ctx->entries[i] = *entry;
|
||||
|
||||
for (unsigned j = 0; j < agx_size_align_16(entry->src.size); j++) {
|
||||
if (is_real(entry))
|
||||
ctx->physreg_use_count[entry->src.value + j]++;
|
||||
|
||||
/* Copies should not have overlapping destinations. */
|
||||
assert(!ctx->physreg_dest[entry->dest + j]);
|
||||
ctx->physreg_dest[entry->dest + j] = &ctx->entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to vectorize aligned 16-bit copies to use 32-bit operations instead */
|
||||
for (unsigned i = 0; i < ctx->entry_count; i++) {
|
||||
struct agx_copy *entry = &ctx->entries[i];
|
||||
if (entry->src.size != AGX_SIZE_16)
|
||||
continue;
|
||||
|
||||
if ((entry->dest & 1) || (entry->src.value & 1))
|
||||
continue;
|
||||
|
||||
if (entry->src.type != AGX_INDEX_UNIFORM &&
|
||||
entry->src.type != AGX_INDEX_REGISTER)
|
||||
continue;
|
||||
|
||||
unsigned next_dest = entry->dest + 1;
|
||||
assert(next_dest < ARRAY_SIZE(ctx->physreg_dest) && "aligned reg");
|
||||
|
||||
struct agx_copy *next_copy = ctx->physreg_dest[next_dest];
|
||||
if (!next_copy)
|
||||
continue;
|
||||
|
||||
assert(next_copy->dest == next_dest && "data structure invariant");
|
||||
assert(next_copy->src.size == AGX_SIZE_16 && "unaligned copy");
|
||||
|
||||
if (next_copy->src.type != entry->src.type)
|
||||
continue;
|
||||
|
||||
if (next_copy->src.value != (entry->src.value + 1))
|
||||
continue;
|
||||
|
||||
/* Vectorize the copies */
|
||||
ctx->physreg_dest[next_dest] = entry;
|
||||
entry->src.size = AGX_SIZE_32;
|
||||
next_copy->done = true;
|
||||
}
|
||||
|
||||
bool progress = true;
|
||||
while (progress) {
|
||||
progress = false;
|
||||
|
||||
/* Step 1: resolve paths in the transfer graph. This means finding
|
||||
* copies whose destination aren't blocked by something else and then
|
||||
* emitting them, continuing this process until every copy is blocked
|
||||
* and there are only cycles left.
|
||||
*
|
||||
* TODO: We should note that src is also available in dest to unblock
|
||||
* cycles that src is involved in.
|
||||
*/
|
||||
|
||||
for (unsigned i = 0; i < ctx->entry_count; i++) {
|
||||
struct agx_copy *entry = &ctx->entries[i];
|
||||
if (!entry->done && !entry_blocked(entry, ctx)) {
|
||||
entry->done = true;
|
||||
progress = true;
|
||||
do_copy(b, entry);
|
||||
for (unsigned j = 0; j < agx_size_align_16(entry->src.size); j++) {
|
||||
if (is_real(entry))
|
||||
ctx->physreg_use_count[entry->src.value + j]--;
|
||||
ctx->physreg_dest[entry->dest + j] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (progress)
|
||||
continue;
|
||||
|
||||
/* Step 2: Find partially blocked copies and split them. In the
|
||||
* mergedregs case, we can 32-bit copies which are only blocked on one
|
||||
* 16-bit half, and splitting them helps get things moving.
|
||||
*
|
||||
* We can skip splitting copies if the source isn't a register,
|
||||
* however, because it does not unblock anything and therefore doesn't
|
||||
* contribute to making forward progress with step 1. These copies
|
||||
* should still be resolved eventually in step 1 because they can't be
|
||||
* part of a cycle.
|
||||
*/
|
||||
for (unsigned i = 0; i < ctx->entry_count; i++) {
|
||||
struct agx_copy *entry = &ctx->entries[i];
|
||||
if (entry->done || (agx_size_align_16(entry->src.size) != 2))
|
||||
continue;
|
||||
|
||||
if (((ctx->physreg_use_count[entry->dest] == 0 ||
|
||||
ctx->physreg_use_count[entry->dest + 1] == 0)) &&
|
||||
is_real(entry)) {
|
||||
split_32bit_copy(ctx, entry);
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 3: resolve cycles through swapping.
|
||||
*
|
||||
* At this point, the transfer graph should consist of only cycles.
|
||||
* The reason is that, given any physreg n_1 that's the source of a
|
||||
* remaining entry, it has a destination n_2, which (because every
|
||||
* copy is blocked) is the source of some other copy whose destination
|
||||
* is n_3, and so we can follow the chain until we get a cycle. If we
|
||||
* reached some other node than n_1:
|
||||
*
|
||||
* n_1 -> n_2 -> ... -> n_i
|
||||
* ^ |
|
||||
* |-------------|
|
||||
*
|
||||
* then n_2 would be the destination of 2 copies, which is illegal
|
||||
* (checked above in an assert). So n_1 must be part of a cycle:
|
||||
*
|
||||
* n_1 -> n_2 -> ... -> n_i
|
||||
* ^ |
|
||||
* |---------------------|
|
||||
*
|
||||
* and this must be only cycle n_1 is involved in, because any other
|
||||
* path starting from n_1 would also have to end in n_1, resulting in
|
||||
* a node somewhere along the way being the destination of 2 copies
|
||||
* when the 2 paths merge.
|
||||
*
|
||||
* The way we resolve the cycle is through picking a copy (n_1, n_2)
|
||||
* and swapping n_1 and n_2. This moves n_1 to n_2, so n_2 is taken
|
||||
* out of the cycle:
|
||||
*
|
||||
* n_1 -> ... -> n_i
|
||||
* ^ |
|
||||
* |--------------|
|
||||
*
|
||||
* and we can keep repeating this until the cycle is empty.
|
||||
*/
|
||||
|
||||
for (unsigned i = 0; i < ctx->entry_count; i++) {
|
||||
struct agx_copy *entry = &ctx->entries[i];
|
||||
if (entry->done)
|
||||
continue;
|
||||
|
||||
assert(is_real(entry));
|
||||
|
||||
/* catch trivial copies */
|
||||
if (entry->dest == entry->src.value) {
|
||||
entry->done = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
do_swap(b, entry);
|
||||
|
||||
/* Split any blocking copies whose sources are only partially
|
||||
* contained within our destination.
|
||||
*/
|
||||
if (agx_size_align_16(entry->src.size) == 1) {
|
||||
for (unsigned j = 0; j < ctx->entry_count; j++) {
|
||||
struct agx_copy *blocking = &ctx->entries[j];
|
||||
|
||||
if (blocking->done)
|
||||
continue;
|
||||
|
||||
if (blocking->src.value <= entry->dest &&
|
||||
blocking->src.value + 1 >= entry->dest &&
|
||||
agx_size_align_16(blocking->src.size) == 2) {
|
||||
split_32bit_copy(ctx, blocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update sources of blocking copies.
|
||||
*
|
||||
* Note: at this point, every blocking copy's source should be
|
||||
* contained within our destination.
|
||||
*/
|
||||
for (unsigned j = 0; j < ctx->entry_count; j++) {
|
||||
struct agx_copy *blocking = &ctx->entries[j];
|
||||
if (blocking->src.value >= entry->dest &&
|
||||
blocking->src.value <
|
||||
entry->dest + agx_size_align_16(entry->src.size)) {
|
||||
blocking->src.value =
|
||||
entry->src.value + (blocking->src.value - entry->dest);
|
||||
}
|
||||
}
|
||||
|
||||
entry->done = true;
|
||||
}
|
||||
|
||||
free(copies2);
|
||||
}
|
||||
|
||||
void
|
||||
agx_emit_parallel_copies(agx_builder *b, struct agx_copy *copies,
|
||||
unsigned num_copies)
|
||||
{
|
||||
/* Emit copies fo reach register class separately because we don't have
|
||||
* register class awareness in the parallel copy lowering data structure.
|
||||
*/
|
||||
agx_emit_parallel_copies_for_class(b, copies, num_copies, false);
|
||||
agx_emit_parallel_copies_for_class(b, copies, num_copies, true);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/lut.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/* Lower pseudo instructions created during optimization. */
|
||||
|
||||
static agx_instr *
|
||||
while_for_break_if(agx_builder *b, agx_instr *I)
|
||||
{
|
||||
if (I->op == AGX_OPCODE_BREAK_IF_FCMP) {
|
||||
return agx_while_fcmp(b, I->src[0], I->src[1], I->nest, I->fcond,
|
||||
!I->invert_cond, NULL);
|
||||
} else {
|
||||
return agx_while_icmp(b, I->src[0], I->src[1], I->nest, I->icond,
|
||||
!I->invert_cond, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static agx_instr *
|
||||
cmpsel_for_break_if(agx_builder *b, agx_instr *I)
|
||||
{
|
||||
agx_index r0l = agx_register(0, AGX_SIZE_16);
|
||||
|
||||
/* If the condition is true, set r0l to nest to break */
|
||||
agx_index t = agx_immediate(I->nest);
|
||||
agx_index f = r0l;
|
||||
|
||||
if (I->invert_cond) {
|
||||
SWAP(t, f);
|
||||
}
|
||||
|
||||
if (I->op == AGX_OPCODE_BREAK_IF_FCMP)
|
||||
agx_fcmpsel_to(b, r0l, I->src[0], I->src[1], t, f, I->fcond);
|
||||
else
|
||||
agx_icmpsel_to(b, r0l, I->src[0], I->src[1], t, f, I->icond);
|
||||
|
||||
return agx_push_exec(b, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
swap(agx_builder *b, agx_index x, agx_index y)
|
||||
{
|
||||
assert(!x.memory && "already lowered");
|
||||
assert(!y.memory && "already lowered");
|
||||
|
||||
/* We can swap lo/hi halves of a 32-bit register with a 32-bit extr */
|
||||
if (x.size == AGX_SIZE_16 && (x.value >> 1) == (y.value >> 1)) {
|
||||
|
||||
assert(((x.value & 1) == (1 - (y.value & 1))) &&
|
||||
"no trivial swaps, and only 2 halves of a register");
|
||||
|
||||
/* r0 = extr r0, r0, #16
|
||||
* = (((r0 << 32) | r0) >> 16) & 0xFFFFFFFF
|
||||
* = (((r0 << 32) >> 16) & 0xFFFFFFFF) | (r0 >> 16)
|
||||
* = (r0l << 16) | r0h
|
||||
*/
|
||||
agx_index reg32 = agx_register(x.value & ~1, AGX_SIZE_32);
|
||||
agx_extr_to(b, reg32, reg32, reg32, agx_immediate(16), 0);
|
||||
} else {
|
||||
/* Otherwise, we're swapping GPRs and fallback on a XOR swap. */
|
||||
agx_xor_to(b, x, x, y);
|
||||
agx_xor_to(b, y, x, y);
|
||||
agx_xor_to(b, x, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
static agx_instr *
|
||||
lower(agx_builder *b, agx_instr *I)
|
||||
{
|
||||
switch (I->op) {
|
||||
|
||||
/* Various instructions are implemented as bitwise truth tables */
|
||||
case AGX_OPCODE_MOV:
|
||||
return agx_bitop_to(b, I->dest[0], I->src[0], agx_zero(), UTIL_LUT2(a));
|
||||
|
||||
case AGX_OPCODE_NOT:
|
||||
return agx_bitop_to(b, I->dest[0], I->src[0], agx_zero(), UTIL_LUT2(~a));
|
||||
|
||||
/* We can sign-extend with an add */
|
||||
case AGX_OPCODE_SIGNEXT:
|
||||
return agx_iadd_to(b, I->dest[0], I->src[0], agx_zero(), 0);
|
||||
|
||||
/* Unfused comparisons are fused with a 0/1 select */
|
||||
case AGX_OPCODE_ICMP:
|
||||
return agx_icmpsel_to(b, I->dest[0], I->src[0], I->src[1],
|
||||
agx_immediate(I->invert_cond ? 0 : 1),
|
||||
agx_immediate(I->invert_cond ? 1 : 0), I->icond);
|
||||
|
||||
case AGX_OPCODE_FCMP:
|
||||
return agx_fcmpsel_to(b, I->dest[0], I->src[0], I->src[1],
|
||||
agx_immediate(I->invert_cond ? 0 : 1),
|
||||
agx_immediate(I->invert_cond ? 1 : 0), I->fcond);
|
||||
|
||||
case AGX_OPCODE_BALLOT:
|
||||
return agx_icmp_ballot_to(b, I->dest[0], I->src[0], agx_zero(),
|
||||
AGX_ICOND_UEQ, true /* invert */);
|
||||
|
||||
case AGX_OPCODE_QUAD_BALLOT:
|
||||
return agx_icmp_quad_ballot_to(b, I->dest[0], I->src[0], agx_zero(),
|
||||
AGX_ICOND_UEQ, true /* invert */);
|
||||
|
||||
/* Writes to the nesting counter lowered to the real register */
|
||||
case AGX_OPCODE_BEGIN_CF:
|
||||
return agx_mov_imm_to(b, agx_register(0, AGX_SIZE_16), 0);
|
||||
|
||||
case AGX_OPCODE_BREAK:
|
||||
agx_mov_imm_to(b, agx_register(0, AGX_SIZE_16), I->nest);
|
||||
return agx_pop_exec(b, 0);
|
||||
|
||||
case AGX_OPCODE_BREAK_IF_ICMP:
|
||||
case AGX_OPCODE_BREAK_IF_FCMP: {
|
||||
if (I->nest == 1)
|
||||
return while_for_break_if(b, I);
|
||||
else
|
||||
return cmpsel_for_break_if(b, I);
|
||||
}
|
||||
|
||||
case AGX_OPCODE_SWAP:
|
||||
swap(b, I->src[0], I->src[1]);
|
||||
return (void *)true;
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_lower_pseudo(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_instr_global_safe(ctx, I) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
if (lower(&b, I))
|
||||
agx_remove_instruction(I);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/macros.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compile.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Lower moves involving memory registers (created when spilling) to concrete
|
||||
* spills and fills.
|
||||
*/
|
||||
|
||||
static void
|
||||
spill_fill(agx_builder *b, agx_instr *I, enum agx_size size, unsigned channels,
|
||||
unsigned component_offset_el)
|
||||
{
|
||||
unsigned size_B = agx_size_align_16(size) * 2;
|
||||
enum agx_format format = size_B == 2 ? AGX_FORMAT_I16 : AGX_FORMAT_I32;
|
||||
unsigned format_size_B = size_B == 2 ? 2 : 4;
|
||||
|
||||
unsigned offset_B = component_offset_el * size_B;
|
||||
unsigned effective_chans = size == AGX_SIZE_64 ? (channels * 2) : channels;
|
||||
unsigned mask = BITFIELD_MASK(effective_chans);
|
||||
|
||||
assert(effective_chans <= 4);
|
||||
|
||||
/* Pick off the memory and register parts of the move */
|
||||
agx_index mem = I->dest[0].memory ? I->dest[0] : I->src[0];
|
||||
agx_index reg = I->dest[0].memory ? I->src[0] : I->dest[0];
|
||||
|
||||
assert(mem.type == AGX_INDEX_REGISTER && mem.memory);
|
||||
assert(reg.type == AGX_INDEX_REGISTER && !reg.memory);
|
||||
|
||||
/* Slice the register according to the part of the spill we're handling */
|
||||
if (component_offset_el > 0 || channels != agx_channels(reg)) {
|
||||
reg.value += component_offset_el * agx_size_align_16(reg.size);
|
||||
reg.channels_m1 = channels - 1;
|
||||
}
|
||||
|
||||
/* Calculate stack offset in bytes. IR registers are 2-bytes each. */
|
||||
unsigned stack_offs_B = b->shader->spill_base_B + (mem.value * 2) + offset_B;
|
||||
unsigned stack_offs_end_B = stack_offs_B + (effective_chans * format_size_B);
|
||||
|
||||
assert(stack_offs_end_B <= b->shader->scratch_size_B &&
|
||||
"RA allocates enough scratch");
|
||||
|
||||
/* Emit the spill/fill */
|
||||
if (I->dest[0].memory) {
|
||||
agx_stack_store(b, reg, agx_immediate(stack_offs_B), format, mask);
|
||||
} else {
|
||||
agx_stack_load_to(b, reg, agx_immediate(stack_offs_B), format, mask);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_lower_spill(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_instr_global_safe(ctx, I) {
|
||||
if (I->op != AGX_OPCODE_MOV || (!I->dest[0].memory && !I->src[0].memory))
|
||||
continue;
|
||||
|
||||
enum agx_size size = I->dest[0].size;
|
||||
unsigned channels = agx_channels(I->dest[0]);
|
||||
|
||||
assert(size == I->src[0].size);
|
||||
assert(channels == agx_channels(I->src[0]));
|
||||
|
||||
/* Texture gradient sources can be vec6, and if such a vector is spilled,
|
||||
* we need to be able to spill/fill a vec6. Since stack_store/stack_load
|
||||
* only work up to vec4, we break up into (at most) vec4 components.
|
||||
*/
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
for (unsigned c = 0; c < channels; c += 4) {
|
||||
spill_fill(&b, I, size, MIN2(channels - c, 4), c);
|
||||
}
|
||||
|
||||
agx_remove_instruction(I);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/*
|
||||
* Not all instructions can take uniforms. Memory instructions can take
|
||||
* uniforms, but only for their base (first) source and only in the
|
||||
* low-half of the uniform file.
|
||||
*
|
||||
* This pass lowers invalid uniforms.
|
||||
*/
|
||||
bool
|
||||
agx_instr_accepts_uniform(enum agx_opcode op, unsigned src_index,
|
||||
unsigned value, enum agx_size size)
|
||||
{
|
||||
/* Some instructions only seem able to access uniforms in the low half */
|
||||
bool high = value >= 256;
|
||||
|
||||
/* ALU cannot access 64-bit uniforms */
|
||||
bool is_64 = size == AGX_SIZE_64;
|
||||
|
||||
switch (op) {
|
||||
case AGX_OPCODE_IMAGE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_SAMPLE:
|
||||
/* Unknown if this works, but the driver will never hit this. */
|
||||
assert(!(src_index == 2 && high) && "texture heap always low");
|
||||
return !high && (src_index == 1 || src_index == 2);
|
||||
|
||||
case AGX_OPCODE_TEX_STATE_STORE:
|
||||
case AGX_OPCODE_SAMPLER_STATE_STORE:
|
||||
assert(!high && "texture heap always low");
|
||||
return !high && src_index == 0;
|
||||
|
||||
case AGX_OPCODE_DEVICE_LOAD:
|
||||
return src_index == 0 && !high;
|
||||
case AGX_OPCODE_DEVICE_STORE:
|
||||
case AGX_OPCODE_ATOMIC:
|
||||
return src_index == 1 && !high;
|
||||
case AGX_OPCODE_LOCAL_LOAD:
|
||||
return src_index == 0;
|
||||
case AGX_OPCODE_LOCAL_STORE:
|
||||
return src_index == 1;
|
||||
case AGX_OPCODE_IMAGE_WRITE:
|
||||
return src_index == 3;
|
||||
case AGX_OPCODE_BLOCK_IMAGE_STORE:
|
||||
case AGX_OPCODE_ZS_EMIT:
|
||||
case AGX_OPCODE_ST_TILE:
|
||||
case AGX_OPCODE_LD_TILE:
|
||||
case AGX_OPCODE_UNIFORM_STORE:
|
||||
case AGX_OPCODE_ST_VARY:
|
||||
case AGX_OPCODE_LOCAL_ATOMIC:
|
||||
case AGX_OPCODE_SAMPLE_MASK:
|
||||
case AGX_OPCODE_ITER:
|
||||
case AGX_OPCODE_ITERPROJ:
|
||||
case AGX_OPCODE_STACK_LOAD:
|
||||
case AGX_OPCODE_STACK_STORE:
|
||||
case AGX_OPCODE_BALLOT:
|
||||
case AGX_OPCODE_FCMP_BALLOT:
|
||||
case AGX_OPCODE_ICMP_BALLOT:
|
||||
case AGX_OPCODE_QUAD_BALLOT:
|
||||
case AGX_OPCODE_FCMP_QUAD_BALLOT:
|
||||
case AGX_OPCODE_ICMP_QUAD_BALLOT:
|
||||
return false;
|
||||
case AGX_OPCODE_EXPORT:
|
||||
case AGX_OPCODE_PHI:
|
||||
/* We would fail validation otherwise */
|
||||
return true;
|
||||
default:
|
||||
return !is_64;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_lower_uniform_sources(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_instr_global_safe(ctx, I) {
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
agx_foreach_src(I, s) {
|
||||
if (I->src[s].type == AGX_INDEX_UNIFORM &&
|
||||
!agx_instr_accepts_uniform(I->op, s, I->src[s].value,
|
||||
I->src[s].size)) {
|
||||
|
||||
agx_index idx = I->src[s];
|
||||
idx.abs = idx.neg = false;
|
||||
agx_replace_src(I, s, agx_mov(&b, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct nir_shader;
|
||||
struct nir_instr;
|
||||
|
||||
bool agx_nir_lower_address(struct nir_shader *shader);
|
||||
bool agx_nir_lower_algebraic_late(struct nir_shader *shader);
|
||||
bool agx_nir_cleanup_amul(struct nir_shader *shader);
|
||||
bool agx_nir_fuse_lea(struct nir_shader *shader);
|
||||
bool agx_nir_lower_lea(struct nir_shader *shader);
|
||||
bool agx_nir_fuse_selects(struct nir_shader *shader);
|
||||
bool agx_nir_fuse_algebraic_late(struct nir_shader *shader);
|
||||
bool agx_nir_fence_images(struct nir_shader *shader);
|
||||
bool agx_nir_lower_layer(struct nir_shader *s);
|
||||
bool agx_nir_lower_clip_distance(struct nir_shader *s);
|
||||
bool agx_nir_lower_subgroups(struct nir_shader *s);
|
||||
bool agx_nir_lower_fminmax(struct nir_shader *s);
|
||||
|
||||
bool agx_nir_lower_texture_early(struct nir_shader *s, bool support_lod_bias);
|
||||
bool agx_nir_lower_texture(struct nir_shader *s);
|
||||
bool agx_nir_lower_multisampled_image_store(struct nir_shader *s);
|
||||
bool agx_nir_needs_texture_crawl(struct nir_instr *instr);
|
||||
@@ -0,0 +1,359 @@
|
||||
# Copyright 2022 Alyssa Rosenzweig
|
||||
# Copyright 2021 Collabora, Ltd.
|
||||
# Copyright 2016 Intel Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import math
|
||||
|
||||
a = 'a'
|
||||
b = 'b'
|
||||
c = 'c'
|
||||
d = 'd'
|
||||
e = 'e'
|
||||
|
||||
lower_sm5_shift = []
|
||||
|
||||
# Our shifts differ from SM5 for the upper bits. Mask to match the NIR
|
||||
# behaviour. Because this happens as a late lowering, NIR won't optimize the
|
||||
# masking back out (that happens in the main nir_opt_algebraic).
|
||||
for s in [8, 16, 32, 64]:
|
||||
for shift in ["ishl", "ishr", "ushr"]:
|
||||
lower_sm5_shift += [((shift, f'a@{s}', b),
|
||||
(shift, a, ('iand', b, s - 1)))]
|
||||
|
||||
lower_pack = [
|
||||
(('pack_half_2x16_split', a, b),
|
||||
('pack_32_2x16_split', ('f2f16', a), ('f2f16', b))),
|
||||
|
||||
# We don't have 8-bit ALU, so we need to lower this. But if we lower it like
|
||||
# this, we can at least coalesce the pack_32_2x16_split and only pay the
|
||||
# cost of the iors and ishl. (u2u16 of 8-bit is assumed free.)
|
||||
(('pack_32_4x8_split', a, b, c, d),
|
||||
('pack_32_2x16_split', ('ior', ('u2u16', a), ('ishl', ('u2u16', b), 8)),
|
||||
('ior', ('u2u16', c), ('ishl', ('u2u16', d), 8)))),
|
||||
|
||||
(('extract_u16', 'a@32', 0), ('u2u32', ('unpack_32_2x16_split_x', a))),
|
||||
(('extract_u16', 'a@32', 1), ('u2u32', ('unpack_32_2x16_split_y', a))),
|
||||
(('extract_i16', 'a@32', 0), ('i2i32', ('unpack_32_2x16_split_x', a))),
|
||||
(('extract_i16', 'a@32', 1), ('i2i32', ('unpack_32_2x16_split_y', a))),
|
||||
|
||||
# For optimizing extract->convert sequences for unpack/pack norm
|
||||
(('u2f32', ('u2u32', a)), ('u2f32', a)),
|
||||
(('i2f32', ('i2i32', a)), ('i2f32', a)),
|
||||
|
||||
# Chew through some 8-bit before the backend has to deal with it
|
||||
(('f2u8', a), ('u2u8', ('f2u16', a))),
|
||||
(('f2i8', a), ('i2i8', ('f2i16', a))),
|
||||
|
||||
# Duplicated from nir_opt_algebraic since this pattern is generated by our
|
||||
# bounds checking optimization which needs to run relatively late.
|
||||
(('unpack_64_2x32_split_x', ('pack_64_2x32_split', a, b)), a),
|
||||
(('unpack_64_2x32_split_y', ('pack_64_2x32_split', a, b)), b),
|
||||
|
||||
# Based on the VIR lowering
|
||||
(('f2f16_rtz', 'a@32'),
|
||||
('bcsel', ('flt', ('fabs', a), ('fabs', ('f2f32', ('f2f16_rtne', a)))),
|
||||
('isub', ('f2f16_rtne', a), 1), ('f2f16_rtne', a))),
|
||||
|
||||
# These are based on the lowerings from nir_opt_algebraic, but conditioned
|
||||
# on the number of bits not being constant. If the bit count is constant
|
||||
# (the happy path) we can use our native instruction instead.
|
||||
(('ibitfield_extract', 'value', 'offset', 'bits(is_not_const)'),
|
||||
('bcsel', ('ieq', 0, 'bits'),
|
||||
0,
|
||||
('ishr',
|
||||
('ishl', 'value', ('isub', ('isub', 32, 'bits'), 'offset')),
|
||||
('isub', 32, 'bits')))),
|
||||
|
||||
(('ubitfield_extract', 'value', 'offset', 'bits(is_not_const)'),
|
||||
('iand',
|
||||
('ushr', 'value', 'offset'),
|
||||
('bcsel', ('ieq', 'bits', 32),
|
||||
0xffffffff,
|
||||
('isub', ('ishl', 1, 'bits'), 1)))),
|
||||
|
||||
# Codegen depends on this trivial case being optimized out.
|
||||
(('ubitfield_extract', 'value', 'offset', 0), 0),
|
||||
(('ibitfield_extract', 'value', 'offset', 0), 0),
|
||||
|
||||
# At this point, bitfield extracts are constant. We can only do constant
|
||||
# unsigned bitfield extract, so lower signed to unsigned + sign extend.
|
||||
(('ibitfield_extract', a, b, '#bits'),
|
||||
('ishr', ('ishl', ('ubitfield_extract', a, b, 'bits'), ('isub', 32, 'bits')),
|
||||
('isub', 32, 'bits'))),
|
||||
]
|
||||
|
||||
lower_selects = []
|
||||
for T, sizes, one in [('f', [16, 32], 1.0),
|
||||
('i', [8, 16, 32], 1),
|
||||
('b', [16, 32], -1)]:
|
||||
for size in sizes:
|
||||
lower_selects.extend([
|
||||
((f'b2{T}{size}', ('inot', 'a@1')), ('bcsel', a, 0, one)),
|
||||
((f'b2{T}{size}', 'a@1'), ('bcsel', a, one, 0)),
|
||||
])
|
||||
|
||||
# Rewriting bcsel(a || b, ...) in terms of bcsel(a, ...) and bcsel(b, ...) lets
|
||||
# our rules to fuse compare-and-select do a better job, assuming that a and b
|
||||
# are comparisons themselves.
|
||||
#
|
||||
# This needs to be a separate pass that runs after lower_selects, in order to
|
||||
# pick up patterns like b2f32(iand(...))
|
||||
opt_selects = [
|
||||
(('bcsel', ('ior(is_used_once)', ('inot', a), b), c, d),
|
||||
('bcsel', a, ('bcsel', b, c, d), c)),
|
||||
|
||||
(('bcsel', ('ior(is_used_once)', a, b), c, d),
|
||||
('bcsel', a, c, ('bcsel', b, c, d))),
|
||||
|
||||
(('bcsel', ('iand(is_used_once)', ('inot', a), b), c, d),
|
||||
('bcsel', a, d, ('bcsel', b, c, d))),
|
||||
|
||||
(('bcsel', ('iand(is_used_once)', a, b), c, d),
|
||||
('bcsel', a, ('bcsel', b, c, d), d)),
|
||||
]
|
||||
|
||||
# When the ior/iand is used multiple times, we can instead fuse the other way.
|
||||
opt_selects.extend([
|
||||
(('iand', ('inot', 'a@1'), b), ('bcsel', a, False, b)),
|
||||
(('iand', 'a@1', b), ('bcsel', a, b, False)),
|
||||
|
||||
(('ior', ('inot', 'a@1'), b), ('bcsel', a, b, True)),
|
||||
(('ior', 'a@1', b), ('bcsel', a, True, b)),
|
||||
])
|
||||
|
||||
# Rewriting fneg to ixor costs a uniform but lets us CSE the bcsel if this is
|
||||
# applied to a whole vector, hence the has_multiple_uses heuristic.
|
||||
opt_selects.extend([
|
||||
(('bcsel', 'a(has_multiple_uses)', ('fneg(is_used_once)', 'b@32'), b),
|
||||
('ixor', b, ('bcsel', a, 1 << 31, 0))),
|
||||
|
||||
(('bcsel', 'a(has_multiple_uses)', b, ('fneg(is_used_once)', 'b@32')),
|
||||
('ixor', b, ('bcsel', a, 0, 1 << 31))),
|
||||
])
|
||||
|
||||
fuse_extr = []
|
||||
for start in range(32):
|
||||
fuse_extr.extend([
|
||||
(('ior', ('ushr', 'a@32', start), ('ishl', 'b@32', 32 - start)),
|
||||
('extr_agx', a, b, start, 0)),
|
||||
])
|
||||
|
||||
fuse_ubfe = []
|
||||
for bits in range(1, 32):
|
||||
fuse_ubfe.extend([
|
||||
(('iand', ('ushr', 'a@32', b), (1 << bits) - 1),
|
||||
('ubitfield_extract', a, b, bits))
|
||||
])
|
||||
|
||||
# (x * y) + s = (x * y) + (s << 0)
|
||||
def imad(x, y, z):
|
||||
return ('imadshl_agx', x, y, z, 0)
|
||||
|
||||
# (x * y) - s = (x * y) - (s << 0)
|
||||
def imsub(x, y, z):
|
||||
return ('imsubshl_agx', x, y, z, 0)
|
||||
|
||||
# x + (y << s) = (x * 1) + (y << s)
|
||||
def iaddshl(x, y, s):
|
||||
return ('imadshl_agx', x, 1, y, s)
|
||||
|
||||
# x - (y << s) = (x * 1) - (y << s)
|
||||
def isubshl(x, y, s):
|
||||
return ('imsubshl_agx', x, 1, y, s)
|
||||
|
||||
fuse_imad = [
|
||||
# Reassociate imul+iadd chain in order to fuse imads. This pattern comes up
|
||||
# in compute shader lowering.
|
||||
(('iadd', ('iadd(is_used_once)', ('imul(is_used_once)', a, b),
|
||||
('imul(is_used_once)', c, d)), e),
|
||||
imad(a, b, imad(c, d, e))),
|
||||
|
||||
# Fuse regular imad
|
||||
(('iadd', ('imul(is_used_once)', a, b), c), imad(a, b, c)),
|
||||
(('isub', ('imul(is_used_once)', a, b), c), imsub(a, b, c)),
|
||||
]
|
||||
|
||||
for s in range(1, 5):
|
||||
fuse_imad += [
|
||||
# Definitions
|
||||
(('iadd', a, ('ishl(is_used_once)', b, s)), iaddshl(a, b, s)),
|
||||
(('isub', a, ('ishl(is_used_once)', b, s)), isubshl(a, b, s)),
|
||||
|
||||
# ineg(x) is 0 - x
|
||||
(('ineg', ('ishl(is_used_once)', b, s)), isubshl(0, b, s)),
|
||||
|
||||
# Definitions
|
||||
(imad(a, b, ('ishl(is_used_once)', c, s)), ('imadshl_agx', a, b, c, s)),
|
||||
(imsub(a, b, ('ishl(is_used_once)', c, s)), ('imsubshl_agx', a, b, c, s)),
|
||||
|
||||
# The above but after the below shift lowering
|
||||
(imad(a, b, ('imadshl_agx(is_used_once)', 0, 1, c, s)), ('imadshl_agx', a, b, c, s)),
|
||||
(imsub(a, b, ('imadshl_agx(is_used_once)', 0, 1, c, s)), ('imsubshl_agx', a, b, c, s)),
|
||||
|
||||
# a + (a << s) = a + a * (1 << s) = a * (1 + (1 << s))
|
||||
(('imul', a, 1 + (1 << s)), iaddshl(a, a, s)),
|
||||
|
||||
# a - (a << s) = a - a * (1 << s) = a * (1 - (1 << s))
|
||||
(('imul', a, 1 - (1 << s)), isubshl(a, a, s)),
|
||||
|
||||
# a - (a << s) = a * (1 - (1 << s)) = -(a * (1 << s) - 1)
|
||||
(('ineg', ('imul(is_used_once)', a, (1 << s) - 1)), isubshl(a, a, s)),
|
||||
|
||||
# iadd is SCIB, general shfit is IC (slower)
|
||||
(('ishl', a, s), iaddshl(0, a, s)),
|
||||
]
|
||||
|
||||
# If the above rules failed, we have a large constant shift on the IC unit.
|
||||
# Might as well fuse an add to form an imad, if we're on the IC anyway.
|
||||
fuse_imad += [
|
||||
(('iadd', a, ('ishl(is_used_once)', b, '#c')), imad(b, ('ishl', 1, c), a)),
|
||||
]
|
||||
|
||||
# Discard lowering generates this pattern, clean it up
|
||||
ixor_bcsel = [
|
||||
(('ixor', ('bcsel', a, '#b', '#c'), '#d'),
|
||||
('bcsel', a, ('ixor', b, d), ('ixor', c, d))),
|
||||
]
|
||||
|
||||
# The main NIR optimizer works on imul, not iadd. We need just enough patterns
|
||||
# for amul to let us fuse lea.
|
||||
cleanup_amul = [
|
||||
# Neither operation overflows so we can keep the amul.
|
||||
(('amul', ('amul', a, '#b'), '#c'), ('amul', a, ('imul', b, c))),
|
||||
|
||||
# Result of u2u64 has zero in upper half, so the shift doesn't overflow, so
|
||||
# neither multiplication overflows.
|
||||
(('amul', ('ishl', ('u2u64', 'a@32'), '#b(is_ult_32)'), '#c'),
|
||||
('amul', ('u2u64', a), ('ishl', c, b))),
|
||||
|
||||
# We depending on simplifying this.
|
||||
(('iadd', a, 0), a),
|
||||
]
|
||||
|
||||
fuse_lea = []
|
||||
|
||||
# Handle 64-bit address arithmetic (OpenCL)
|
||||
for s in range(1, 5):
|
||||
pot = 1 << s
|
||||
|
||||
fuse_lea += [
|
||||
# A + (#b + c) 2^s = (A + c 2^s) + #b 2^s
|
||||
(('iadd', 'a@64', ('amul', pot, ('iadd', '#b(is_upper_half_zero)', ('u2u64', 'c@32')))),
|
||||
('ulea_agx', ('ulea_agx', a, c, s), ('u2u32', b), s)),
|
||||
|
||||
# A + (B + c) 2^s = (A + B 2^s) + c 2^s
|
||||
(('iadd', 'a@64', ('amul', ('iadd', 'b@64', ('i2i64', 'c@32')), pot)),
|
||||
('ilea_agx', ('iadd', a, ('ishl', b, s)), c, s)),
|
||||
|
||||
# A + 2^s (B + (C + d)) = (A + (B + C)2^s) + d 2^s
|
||||
(('iadd', 'a@64', ('amul', ('iadd', 'b@64',
|
||||
('iadd', 'c@64', ('u2u64', 'd@32'))), pot)),
|
||||
('ulea_agx', ('iadd', a, ('ishl', ('iadd', b, c), s)), d, s)),
|
||||
]
|
||||
|
||||
for sgn in ["u", "i"]:
|
||||
upconv = f'{sgn}2{sgn}64'
|
||||
lea = f'{sgn}lea_agx'
|
||||
|
||||
fuse_lea += [
|
||||
# Basic pattern match
|
||||
(('iadd', 'a@64', ('amul', (upconv, 'b@32'), pot)), (lea, a, b, s)),
|
||||
(('iadd', 'a@64', ('ishl', (upconv, 'b@32'), s)), (lea, a, b, s)),
|
||||
]
|
||||
|
||||
# Handle relaxed 32-bit address arithmetic (OpenGL, Vulkan)
|
||||
for s_ in range(1, 5):
|
||||
# Iterate backwards
|
||||
s = 5 - s_
|
||||
|
||||
v = 1 << s
|
||||
is_mult = f'(is_unsigned_multiple_of_{v})'
|
||||
|
||||
fuse_lea += [
|
||||
# A + b * s = A + B * s with relaxed multiply
|
||||
(('iadd', 'a@64', ('u2u64', ('amul', 'b@32', v))),
|
||||
('ulea_agx', a, b, s)),
|
||||
|
||||
# A + (b * c 2^s) = A + (b * c) 2^s with relaxed multiply
|
||||
(('iadd', 'a@64', ('u2u64', ('amul', 'b@32', f'#c{is_mult}'))),
|
||||
('ulea_agx', a, ('imul', b, ('ushr', c, s)), s)),
|
||||
|
||||
# A + (b 2^s + c d 2^s) = A + (b + cd) 2^s with relaxation.
|
||||
#
|
||||
# amul is bounded by the buffer size by definition, and both the GL & VK
|
||||
# limit UBOs and SSBOs to INT32_MAX bytes. Therefore, amul has no signed
|
||||
# wrap.
|
||||
#
|
||||
# Further, because we are zero-extending the 32-bit result, the 32-bit
|
||||
# sum must be nonnegative -- if it were negative, it would represent an
|
||||
# offset above INT32_MAX which would be invalid given the amul and
|
||||
# max buffer size. Thus with signed math
|
||||
#
|
||||
# 0 <= b 2^s + cd 2^s < INT32_MAX
|
||||
#
|
||||
# ..and hence
|
||||
#
|
||||
# 0 <= b + cd < INT32_MAX
|
||||
#
|
||||
# Those bounds together with distributivity mean that
|
||||
#
|
||||
# (b 2^s + cd 2^s) mod 2^32 = 2^s ((b + cd) mod 2^32)
|
||||
#
|
||||
# ...which is exactly what we need to factor out the shift.
|
||||
(('iadd', 'a@64', ('u2u64', ('iadd', f'#b{is_mult}',
|
||||
('amul', 'c@32', f'#d{is_mult}')))),
|
||||
('ulea_agx', a, ('iadd', ('ishr', b, s),
|
||||
('amul', 'c@32', ('ishr', d, s))), s)),
|
||||
]
|
||||
|
||||
# 8-bit rules are lowest precedence since we really like to fuse shifts
|
||||
fuse_lea += [
|
||||
(('iadd', a, ('u2u64', 'b@32')), ('ulea_agx', a, b, 0)),
|
||||
(('iadd', a, ('i2i64', 'b@32')), ('ilea_agx', a, b, 0)),
|
||||
|
||||
(('iadd', a, ('iadd', ('u2u64', 'b@32'), c)),
|
||||
('ulea_agx', ('iadd', a, c), b, 0)),
|
||||
(('iadd', a, ('iadd', ('i2i64', 'b@32'), c)),
|
||||
('ilea_agx', ('iadd', a, c), b, 0)),
|
||||
]
|
||||
|
||||
# After lowering address arithmetic, the various address arithmetic opcodes are
|
||||
# no longer useful. Lower them to regular arithmetic to let nir_opt_algebraic
|
||||
# take over.
|
||||
lower_lea = [
|
||||
(('amul', a, b), ('imul', a, b)),
|
||||
(('ulea_agx', a, b, c), ('iadd', a, ('ishl', ('u2u64', b), c))),
|
||||
(('ilea_agx', a, b, c), ('iadd', a, ('ishl', ('i2i64', b), c))),
|
||||
]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-p', '--import-path', required=True)
|
||||
args = parser.parse_args()
|
||||
sys.path.insert(0, args.import_path)
|
||||
run()
|
||||
|
||||
def run():
|
||||
import nir_algebraic # pylint: disable=import-error
|
||||
|
||||
print('#include "agx_nir.h"')
|
||||
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_cleanup_amul", cleanup_amul).render())
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_fuse_lea", fuse_lea).render())
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_lower_lea", lower_lea).render())
|
||||
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_lower_algebraic_late",
|
||||
lower_sm5_shift + lower_pack +
|
||||
lower_selects).render())
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_fuse_selects",
|
||||
opt_selects).render())
|
||||
print(nir_algebraic.AlgebraicPass("agx_nir_fuse_algebraic_late",
|
||||
fuse_extr + fuse_ubfe +
|
||||
fuse_imad + ixor_bcsel).render())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_nir.h"
|
||||
#include "nir.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_opcodes.h"
|
||||
|
||||
struct match {
|
||||
nir_scalar base, offset;
|
||||
bool sign_extend;
|
||||
uint8_t shift;
|
||||
};
|
||||
|
||||
static enum pipe_format
|
||||
format_for_bitsize(unsigned bitsize)
|
||||
{
|
||||
switch (bitsize) {
|
||||
case 8:
|
||||
return PIPE_FORMAT_R8_UINT;
|
||||
case 16:
|
||||
return PIPE_FORMAT_R16_UINT;
|
||||
case 32:
|
||||
return PIPE_FORMAT_R32_UINT;
|
||||
default:
|
||||
UNREACHABLE("should have been lowered");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
pass(struct nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
if (intr->intrinsic != nir_intrinsic_load_global &&
|
||||
intr->intrinsic != nir_intrinsic_load_global_constant &&
|
||||
intr->intrinsic != nir_intrinsic_global_atomic &&
|
||||
intr->intrinsic != nir_intrinsic_global_atomic_swap &&
|
||||
intr->intrinsic != nir_intrinsic_store_global)
|
||||
return false;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
unsigned bitsize = intr->intrinsic == nir_intrinsic_store_global
|
||||
? nir_src_bit_size(intr->src[0])
|
||||
: intr->def.bit_size;
|
||||
enum pipe_format format = format_for_bitsize(bitsize);
|
||||
unsigned format_shift = util_logbase2(util_format_get_blocksize(format));
|
||||
|
||||
nir_src *orig_offset = nir_get_io_offset_src(intr);
|
||||
nir_scalar base = nir_scalar_resolved(orig_offset->ssa, 0);
|
||||
struct match match = {.base = base};
|
||||
bool shift_must_match =
|
||||
(intr->intrinsic == nir_intrinsic_global_atomic) ||
|
||||
(intr->intrinsic == nir_intrinsic_global_atomic_swap);
|
||||
unsigned max_shift = format_shift + (shift_must_match ? 0 : 2);
|
||||
|
||||
if (nir_scalar_is_alu(base)) {
|
||||
nir_op op = nir_scalar_alu_op(base);
|
||||
if (op == nir_op_ulea_agx || op == nir_op_ilea_agx) {
|
||||
unsigned shift = nir_scalar_as_uint(nir_scalar_chase_alu_src(base, 2));
|
||||
if (shift >= format_shift && shift <= max_shift) {
|
||||
match = (struct match){
|
||||
.base = nir_scalar_chase_alu_src(base, 0),
|
||||
.offset = nir_scalar_chase_alu_src(base, 1),
|
||||
.shift = shift - format_shift,
|
||||
.sign_extend = (op == nir_op_ilea_agx),
|
||||
};
|
||||
}
|
||||
} else if (op == nir_op_iadd) {
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
nir_scalar const_scalar = nir_scalar_chase_alu_src(base, i);
|
||||
if (!nir_scalar_is_const(const_scalar))
|
||||
continue;
|
||||
|
||||
/* Put scalar into form (k*2^n), clamping n at the maximum hardware
|
||||
* shift.
|
||||
*/
|
||||
int64_t raw_scalar = nir_scalar_as_uint(const_scalar);
|
||||
assert(raw_scalar != 0 && "must have been optimized out");
|
||||
|
||||
uint32_t shift = MIN2(__builtin_ctz(raw_scalar), max_shift);
|
||||
int64_t k = raw_scalar >> shift;
|
||||
|
||||
/* See if the reduced scalar is from a sign extension. We must have
|
||||
* at least the format shift to avoid underflowing.
|
||||
*/
|
||||
if (k > INT32_MAX || k < INT32_MIN || shift < format_shift)
|
||||
break;
|
||||
|
||||
/* Match the constant */
|
||||
match = (struct match){
|
||||
.base = nir_scalar_chase_alu_src(base, 1 - i),
|
||||
.offset = nir_get_scalar(nir_imm_int(b, k), 0),
|
||||
.shift = shift - format_shift,
|
||||
.sign_extend = true,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nir_def *offset = match.offset.def != NULL
|
||||
? nir_channel(b, match.offset.def, match.offset.comp)
|
||||
: nir_imm_int(b, 0);
|
||||
|
||||
nir_def *new_base = nir_channel(b, match.base.def, match.base.comp);
|
||||
|
||||
nir_def *repl = NULL;
|
||||
bool has_dest = (intr->intrinsic != nir_intrinsic_store_global);
|
||||
unsigned num_components = has_dest ? intr->def.num_components : 0;
|
||||
unsigned bit_size = has_dest ? intr->def.bit_size : 0;
|
||||
|
||||
if (intr->intrinsic == nir_intrinsic_load_global) {
|
||||
repl =
|
||||
nir_load_agx(b, num_components, bit_size, new_base, offset,
|
||||
.access = nir_intrinsic_access(intr), .base = match.shift,
|
||||
.format = format, .sign_extend = match.sign_extend);
|
||||
|
||||
} else if (intr->intrinsic == nir_intrinsic_load_global_constant) {
|
||||
repl = nir_load_constant_agx(b, num_components, bit_size, new_base,
|
||||
offset, .access = nir_intrinsic_access(intr),
|
||||
.base = match.shift, .format = format,
|
||||
.sign_extend = match.sign_extend);
|
||||
} else if (intr->intrinsic == nir_intrinsic_global_atomic) {
|
||||
repl =
|
||||
nir_global_atomic_agx(b, bit_size, new_base, offset, intr->src[1].ssa,
|
||||
.atomic_op = nir_intrinsic_atomic_op(intr),
|
||||
.sign_extend = match.sign_extend);
|
||||
} else if (intr->intrinsic == nir_intrinsic_global_atomic_swap) {
|
||||
repl = nir_global_atomic_swap_agx(
|
||||
b, bit_size, new_base, offset, intr->src[1].ssa, intr->src[2].ssa,
|
||||
.atomic_op = nir_intrinsic_atomic_op(intr),
|
||||
.sign_extend = match.sign_extend);
|
||||
} else {
|
||||
nir_store_agx(b, intr->src[0].ssa, new_base, offset,
|
||||
.access = nir_intrinsic_access(intr), .base = match.shift,
|
||||
.format = format, .sign_extend = match.sign_extend);
|
||||
}
|
||||
|
||||
if (repl)
|
||||
nir_def_rewrite_uses(&intr->def, repl);
|
||||
|
||||
nir_instr_remove(&intr->instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_address(nir_shader *nir)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
/* First, clean up as much as possible. This will make fusing more effective.
|
||||
*/
|
||||
do {
|
||||
progress = false;
|
||||
NIR_PASS(progress, nir, agx_nir_cleanup_amul);
|
||||
NIR_PASS(progress, nir, nir_opt_constant_folding);
|
||||
NIR_PASS(progress, nir, nir_opt_dce);
|
||||
} while (progress);
|
||||
|
||||
/* Then, fuse as many lea as possible */
|
||||
NIR_PASS(progress, nir, agx_nir_fuse_lea);
|
||||
|
||||
/* Next, lower load/store using the lea's */
|
||||
NIR_PASS(progress, nir, nir_shader_intrinsics_pass, pass,
|
||||
nir_metadata_control_flow, NULL);
|
||||
|
||||
/* Finally, lower any leftover lea instructions back to ALU to let
|
||||
* nir_opt_algebraic simplify them from here.
|
||||
*/
|
||||
NIR_PASS(progress, nir, agx_nir_lower_lea);
|
||||
NIR_PASS(progress, nir, nir_opt_dce);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "util/macros.h"
|
||||
#include "agx_compile.h"
|
||||
#include "agx_nir.h"
|
||||
#include "glsl_types.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "shader_enums.h"
|
||||
|
||||
/*
|
||||
* Lower cull distance to discard. From the spec:
|
||||
*
|
||||
* If the cull distance for any enabled cull half-space is negative for all
|
||||
* of the vertices of the primitive under consideration, the primitive is
|
||||
* discarded.
|
||||
*
|
||||
* We don't have a direct way to read the cull distance at non-provoking
|
||||
* vertices in the fragment shader. Instead, we interpolate the quantity:
|
||||
*
|
||||
* cull distance >= 0.0 ? 1.0 : 0.0
|
||||
*
|
||||
* Then, the discard condition is equivalent to:
|
||||
*
|
||||
* "quantity is zero for all vertices of the primitive"
|
||||
*
|
||||
* which by linearity is equivalent to:
|
||||
*
|
||||
* quantity is zero somewhere in the primitive and quantity has zero
|
||||
* first-order screen space derivatives.
|
||||
*
|
||||
* which we can determine with ease in the fragment shader.
|
||||
*/
|
||||
|
||||
static bool
|
||||
lower_write(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
if (intr->intrinsic != nir_intrinsic_store_output)
|
||||
return false;
|
||||
|
||||
nir_io_semantics sem = nir_intrinsic_io_semantics(intr);
|
||||
if (sem.location != VARYING_SLOT_CLIP_DIST0 &&
|
||||
sem.location != VARYING_SLOT_CLIP_DIST1)
|
||||
return false;
|
||||
|
||||
signed loc = sem.location + nir_src_as_uint(intr->src[1]);
|
||||
unsigned total_component =
|
||||
(loc - VARYING_SLOT_CLIP_DIST0) * 4 + nir_intrinsic_component(intr);
|
||||
|
||||
unsigned base = b->shader->info.clip_distance_array_size;
|
||||
if (total_component < base)
|
||||
return false;
|
||||
|
||||
unsigned component = total_component - base;
|
||||
if (component >= b->shader->info.cull_distance_array_size)
|
||||
return false;
|
||||
|
||||
assert(nir_src_num_components(intr->src[0]) == 1 && "must be scalarized");
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
nir_def *offs = nir_imm_int(b, component / 4);
|
||||
nir_def *v = nir_b2f32(b, nir_fge_imm(b, intr->src[0].ssa, 0.0));
|
||||
|
||||
nir_store_output(b, v, offs, .component = component % 4,
|
||||
.src_type = nir_type_float32,
|
||||
.io_semantics.location = VARYING_SLOT_CULL_PRIMITIVE,
|
||||
.io_semantics.num_slots = 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_cull_distance_vs(nir_shader *s)
|
||||
{
|
||||
assert(s->info.stage == MESA_SHADER_VERTEX ||
|
||||
s->info.stage == MESA_SHADER_TESS_EVAL);
|
||||
|
||||
nir_shader_intrinsics_pass(s, lower_write, nir_metadata_control_flow, NULL);
|
||||
|
||||
s->info.outputs_written |=
|
||||
BITFIELD64_RANGE(VARYING_SLOT_CULL_PRIMITIVE,
|
||||
DIV_ROUND_UP(s->info.cull_distance_array_size, 4));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_cull_distance_fs(nir_shader *s, unsigned nr_distances)
|
||||
{
|
||||
assert(s->info.stage == MESA_SHADER_FRAGMENT);
|
||||
assert(nr_distances > 0);
|
||||
|
||||
nir_builder b_ =
|
||||
nir_builder_at(nir_before_impl(nir_shader_get_entrypoint(s)));
|
||||
nir_builder *b = &b_;
|
||||
|
||||
/* Test each half-space */
|
||||
nir_def *culled = nir_imm_false(b);
|
||||
|
||||
for (unsigned i = 0; i < nr_distances; ++i) {
|
||||
/* Load the coefficient vector for this half-space. Imaginapple
|
||||
* partial derivatives and the value somewhere.
|
||||
*/
|
||||
nir_def *cf = nir_load_coefficients_agx(
|
||||
b, nir_imm_int(b, 0), .component = i & 3,
|
||||
.io_semantics.location = VARYING_SLOT_CULL_PRIMITIVE + (i / 4),
|
||||
.io_semantics.num_slots = nr_distances / 4,
|
||||
.interp_mode = INTERP_MODE_NOPERSPECTIVE);
|
||||
|
||||
/* If the coefficients are identically zero, then the quantity is
|
||||
* zero across the primtive <==> cull distance is negative across the
|
||||
* primitive <==> the primitive is culled.
|
||||
*/
|
||||
culled = nir_ior(b, culled, nir_ball(b, nir_feq_imm(b, cf, 0)));
|
||||
}
|
||||
|
||||
/* Emulate primitive culling by discarding fragments */
|
||||
nir_demote_if(b, culled);
|
||||
|
||||
s->info.inputs_read |= BITFIELD64_RANGE(VARYING_SLOT_CULL_PRIMITIVE,
|
||||
DIV_ROUND_UP(nr_distances, 4));
|
||||
|
||||
s->info.fs.uses_discard = true;
|
||||
return nir_progress(true, b->impl, nir_metadata_control_flow);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
|
||||
#define ALL_SAMPLES 0xFF
|
||||
#define BASE_Z 1
|
||||
#define BASE_S 2
|
||||
|
||||
static bool
|
||||
lower_zs_emit(nir_block *block, bool force_early_z)
|
||||
{
|
||||
nir_intrinsic_instr *zs_emit = NULL;
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_instr_reverse_safe(instr, block) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
if (intr->intrinsic != nir_intrinsic_store_output)
|
||||
continue;
|
||||
|
||||
nir_io_semantics sem = nir_intrinsic_io_semantics(intr);
|
||||
if (sem.location != FRAG_RESULT_DEPTH &&
|
||||
sem.location != FRAG_RESULT_STENCIL)
|
||||
continue;
|
||||
|
||||
/* If early-Z is forced, z/s writes are a no-op (and will cause problems
|
||||
* later in the compile). Piglit early-z tests this. Just remove the
|
||||
* offending writes.
|
||||
*/
|
||||
if (force_early_z) {
|
||||
nir_instr_remove(instr);
|
||||
progress = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
nir_builder b = nir_builder_at(nir_before_instr(instr));
|
||||
|
||||
nir_def *value = intr->src[0].ssa;
|
||||
bool z = (sem.location == FRAG_RESULT_DEPTH);
|
||||
|
||||
unsigned src_idx = z ? 1 : 2;
|
||||
unsigned base = z ? BASE_Z : BASE_S;
|
||||
|
||||
/* In the hw, depth is 32-bit but stencil is 16-bit. Instruction
|
||||
* selection checks this, so emit the conversion now.
|
||||
*/
|
||||
if (z)
|
||||
value = nir_f2f32(&b, value);
|
||||
else
|
||||
value = nir_u2u16(&b, value);
|
||||
|
||||
if (zs_emit == NULL) {
|
||||
/* Multisampling will get lowered later if needed, default to
|
||||
* broadcast
|
||||
*/
|
||||
nir_def *sample_mask = nir_imm_intN_t(&b, ALL_SAMPLES, 16);
|
||||
zs_emit =
|
||||
nir_store_zs_agx(&b, sample_mask, nir_undef(&b, 1, 32) /* depth */,
|
||||
nir_undef(&b, 1, 16) /* stencil */);
|
||||
}
|
||||
|
||||
assert((nir_intrinsic_base(zs_emit) & base) == 0 &&
|
||||
"each of depth/stencil may only be written once");
|
||||
|
||||
nir_src_rewrite(&zs_emit->src[src_idx], value);
|
||||
nir_intrinsic_set_base(zs_emit, nir_intrinsic_base(zs_emit) | base);
|
||||
|
||||
nir_instr_remove(instr);
|
||||
progress = true;
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_discard(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
if (intr->intrinsic != nir_intrinsic_demote &&
|
||||
intr->intrinsic != nir_intrinsic_demote_if)
|
||||
return false;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
nir_def *all_samples = nir_imm_intN_t(b, ALL_SAMPLES, 16);
|
||||
nir_def *no_samples = nir_imm_intN_t(b, 0, 16);
|
||||
nir_def *killed_samples = all_samples;
|
||||
|
||||
if (intr->intrinsic == nir_intrinsic_demote_if)
|
||||
killed_samples = nir_bcsel(b, intr->src[0].ssa, all_samples, no_samples);
|
||||
|
||||
/* This will get lowered later as needed */
|
||||
nir_demote_samples(b, killed_samples);
|
||||
nir_instr_remove(&intr->instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_nir_lower_discard(nir_shader *s)
|
||||
{
|
||||
if (!s->info.fs.uses_discard)
|
||||
return false;
|
||||
|
||||
return nir_shader_intrinsics_pass(s, lower_discard,
|
||||
nir_metadata_control_flow, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_nir_lower_zs_emit(nir_shader *s)
|
||||
{
|
||||
/* If depth/stencil isn't written, there's nothing to lower */
|
||||
if (!(s->info.outputs_written & (BITFIELD64_BIT(FRAG_RESULT_STENCIL) |
|
||||
BITFIELD64_BIT(FRAG_RESULT_DEPTH))))
|
||||
return false;
|
||||
|
||||
bool any_progress = false;
|
||||
|
||||
nir_foreach_function_impl(impl, s) {
|
||||
bool progress = false;
|
||||
|
||||
nir_foreach_block(block, impl) {
|
||||
progress |= lower_zs_emit(block, s->info.fs.early_fragment_tests);
|
||||
}
|
||||
|
||||
any_progress |= nir_progress(progress, impl, nir_metadata_control_flow);
|
||||
}
|
||||
|
||||
return any_progress;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_discard_zs_emit(nir_shader *s)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
/* Lower depth/stencil writes before discard so the interaction works */
|
||||
progress |= agx_nir_lower_zs_emit(s);
|
||||
progress |= agx_nir_lower_discard(s);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2024 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_nir.h"
|
||||
|
||||
/*
|
||||
* AGX generally flushes FP32 denorms. However, the min/max instructions do not
|
||||
* as they are implemented with cmpsel. We need to flush the results of fp32
|
||||
* min/max for correctness. Doing so naively will generate redundant flushes, so
|
||||
* this pass tries to be clever and elide flushes when possible.
|
||||
*
|
||||
* This pass is still pretty simple, it doesn't see through phis or bcsels yet.
|
||||
*/
|
||||
static bool
|
||||
could_be_denorm(nir_scalar s)
|
||||
{
|
||||
/* Constants can be denorms only if they are denorms. */
|
||||
if (nir_scalar_is_const(s)) {
|
||||
return fpclassify(nir_scalar_as_float(s)) == FP_SUBNORMAL;
|
||||
}
|
||||
|
||||
/* Floating-point instructions flush denormals, so ALU results can only be
|
||||
* denormal if they are not from a float instruction. Crucially fmin/fmax
|
||||
* flushes in NIR, so this pass handles chains of fmin/fmax properly.
|
||||
*/
|
||||
if (nir_scalar_is_alu(s)) {
|
||||
nir_op op = nir_scalar_alu_op(s);
|
||||
nir_alu_type T = nir_op_infos[op].output_type;
|
||||
|
||||
return nir_alu_type_get_base_type(T) != nir_type_float &&
|
||||
op != nir_op_fmin_agx && op != nir_op_fmax_agx;
|
||||
}
|
||||
|
||||
/* Otherwise, assume it could be denormal (say, loading from a buffer). */
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower(nir_builder *b, nir_alu_instr *alu, void *data)
|
||||
{
|
||||
if ((alu->op != nir_op_fmin && alu->op != nir_op_fmax) ||
|
||||
(alu->def.bit_size != 32))
|
||||
return false;
|
||||
|
||||
/* Lower the op, we'll fix up the denorms right after. */
|
||||
if (alu->op == nir_op_fmax)
|
||||
alu->op = nir_op_fmax_agx;
|
||||
else
|
||||
alu->op = nir_op_fmin_agx;
|
||||
|
||||
/* We need to canonicalize the result if the output could be a denorm. That
|
||||
* occurs only when one of the sources could be a denorm. Check each source.
|
||||
* Swizzles don't affect denormalness so we can grab the def directly.
|
||||
*/
|
||||
nir_scalar scalar = nir_get_scalar(&alu->def, 0);
|
||||
nir_scalar src0 = nir_scalar_chase_alu_src(scalar, 0);
|
||||
nir_scalar src1 = nir_scalar_chase_alu_src(scalar, 1);
|
||||
|
||||
if (could_be_denorm(src0) || could_be_denorm(src1)) {
|
||||
b->cursor = nir_after_instr(&alu->instr);
|
||||
b->fp_math_ctrl = nir_fp_no_fast_math;
|
||||
|
||||
nir_def *canonicalized = nir_fadd_imm(b, &alu->def, -0.0);
|
||||
nir_def_rewrite_uses_after(&alu->def, canonicalized);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_fminmax(nir_shader *s)
|
||||
{
|
||||
return nir_shader_alu_pass(s, lower, nir_metadata_control_flow, NULL);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Fragment shaders with side effects require special handling to ensure the
|
||||
* side effects execute as intended. By default, they require late depth
|
||||
* testing, to ensure the side effects happen even for killed pixels. To handle,
|
||||
* the driver inserts a dummy `gl_FragDepth = gl_Position.z` in shaders that
|
||||
* don't otherwise write their depth, forcing a late depth test.
|
||||
*
|
||||
* For side effects with force early testing forced, the sample mask is written
|
||||
* at the *beginning* of the shader.
|
||||
*/
|
||||
|
||||
#define ALL_SAMPLES (0xFF)
|
||||
|
||||
static void
|
||||
insert_z_write(nir_builder *b)
|
||||
{
|
||||
/* When forcing depth test to NEVER, force Z to NaN. This works around the
|
||||
* hardware discarding depth=NEVER.
|
||||
*
|
||||
* TODO: Check if Apple has a better way of handling this.
|
||||
*/
|
||||
nir_def *bias = nir_bcsel(b, nir_ine_imm(b, nir_load_depth_never_agx(b), 0),
|
||||
nir_imm_float(b, NAN), nir_imm_float(b, -0.0));
|
||||
|
||||
nir_def *z = nir_fadd(b, bias, nir_load_frag_coord_z(b));
|
||||
|
||||
nir_store_output(b, z, nir_imm_int(b, 0),
|
||||
.io_semantics.location = FRAG_RESULT_DEPTH,
|
||||
.src_type = nir_type_float32);
|
||||
|
||||
b->shader->info.outputs_written |= BITFIELD64_BIT(FRAG_RESULT_DEPTH);
|
||||
}
|
||||
|
||||
static bool
|
||||
pass(struct nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
if (intr->intrinsic != nir_intrinsic_store_output)
|
||||
return false;
|
||||
|
||||
/* Only lower once */
|
||||
bool *done = data;
|
||||
if (*done)
|
||||
return false;
|
||||
*done = true;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
insert_z_write(b);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_frag_sidefx(nir_shader *s)
|
||||
{
|
||||
assert(s->info.stage == MESA_SHADER_FRAGMENT);
|
||||
|
||||
/* If there are no side effects, there's nothing to lower */
|
||||
if (!s->info.writes_memory)
|
||||
return false;
|
||||
|
||||
/* Lower writes from helper invocations with the common pass. The hardware
|
||||
* will predicate simple writes for us, but that fails with sample shading so
|
||||
* we do that in software too.
|
||||
*/
|
||||
NIR_PASS(_, s, nir_lower_helper_writes, s->info.fs.uses_sample_shading);
|
||||
|
||||
bool writes_zs =
|
||||
s->info.outputs_written &
|
||||
(BITFIELD64_BIT(FRAG_RESULT_STENCIL) | BITFIELD64_BIT(FRAG_RESULT_DEPTH));
|
||||
|
||||
/* If the shader wants early fragment tests, the sample mask lowering pass
|
||||
* will trigger an early test at the beginning of the shader. This lets us
|
||||
* use a Passthrough punch type, instead of Opaque which may result in the
|
||||
* shader getting skipped incorrectly and then the side effects not kicking
|
||||
* in. But this happens there to avoid it happening twice with a discard.
|
||||
*/
|
||||
if (s->info.fs.early_fragment_tests)
|
||||
return false;
|
||||
|
||||
/* If depth/stencil feedback is already used, we're done */
|
||||
if (writes_zs)
|
||||
return false;
|
||||
|
||||
bool done = false;
|
||||
nir_shader_intrinsics_pass(s, pass, nir_metadata_control_flow, &done);
|
||||
|
||||
/* If there's no render targets written, just put the write at the end */
|
||||
if (!done) {
|
||||
nir_function_impl *impl = nir_shader_get_entrypoint(s);
|
||||
nir_builder b = nir_builder_at(nir_after_impl(impl));
|
||||
|
||||
insert_z_write(&b);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/shader_enums.h"
|
||||
#include "agx_compile.h"
|
||||
#include "nir.h"
|
||||
#include "nir_builder.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_intrinsics_indices.h"
|
||||
|
||||
/*
|
||||
* In AGX, the values of fragment shader inputs are represented as coefficient
|
||||
* vectors <A, B, C>, which are dotted with <x, y, 1> to perform interpolation.
|
||||
* x and y are relative to the tile. In other words, A and B are the
|
||||
* screen-space partial derivatives of the input, and C is the value at the
|
||||
* corner of the tile.
|
||||
*
|
||||
* For some interpolation modes, the dot product happens in the iterator
|
||||
* hardware. Other modes are implemented in this file, by lowering to math on
|
||||
* the coefficient vectors.
|
||||
*/
|
||||
|
||||
/* XXX: It's not clear what this is for, but seems necessary */
|
||||
static nir_def *
|
||||
cf_valid(nir_builder *b, nir_def *cf)
|
||||
{
|
||||
nir_def *bit = nir_ieq_imm(b, nir_iand_imm(b, nir_channel(b, cf, 0), 1), 0);
|
||||
|
||||
/* XXX: Apple's compiler actually checks that the significand is nonzero and
|
||||
* the exponent is 0 or 1. This is probably a typo -- it doesn't make any
|
||||
* logical sense. Presumably they just meant to check for denorms, so let's
|
||||
* do that. Either way the tests pass.
|
||||
*/
|
||||
nir_def *cf01 = nir_trim_vector(b, cf, 2);
|
||||
return nir_ior(b, bit, nir_fisnormal(b, cf01));
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
interpolate_at_offset(nir_builder *b, nir_def *cf, nir_def *offset,
|
||||
bool perspective)
|
||||
{
|
||||
/* Get the coordinate of the pixel within the tile */
|
||||
nir_def *pixel_coords = nir_load_pixel_coord(b);
|
||||
nir_def *tile_offs = nir_umod_imm(b, pixel_coords, 32);
|
||||
|
||||
/* Convert to float, getting the center of the pixel */
|
||||
nir_def *center = nir_fadd_imm(b, nir_u2f32(b, tile_offs), 0.5);
|
||||
|
||||
/* Calculate the location to interpolate. offset is defined relative to the
|
||||
* center of the pixel and is a float.
|
||||
*/
|
||||
nir_def *pos = nir_fadd(b, center, nir_f2f32(b, offset));
|
||||
|
||||
/* Interpolate with the given coefficients */
|
||||
nir_def *interp = nir_ffma_weak(b, nir_channel(b, pos, 1), nir_channel(b, cf, 1),
|
||||
nir_channel(b, cf, 2));
|
||||
|
||||
interp = nir_ffma_weak(b, nir_channel(b, pos, 0), nir_channel(b, cf, 0), interp);
|
||||
|
||||
/* Divide by RHW. This load will be lowered recursively. */
|
||||
if (perspective) {
|
||||
nir_def *bary = nir_load_barycentric_at_offset(
|
||||
b, 32, offset, .interp_mode = INTERP_MODE_NOPERSPECTIVE);
|
||||
|
||||
nir_def *rhw = nir_load_interpolated_input(
|
||||
b, 1, 32, bary, nir_imm_int(b, 0), .component = 3,
|
||||
.io_semantics = {
|
||||
.location = VARYING_SLOT_POS,
|
||||
.num_slots = 1,
|
||||
});
|
||||
|
||||
interp = nir_fdiv(b, interp, rhw);
|
||||
}
|
||||
|
||||
/* Replace invalid interpolations with the constant channel */
|
||||
return nir_bcsel(b, cf_valid(b, cf), interp, nir_channel(b, cf, 2));
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
interpolate_flat(nir_builder *b, nir_def *coefficients)
|
||||
{
|
||||
/* Same value anywhere, so just take the constant (affine) component. For
|
||||
* triangle fans with the first provoking vertex, the CF layout is slightly
|
||||
* different. I am unsure why, but Apple does the same and the bcsel is
|
||||
* required for corrctness.
|
||||
*/
|
||||
return nir_bcsel(b, nir_load_is_first_fan_agx(b),
|
||||
nir_channel(b, coefficients, 1),
|
||||
nir_channel(b, coefficients, 2));
|
||||
}
|
||||
|
||||
static enum glsl_interp_mode
|
||||
interp_mode_for_load(nir_intrinsic_instr *load)
|
||||
{
|
||||
if (load->intrinsic == nir_intrinsic_load_input)
|
||||
return INTERP_MODE_FLAT;
|
||||
else
|
||||
return nir_intrinsic_interp_mode(nir_src_as_intrinsic(load->src[0]));
|
||||
}
|
||||
|
||||
static bool
|
||||
needs_lower(const nir_instr *instr, UNUSED const void *_)
|
||||
{
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
return false;
|
||||
|
||||
const nir_intrinsic_instr *load = nir_instr_as_intrinsic(instr);
|
||||
|
||||
/* at_offset barycentrics need to be lowered */
|
||||
if (load->intrinsic == nir_intrinsic_load_interpolated_input) {
|
||||
return (nir_src_as_intrinsic(load->src[0])->intrinsic ==
|
||||
nir_intrinsic_load_barycentric_at_offset);
|
||||
}
|
||||
|
||||
/* Flat shading always lowered */
|
||||
return (load->intrinsic == nir_intrinsic_load_input);
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
interpolate_channel(nir_builder *b, nir_intrinsic_instr *load, unsigned channel)
|
||||
{
|
||||
nir_def *coefficients = nir_load_coefficients_agx(
|
||||
b, nir_get_io_offset_src(load)->ssa,
|
||||
.component = nir_intrinsic_component(load) + channel,
|
||||
.interp_mode = interp_mode_for_load(load),
|
||||
.io_semantics = nir_intrinsic_io_semantics(load));
|
||||
|
||||
if (load->intrinsic == nir_intrinsic_load_input) {
|
||||
assert(load->def.bit_size == 32);
|
||||
|
||||
if (nir_intrinsic_io_semantics(load).location == VARYING_SLOT_LAYER)
|
||||
return nir_load_layer_id(b);
|
||||
else
|
||||
return interpolate_flat(b, coefficients);
|
||||
} else {
|
||||
nir_intrinsic_instr *bary = nir_src_as_intrinsic(load->src[0]);
|
||||
|
||||
nir_def *interp = interpolate_at_offset(
|
||||
b, coefficients, bary->src[0].ssa,
|
||||
nir_intrinsic_interp_mode(bary) != INTERP_MODE_NOPERSPECTIVE);
|
||||
|
||||
return nir_f2fN(b, interp, load->def.bit_size);
|
||||
}
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
lower(nir_builder *b, nir_instr *instr, void *data)
|
||||
{
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
|
||||
/* Each component is loaded separated */
|
||||
nir_def *values[NIR_MAX_VEC_COMPONENTS] = {NULL};
|
||||
for (unsigned i = 0; i < intr->def.num_components; ++i) {
|
||||
values[i] = interpolate_channel(b, intr, i);
|
||||
}
|
||||
|
||||
return nir_vec(b, values, intr->def.num_components);
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_interpolation(nir_shader *s)
|
||||
{
|
||||
assert(s->info.stage == MESA_SHADER_FRAGMENT);
|
||||
|
||||
return nir_shader_lower_instructions(s, needs_lower, lower, NULL);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/list.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "nir.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_intrinsics.h"
|
||||
|
||||
/*
|
||||
* sample_mask takes two bitmasks as arguments, TARGET and LIVE. Each bit refers
|
||||
* to an indexed sample. Roughly, the instruction does:
|
||||
*
|
||||
* foreach sample in TARGET {
|
||||
* if sample in LIVE {
|
||||
* run depth/stencil/occlusion test/update
|
||||
* } else {
|
||||
* kill sample
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* As a special case, TARGET may be set to all-1s (~0) to refer to all samples
|
||||
* regardless of the framebuffer sample count.
|
||||
*
|
||||
* For example, to discard an entire pixel unconditionally, we could run:
|
||||
*
|
||||
* sample_mask ~0, 0
|
||||
*
|
||||
* sample_mask must follow these rules:
|
||||
*
|
||||
* 1. All sample_mask instructions affecting a sample must execute before a
|
||||
* local_store_pixel instruction targeting that sample. This ensures that
|
||||
* nothing is written for discarded samples (whether discarded in shader or
|
||||
* due to a failed depth/stencil test).
|
||||
*
|
||||
* 2. If sample_mask is used anywhere in a shader, then on every execution path,
|
||||
* every sample must be killed or else run depth/stencil tests exactly ONCE.
|
||||
*
|
||||
* 3. If a sample is killed, future sample_mask instructions have
|
||||
* no effect on that sample. The following code sequence correctly implements
|
||||
* a conditional discard (if there are no other sample_mask instructions in
|
||||
* the shader):
|
||||
*
|
||||
* sample_mask discarded, 0
|
||||
* sample_mask ~0, ~0
|
||||
*
|
||||
* but this sequence is incorrect:
|
||||
*
|
||||
* sample_mask ~0, ~discarded
|
||||
* sample_mask ~0, ~0 <-- incorrect: depth/stencil tests run twice
|
||||
*
|
||||
* 4. Conversely, if a sample is tested, future sample_mask instructions may not
|
||||
* discard that sample. The following code is invalid:
|
||||
*
|
||||
* sample_mask ~0, ~0
|
||||
* sample_mask discarded, 0
|
||||
* st_tile
|
||||
*
|
||||
* To implement the semantic of "force early tests with discard", manual
|
||||
* colour masking must be used. It's a weird case, but CTS does it.
|
||||
*
|
||||
* 5. zs_emit may be used in the shader exactly once to trigger tests.
|
||||
* sample_mask with 0 may be used to discard early.
|
||||
*
|
||||
* This pass lowers demote_samples to sample_mask instructions satisfying these
|
||||
* rules. Other passes should not generate sample_mask instructions, as there
|
||||
* are too many footguns.
|
||||
*/
|
||||
|
||||
#define ALL_SAMPLES (0xFF)
|
||||
#define BASE_Z 1
|
||||
#define BASE_S 2
|
||||
|
||||
static bool
|
||||
lower_discard_to_sample_mask_0(nir_builder *b, nir_intrinsic_instr *intr,
|
||||
UNUSED void *data)
|
||||
{
|
||||
if (intr->intrinsic != nir_intrinsic_demote_samples)
|
||||
return false;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
nir_sample_mask_agx(b, nir_u2u16(b, intr->src[0].ssa),
|
||||
nir_imm_intN_t(b, 0, 16));
|
||||
nir_instr_remove(&intr->instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_intrinsic_instr *
|
||||
last_discard_in_block(nir_block *block)
|
||||
{
|
||||
nir_foreach_instr_reverse(instr, block) {
|
||||
if (instr->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
if (intr->intrinsic == nir_intrinsic_demote_samples)
|
||||
return intr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
cf_node_contains_discard(nir_cf_node *node)
|
||||
{
|
||||
nir_foreach_block_in_cf_node(block, node) {
|
||||
if (last_discard_in_block(block))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to run depth/stencil tests as early as possible, but we have to
|
||||
* wait until after the last discard. We find the last discard and
|
||||
* execute depth/stencil tests in the first unconditional block after (if
|
||||
* in conditional control flow), or fuse depth/stencil tests into the
|
||||
* sample instruction (if in unconditional control flow).
|
||||
*
|
||||
* To do so, we walk the root control flow list backwards, looking for the
|
||||
* earliest unconditionally executed instruction after all discard.
|
||||
*/
|
||||
static void
|
||||
run_tests_after_last_discard(nir_builder *b)
|
||||
{
|
||||
foreach_list_typed_reverse(nir_cf_node, node, node, &b->impl->body) {
|
||||
if (node->type == nir_cf_node_block) {
|
||||
/* Unconditionally executed block */
|
||||
nir_block *block = nir_cf_node_as_block(node);
|
||||
nir_intrinsic_instr *intr = last_discard_in_block(block);
|
||||
|
||||
if (intr) {
|
||||
/* Last discard is executed unconditionally, so fuse tests:
|
||||
*
|
||||
* sample_mask (testing | killed), ~killed
|
||||
*
|
||||
* When testing, this is `sample_mask ~0, ~killed` which kills the
|
||||
* kill set and triggers tests on the rest.
|
||||
*
|
||||
* When not testing, this is `sample_mask killed, ~killed` which is
|
||||
* equivalent to `sample_mask killed, 0`, killing without testing.
|
||||
*/
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
nir_def *all_samples = nir_imm_intN_t(b, ALL_SAMPLES, 16);
|
||||
nir_def *killed = nir_u2u16(b, intr->src[0].ssa);
|
||||
nir_def *live = nir_ixor(b, killed, all_samples);
|
||||
|
||||
nir_def *testing = nir_load_shader_part_tests_zs_agx(b);
|
||||
nir_def *affected = nir_ior(b, testing, killed);
|
||||
|
||||
nir_sample_mask_agx(b, affected, live);
|
||||
nir_instr_remove(&intr->instr);
|
||||
return;
|
||||
} else {
|
||||
/* Set cursor for insertion due to a preceding conditionally
|
||||
* executed discard.
|
||||
*/
|
||||
b->cursor = nir_before_block_after_phis(block);
|
||||
}
|
||||
} else if (cf_node_contains_discard(node)) {
|
||||
/* Conditionally executed block contains the last discard. Test
|
||||
* depth/stencil for remaining samples in unconditional code after.
|
||||
*
|
||||
* If we're not testing, this turns into sample_mask(0, ~0) which is a
|
||||
* no-op.
|
||||
*/
|
||||
nir_sample_mask_agx(b, nir_load_shader_part_tests_zs_agx(b),
|
||||
nir_imm_intN_t(b, ALL_SAMPLES, 16));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
run_tests_at_start(nir_shader *shader)
|
||||
{
|
||||
nir_function_impl *impl = nir_shader_get_entrypoint(shader);
|
||||
nir_builder b = nir_builder_at(nir_before_impl(impl));
|
||||
|
||||
nir_sample_mask_agx(&b, nir_imm_intN_t(&b, ALL_SAMPLES, 16),
|
||||
nir_imm_intN_t(&b, ALL_SAMPLES, 16));
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_sample_mask(nir_shader *shader)
|
||||
{
|
||||
nir_function_impl *impl = nir_shader_get_entrypoint(shader);
|
||||
|
||||
bool writes_zs =
|
||||
shader->info.outputs_written &
|
||||
(BITFIELD64_BIT(FRAG_RESULT_STENCIL) | BITFIELD64_BIT(FRAG_RESULT_DEPTH));
|
||||
|
||||
if (shader->info.fs.early_fragment_tests) {
|
||||
/* run tests early, if we need testing */
|
||||
if (shader->info.fs.uses_discard || writes_zs ||
|
||||
shader->info.writes_memory) {
|
||||
|
||||
run_tests_at_start(shader);
|
||||
}
|
||||
} else if (shader->info.fs.uses_discard) {
|
||||
/* If we have zs_emit, the tests will be triggered by zs_emit, otherwise
|
||||
* we need to trigger tests explicitly. Allow sample_mask with zs_emit.
|
||||
*/
|
||||
if (!writes_zs) {
|
||||
nir_builder b = nir_builder_create(impl);
|
||||
|
||||
/* run tests late */
|
||||
run_tests_after_last_discard(&b);
|
||||
}
|
||||
} else {
|
||||
/* regular shaders that don't use discard have nothing to lower */
|
||||
return nir_no_progress(impl);
|
||||
}
|
||||
|
||||
nir_progress(true, impl, nir_metadata_control_flow);
|
||||
|
||||
nir_shader_intrinsics_pass(shader, lower_discard_to_sample_mask_0,
|
||||
nir_metadata_control_flow, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Local memory instructions require 16-bit offsets, so we add conversions. */
|
||||
static bool
|
||||
pass(struct nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
nir_src *offset = nir_get_io_offset_src(intr);
|
||||
if (!nir_is_shared_access(intr) || nir_src_bit_size(*offset) == 16)
|
||||
return false;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
nir_src_rewrite(offset, nir_u2u16(b, offset->ssa));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_shared_bitsize(nir_shader *shader)
|
||||
{
|
||||
return nir_shader_intrinsics_pass(shader, pass, nir_metadata_control_flow,
|
||||
NULL);
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "util/list.h"
|
||||
#include "agx_nir.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_intrinsics_indices.h"
|
||||
#include "nir_opcodes.h"
|
||||
|
||||
/* XXX: cribbed from nak, move to common */
|
||||
static nir_def *
|
||||
nir_udiv_round_up(nir_builder *b, nir_def *n, nir_def *d)
|
||||
{
|
||||
return nir_udiv(b, nir_iadd(b, n, nir_iadd_imm(b, d, -1)), d);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower(nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
case nir_intrinsic_quad_vote_any: {
|
||||
nir_def *ballot = nir_quad_ballot_agx(b, 16, intr->src[0].ssa);
|
||||
nir_def_rewrite_uses(&intr->def, nir_ine_imm(b, ballot, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_quad_vote_all: {
|
||||
nir_def *ballot =
|
||||
nir_quad_ballot_agx(b, 16, nir_inot(b, intr->src[0].ssa));
|
||||
nir_def_rewrite_uses(&intr->def, nir_ieq_imm(b, ballot, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_elect: {
|
||||
nir_def *active_id = nir_load_active_subgroup_invocation_agx(b, 16);
|
||||
nir_def_rewrite_uses(&intr->def, nir_ieq_imm(b, active_id, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_first_invocation: {
|
||||
nir_def *active_id = nir_load_active_subgroup_invocation_agx(b, 16);
|
||||
nir_def *is_first = nir_ieq_imm(b, active_id, 0);
|
||||
nir_def *first_bit = nir_ballot(b, 1, 32, is_first);
|
||||
nir_def_rewrite_uses(&intr->def, nir_ufind_msb(b, first_bit));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_last_invocation: {
|
||||
nir_def *active_mask = nir_ballot(b, 1, 32, nir_imm_true(b));
|
||||
nir_def_rewrite_uses(&intr->def, nir_ufind_msb(b, active_mask));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_vote_ieq:
|
||||
case nir_intrinsic_vote_feq: {
|
||||
/* The common lowering does:
|
||||
*
|
||||
* vote_all(x == read_first(x))
|
||||
*
|
||||
* This is not optimal for AGX, since we have ufind_msb but not ctz, so
|
||||
* it's cheaper to read the last invocation than the first. So we do:
|
||||
*
|
||||
* vote_all(x == read_last(x))
|
||||
*
|
||||
* implemented with lowered instructions as
|
||||
*
|
||||
* ballot(x != broadcast(x, ffs(ballot(true)))) == 0
|
||||
*/
|
||||
nir_def *active_mask = nir_ballot(b, 1, 32, nir_imm_true(b));
|
||||
nir_def *active_bit = nir_ufind_msb(b, active_mask);
|
||||
nir_def *other = nir_read_invocation(b, intr->src[0].ssa, active_bit);
|
||||
nir_def *is_ne;
|
||||
|
||||
if (intr->intrinsic == nir_intrinsic_vote_feq) {
|
||||
is_ne = nir_fneu(b, other, intr->src[0].ssa);
|
||||
} else {
|
||||
is_ne = nir_ine(b, other, intr->src[0].ssa);
|
||||
}
|
||||
|
||||
nir_def *ballot = nir_ballot(b, 1, 32, is_ne);
|
||||
nir_def_rewrite_uses(&intr->def, nir_ieq_imm(b, ballot, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_load_num_subgroups: {
|
||||
nir_def *workgroup_size = nir_load_workgroup_size(b);
|
||||
workgroup_size = nir_imul(b,
|
||||
nir_imul(b, nir_channel(b, workgroup_size, 0),
|
||||
nir_channel(b, workgroup_size, 1)),
|
||||
nir_channel(b, workgroup_size, 2));
|
||||
nir_def *subgroup_size = nir_imm_int(b, 32);
|
||||
nir_def *num_subgroups =
|
||||
nir_udiv_round_up(b, workgroup_size, subgroup_size);
|
||||
nir_def_rewrite_uses(&intr->def, num_subgroups);
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_shuffle: {
|
||||
nir_def *data = intr->src[0].ssa;
|
||||
nir_def *target = intr->src[1].ssa;
|
||||
|
||||
/* The hardware shuffle instruction chooses a single index within the
|
||||
* target quad to shuffle each source quad with. Consequently, the low
|
||||
* 2-bits of shuffle indices should not be quad divergent. To implement
|
||||
* arbitrary shuffle, pull each low 2-bits index in the quad separately.
|
||||
*/
|
||||
nir_def *quad_start = nir_iand_imm(b, target, 0x1c);
|
||||
nir_def *result = NULL;
|
||||
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
nir_def *target_i = nir_iadd_imm(b, quad_start, i);
|
||||
nir_def *shuf = nir_read_invocation(b, data, target_i);
|
||||
|
||||
if (result)
|
||||
result = nir_bcsel(b, nir_ieq(b, target, target_i), shuf, result);
|
||||
else
|
||||
result = shuf;
|
||||
}
|
||||
|
||||
nir_def_rewrite_uses(&intr->def, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_inclusive_scan: {
|
||||
/* If we got here, we support the corresponding exclusive scan in
|
||||
* hardware, so just handle the last element.
|
||||
*/
|
||||
nir_op red_op = nir_intrinsic_reduction_op(intr);
|
||||
nir_def *data = intr->src[0].ssa;
|
||||
|
||||
b->cursor = nir_after_instr(&intr->instr);
|
||||
intr->intrinsic = nir_intrinsic_exclusive_scan;
|
||||
nir_def *accum = nir_build_alu2(b, red_op, data, &intr->def);
|
||||
nir_def_rewrite_uses_after(&intr->def, accum);
|
||||
return true;
|
||||
}
|
||||
|
||||
case nir_intrinsic_ballot: {
|
||||
/* Optimize popcount(ballot(true)) to load_active_subgroup_count_agx() */
|
||||
if (!nir_src_is_const(intr->src[0]) || !nir_src_as_bool(intr->src[0]) ||
|
||||
!list_is_singular(&intr->def.uses))
|
||||
return false;
|
||||
|
||||
nir_src *use = list_first_entry(&intr->def.uses, nir_src, use_link);
|
||||
nir_instr *parent = nir_src_use_instr(use);
|
||||
if (parent->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
nir_alu_instr *alu = nir_instr_as_alu(parent);
|
||||
if (alu->op != nir_op_bit_count)
|
||||
return false;
|
||||
|
||||
nir_def_rewrite_uses(&alu->def,
|
||||
nir_load_active_subgroup_count_agx(b, 32));
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_subgroup_filter(const nir_intrinsic_instr *intr, UNUSED const void *data)
|
||||
{
|
||||
/* Use default behaviour for everything but scans */
|
||||
if (intr->intrinsic != nir_intrinsic_exclusive_scan &&
|
||||
intr->intrinsic != nir_intrinsic_inclusive_scan &&
|
||||
intr->intrinsic != nir_intrinsic_reduce)
|
||||
return true;
|
||||
|
||||
if (intr->def.num_components > 1 || intr->def.bit_size == 1)
|
||||
return true;
|
||||
|
||||
/* Hardware supports quad ops but no other support clustered reductions. */
|
||||
if (nir_intrinsic_has_cluster_size(intr)) {
|
||||
unsigned cluster = nir_intrinsic_cluster_size(intr);
|
||||
if (cluster && cluster != 4 && cluster < 32)
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (nir_intrinsic_reduction_op(intr)) {
|
||||
case nir_op_imul:
|
||||
case nir_op_fmin:
|
||||
case nir_op_fmax:
|
||||
/* no hardware scan for this opcode, always lower it */
|
||||
return true;
|
||||
|
||||
case nir_op_iadd:
|
||||
case nir_op_iand:
|
||||
case nir_op_ixor:
|
||||
case nir_op_ior:
|
||||
/* these have dedicated 64-bit lowering paths that use the 32-bit hardware
|
||||
* instructions so are likely better than the full lowering.
|
||||
*/
|
||||
return false;
|
||||
|
||||
default:
|
||||
/* otherwise, lower 64-bit, since the hw ops are at most 32-bit. */
|
||||
return intr->def.bit_size == 64;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_subgroups(nir_shader *s)
|
||||
{
|
||||
/* First, do as much common lowering as we can */
|
||||
nir_lower_subgroups_options opts = {
|
||||
.filter = lower_subgroup_filter,
|
||||
.lower_read_first_invocation = true,
|
||||
.lower_inverse_ballot = true,
|
||||
.lower_to_scalar = true,
|
||||
.lower_relative_shuffle = true,
|
||||
.lower_rotate_to_shuffle = true,
|
||||
.lower_subgroup_masks = true,
|
||||
.lower_reduce = true,
|
||||
.lower_vote = true,
|
||||
.ballot_components = 1,
|
||||
.ballot_bit_size = 32,
|
||||
.subgroup_size = 32,
|
||||
};
|
||||
|
||||
bool progress = nir_lower_subgroups(s, &opts);
|
||||
|
||||
/* Then do AGX-only lowerings on top */
|
||||
progress |=
|
||||
nir_shader_intrinsics_pass(s, lower, nir_metadata_control_flow, NULL);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,848 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* Copyright 2020 Collabora Ltd.
|
||||
* Copyright 2016 Broadcom
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir.h"
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "agx_nir.h"
|
||||
#include "agx_nir_texture.h"
|
||||
#include "glsl_types.h"
|
||||
#include "libagx.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_builtin_builder.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_intrinsics_indices.h"
|
||||
#include "shader_enums.h"
|
||||
|
||||
/* Residency flags are inverted from NIR */
|
||||
#define AGX_RESIDENT (0)
|
||||
|
||||
static bool
|
||||
fence_image(struct nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
b->cursor = nir_after_instr(&intr->instr);
|
||||
|
||||
/* If the image is write-only, there is no fencing needed */
|
||||
if (nir_intrinsic_has_access(intr) &&
|
||||
(nir_intrinsic_access(intr) & ACCESS_NON_READABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
case nir_intrinsic_image_store:
|
||||
case nir_intrinsic_bindless_image_store:
|
||||
nir_fence_pbe_to_tex_agx(b);
|
||||
return true;
|
||||
|
||||
case nir_intrinsic_image_atomic:
|
||||
case nir_intrinsic_bindless_image_atomic:
|
||||
case nir_intrinsic_image_atomic_swap:
|
||||
case nir_intrinsic_bindless_image_atomic_swap:
|
||||
nir_fence_mem_to_tex_agx(b);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
texture_descriptor_ptr(nir_builder *b, nir_tex_instr *tex)
|
||||
{
|
||||
int handle_idx = nir_tex_instr_src_index(tex, nir_tex_src_texture_handle);
|
||||
assert(handle_idx >= 0 && "must be bindless");
|
||||
return nir_load_from_texture_handle_agx(b, tex->src[handle_idx].src.ssa);
|
||||
}
|
||||
|
||||
static bool
|
||||
has_nonzero_lod(nir_tex_instr *tex)
|
||||
{
|
||||
int idx = nir_tex_instr_src_index(tex, nir_tex_src_lod);
|
||||
if (idx < 0)
|
||||
return false;
|
||||
|
||||
nir_src src = tex->src[idx].src;
|
||||
return !(nir_src_is_const(src) && nir_src_as_uint(src) == 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_tex_crawl(nir_builder *b, nir_instr *instr, UNUSED void *data)
|
||||
{
|
||||
if (instr->type != nir_instr_type_tex)
|
||||
return false;
|
||||
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
b->cursor = nir_before_instr(instr);
|
||||
|
||||
if (tex->op != nir_texop_txs && tex->op != nir_texop_texture_samples &&
|
||||
tex->op != nir_texop_query_levels)
|
||||
return false;
|
||||
|
||||
nir_def *ptr = texture_descriptor_ptr(b, tex);
|
||||
unsigned nr_comps = tex->def.num_components;
|
||||
assert(nr_comps <= 3);
|
||||
|
||||
int lod_idx = nir_tex_instr_src_index(tex, nir_tex_src_lod);
|
||||
nir_def *lod = lod_idx >= 0 ? nir_u2u16(b, tex->src[lod_idx].src.ssa)
|
||||
: nir_imm_intN_t(b, 0, 16);
|
||||
|
||||
nir_def *res;
|
||||
if (tex->op == nir_texop_txs) {
|
||||
res =
|
||||
libagx_txs(b, ptr, lod, nir_imm_int(b, nr_comps),
|
||||
nir_imm_bool(b, tex->sampler_dim == GLSL_SAMPLER_DIM_BUF),
|
||||
nir_imm_bool(b, tex->sampler_dim == GLSL_SAMPLER_DIM_1D),
|
||||
nir_imm_bool(b, tex->sampler_dim == GLSL_SAMPLER_DIM_2D),
|
||||
nir_imm_bool(b, tex->sampler_dim == GLSL_SAMPLER_DIM_CUBE),
|
||||
nir_imm_bool(b, tex->is_array));
|
||||
} else if (tex->op == nir_texop_query_levels) {
|
||||
res = libagx_texture_levels(b, ptr);
|
||||
} else {
|
||||
res = libagx_texture_samples(b, ptr);
|
||||
}
|
||||
|
||||
nir_def_rewrite_uses(&tex->def, nir_trim_vector(b, res, nr_comps));
|
||||
nir_instr_remove(instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a 1D buffer texture coordinate, calculate the 2D coordinate vector that
|
||||
* will be used to access the linear 2D texture bound to the buffer.
|
||||
*/
|
||||
static nir_def *
|
||||
coords_for_buffer_texture(nir_builder *b, nir_def *coord)
|
||||
{
|
||||
return nir_vec2(b, nir_umod_imm(b, coord, AGX_TEXTURE_BUFFER_WIDTH),
|
||||
nir_udiv_imm(b, coord, AGX_TEXTURE_BUFFER_WIDTH));
|
||||
}
|
||||
|
||||
/*
|
||||
* Buffer textures are lowered to 2D (1024xN) textures in the driver to access
|
||||
* more storage. When lowering, we need to fix up the coordinate accordingly.
|
||||
*
|
||||
* Furthermore, RGB32 formats are emulated by lowering to global memory access,
|
||||
* so to read a buffer texture we generate code that looks like:
|
||||
*
|
||||
* if (descriptor->format == RGB32)
|
||||
* return ((uint32_t *) descriptor->address)[x];
|
||||
* else
|
||||
* return txf(texture_as_2d, vec2(x % 1024, x / 1024));
|
||||
*/
|
||||
static bool
|
||||
lower_buffer_texture(nir_builder *b, nir_tex_instr *tex)
|
||||
{
|
||||
nir_def *coord = nir_steal_tex_src(tex, nir_tex_src_coord);
|
||||
nir_def *size = nir_get_texture_size(b, tex);
|
||||
nir_def *oob = nir_uge(b, coord, size);
|
||||
|
||||
/* Apply the buffer offset after calculating oob but before remapping */
|
||||
nir_def *desc = texture_descriptor_ptr(b, tex);
|
||||
coord = libagx_buffer_texture_offset(b, desc, coord);
|
||||
|
||||
/* Map out-of-bounds indices to out-of-bounds coordinates for robustness2
|
||||
* semantics from the hardware.
|
||||
*/
|
||||
coord = nir_bcsel(b, oob, nir_imm_int(b, -1), coord);
|
||||
|
||||
bool is_float = nir_alu_type_get_base_type(tex->dest_type) == nir_type_float;
|
||||
|
||||
/* Lower RGB32 reads if the format requires. If we are out-of-bounds, we use
|
||||
* the hardware path so we get a zero texel.
|
||||
*/
|
||||
nir_if *nif = nir_push_if(
|
||||
b, nir_iand(b, libagx_texture_is_rgb32(b, desc), nir_inot(b, oob)));
|
||||
|
||||
nir_def *rgb32 = nir_trim_vector(
|
||||
b, libagx_texture_load_rgb32(b, desc, coord, nir_imm_bool(b, is_float)),
|
||||
nir_tex_instr_result_size(tex));
|
||||
|
||||
/* Raw loads do not return residency information, but residency queries are
|
||||
* supported on buffer textures. Fortunately, we do not need to support
|
||||
* sparse RGB32 buffers, so we simply claim all RGB32 loads were resident.
|
||||
* Nothing should hit this in practice, but if we don't do *something* here
|
||||
* we'll get vector size mismatches which blow up in vkd3d-proton.
|
||||
*/
|
||||
if (tex->is_sparse) {
|
||||
rgb32 = nir_pad_vector_imm_int(b, rgb32, AGX_RESIDENT,
|
||||
rgb32->num_components + 1);
|
||||
}
|
||||
|
||||
nir_push_else(b, nif);
|
||||
|
||||
/* Otherwise, lower the texture instruction to read from 2D */
|
||||
assert(coord->num_components == 1 && "buffer textures are 1D");
|
||||
tex->sampler_dim = GLSL_SAMPLER_DIM_2D;
|
||||
|
||||
nir_def *coord2d = coords_for_buffer_texture(b, coord);
|
||||
nir_instr_remove(&tex->instr);
|
||||
nir_builder_instr_insert(b, &tex->instr);
|
||||
nir_tex_instr_add_src(tex, nir_tex_src_backend1, coord2d);
|
||||
nir_steal_tex_src(tex, nir_tex_src_sampler_handle);
|
||||
nir_steal_tex_src(tex, nir_tex_src_sampler_offset);
|
||||
nir_block *else_block = nir_cursor_current_block(b->cursor);
|
||||
nir_pop_if(b, nif);
|
||||
|
||||
/* Put it together with a phi */
|
||||
nir_def *phi = nir_if_phi(b, rgb32, &tex->def);
|
||||
nir_def_rewrite_uses(&tex->def, phi);
|
||||
nir_phi_instr *phi_instr = nir_def_as_phi(phi);
|
||||
nir_phi_src *else_src = nir_phi_get_src_from_block(phi_instr, else_block);
|
||||
nir_src_rewrite(&else_src->src, &tex->def);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* NIR indexes into array textures with unclamped floats (integer for txf). AGX
|
||||
* requires the index to be a clamped integer. Lower tex_src_coord into
|
||||
* tex_src_backend1 for array textures by type-converting and clamping.
|
||||
*/
|
||||
static bool
|
||||
lower_regular_texture(nir_builder *b, nir_instr *instr, UNUSED void *data)
|
||||
{
|
||||
if (instr->type != nir_instr_type_tex)
|
||||
return false;
|
||||
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
b->cursor = nir_before_instr(instr);
|
||||
|
||||
if (nir_tex_instr_is_query(tex) && tex->op != nir_texop_lod)
|
||||
return false;
|
||||
|
||||
if (tex->sampler_dim == GLSL_SAMPLER_DIM_BUF)
|
||||
return lower_buffer_texture(b, tex);
|
||||
|
||||
/* Don't lower twice */
|
||||
if (nir_tex_instr_src_index(tex, nir_tex_src_backend1) >= 0)
|
||||
return false;
|
||||
|
||||
/* Get the coordinates */
|
||||
nir_def *coord = nir_steal_tex_src(tex, nir_tex_src_coord);
|
||||
nir_def *ms_idx = nir_steal_tex_src(tex, nir_tex_src_ms_index);
|
||||
|
||||
/* Apply txf workaround, see libagx_lower_txf_robustness */
|
||||
bool is_txf = ((tex->op == nir_texop_txf) || (tex->op == nir_texop_txf_ms));
|
||||
|
||||
if (is_txf &&
|
||||
(has_nonzero_lod(tex) || tex->is_array ||
|
||||
nir_tex_instr_src_index(tex, nir_tex_src_min_lod) >= 0) &&
|
||||
!(tex->backend_flags & AGX_TEXTURE_FLAG_NO_CLAMP)) {
|
||||
|
||||
int lod_idx = nir_tex_instr_src_index(tex, nir_tex_src_lod);
|
||||
nir_def *lod =
|
||||
lod_idx >= 0 ? tex->src[lod_idx].src.ssa : nir_undef(b, 1, 16);
|
||||
|
||||
nir_def *min_lod = nir_steal_tex_src(tex, nir_tex_src_min_lod);
|
||||
|
||||
unsigned lidx = coord->num_components - 1;
|
||||
nir_def *layer = nir_channel(b, coord, lidx);
|
||||
|
||||
nir_def *replaced = libagx_lower_txf_robustness(
|
||||
b, texture_descriptor_ptr(b, tex),
|
||||
nir_imm_bool(b, has_nonzero_lod(tex)), lod,
|
||||
nir_imm_bool(b, min_lod != NULL), min_lod ?: nir_undef(b, 1, 16),
|
||||
nir_imm_bool(b, tex->is_array), layer, nir_channel(b, coord, 0));
|
||||
|
||||
coord = nir_vector_insert_imm(b, coord, replaced, 0);
|
||||
}
|
||||
|
||||
/* The layer is always the last component of the NIR coordinate, split it off
|
||||
* because we'll need to swizzle.
|
||||
*/
|
||||
nir_def *layer = NULL;
|
||||
|
||||
if (tex->is_array && tex->op != nir_texop_lod) {
|
||||
unsigned lidx = coord->num_components - 1;
|
||||
nir_def *unclamped_layer = nir_channel(b, coord, lidx);
|
||||
coord = nir_trim_vector(b, coord, lidx);
|
||||
|
||||
/* Round layer to nearest even */
|
||||
if (!is_txf) {
|
||||
unclamped_layer = nir_fround_even(b, unclamped_layer);
|
||||
|
||||
/* Explicitly round negative to avoid undefined behaviour when constant
|
||||
* folding. This is load bearing on x86 builds.
|
||||
*/
|
||||
unclamped_layer =
|
||||
nir_f2u32(b, nir_fmax(b, unclamped_layer, nir_imm_float(b, 0.0f)));
|
||||
}
|
||||
|
||||
/* For a cube array, the layer is zero-indexed component 3 of the
|
||||
* coordinate but the number of layers is component 2 of the txs result.
|
||||
*/
|
||||
if (tex->sampler_dim == GLSL_SAMPLER_DIM_CUBE) {
|
||||
assert(lidx == 3 && "4 components");
|
||||
lidx = 2;
|
||||
}
|
||||
|
||||
/* Clamp to max layer = (# of layers - 1) for out-of-bounds handling.
|
||||
* Layer must be 16-bits for the hardware, drop top bits after clamping.
|
||||
*
|
||||
* For txf, we drop out-of-bounds components rather than clamp, see the
|
||||
* above txf robustness workaround.
|
||||
*/
|
||||
if (!(tex->backend_flags & AGX_TEXTURE_FLAG_NO_CLAMP) && !is_txf) {
|
||||
nir_def *txs = nir_get_texture_size(b, tex);
|
||||
nir_def *nr_layers = nir_channel(b, txs, lidx);
|
||||
nir_def *max_layer = nir_iadd_imm(b, nr_layers, -1);
|
||||
layer = nir_umin(b, unclamped_layer, max_layer);
|
||||
} else {
|
||||
layer = unclamped_layer;
|
||||
}
|
||||
|
||||
layer = nir_u2u16(b, layer);
|
||||
}
|
||||
|
||||
/* Combine layer and multisample index into 32-bit so we don't need a vec5 or
|
||||
* vec6 16-bit coordinate tuple, which would be inconvenient in NIR for
|
||||
* little benefit (a minor optimization, I guess).
|
||||
*/
|
||||
nir_def *sample_array = (ms_idx && layer)
|
||||
? nir_pack_32_2x16_split(b, ms_idx, layer)
|
||||
: ms_idx ? nir_u2u32(b, ms_idx)
|
||||
: layer ? nir_u2u32(b, layer)
|
||||
: NULL;
|
||||
|
||||
/* Combine into the final 32-bit tuple */
|
||||
if (sample_array != NULL) {
|
||||
unsigned end = coord->num_components;
|
||||
coord = nir_pad_vector(b, coord, end + 1);
|
||||
coord = nir_vector_insert_imm(b, coord, sample_array, end);
|
||||
}
|
||||
|
||||
nir_tex_instr_add_src(tex, nir_tex_src_backend1, coord);
|
||||
|
||||
/* Furthermore, if there is an offset vector, it must be packed */
|
||||
nir_def *offset = nir_steal_tex_src(tex, nir_tex_src_offset);
|
||||
|
||||
if (offset != NULL) {
|
||||
nir_def *packed = NULL;
|
||||
|
||||
for (unsigned c = 0; c < offset->num_components; ++c) {
|
||||
nir_def *nibble = nir_iand_imm(b, nir_channel(b, offset, c), 0xF);
|
||||
nir_def *shifted = nir_ishl_imm(b, nibble, 4 * c);
|
||||
|
||||
/* We pack with iadd instead of ior to let us fuse in the shift with an
|
||||
* iadd-lsl instruction.
|
||||
*/
|
||||
if (packed != NULL)
|
||||
packed = nir_iadd(b, packed, shifted);
|
||||
else
|
||||
packed = shifted;
|
||||
}
|
||||
|
||||
nir_tex_instr_add_src(tex, nir_tex_src_backend2, packed);
|
||||
}
|
||||
|
||||
if (nir_tex_instr_src_index(tex, nir_tex_src_bias) >= 0 &&
|
||||
nir_tex_instr_src_index(tex, nir_tex_src_min_lod) >= 0) {
|
||||
|
||||
nir_def *bias = nir_steal_tex_src(tex, nir_tex_src_bias);
|
||||
nir_def *min_lod = nir_steal_tex_src(tex, nir_tex_src_min_lod);
|
||||
nir_def *packed = nir_pack_32_2x16_split(b, bias, min_lod);
|
||||
nir_tex_instr_add_src(tex, nir_tex_src_lod_bias_min_agx, packed);
|
||||
}
|
||||
|
||||
/* We reserve bound sampler #0, so we offset bound samplers by 1 and
|
||||
* otherwise map bound samplers as-is.
|
||||
*/
|
||||
nir_def *sampler = nir_steal_tex_src(tex, nir_tex_src_sampler_offset);
|
||||
if (!sampler)
|
||||
sampler = nir_imm_intN_t(b, tex->sampler_index, 16);
|
||||
|
||||
if (!is_txf &&
|
||||
nir_tex_instr_src_index(tex, nir_tex_src_sampler_handle) < 0) {
|
||||
|
||||
nir_tex_instr_add_src(tex, nir_tex_src_sampler_handle,
|
||||
nir_iadd_imm(b, nir_u2u16(b, sampler), 1));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
legalize_image_lod(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
nir_src *src;
|
||||
|
||||
#define CASE(op, idx) \
|
||||
case nir_intrinsic_##op: \
|
||||
case nir_intrinsic_bindless_##op: \
|
||||
src = &intr->src[idx]; \
|
||||
break;
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
CASE(image_load, 3)
|
||||
CASE(image_sparse_load, 3)
|
||||
CASE(image_store, 4)
|
||||
CASE(image_size, 1)
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef CASE
|
||||
|
||||
if (src->ssa->bit_size == 16)
|
||||
return false;
|
||||
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
nir_src_rewrite(src, nir_i2i16(b, src->ssa));
|
||||
return true;
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
txs_for_image(nir_builder *b, nir_intrinsic_instr *intr,
|
||||
unsigned num_components, unsigned bit_size, bool query_samples)
|
||||
{
|
||||
enum glsl_sampler_dim dim = nir_intrinsic_image_dim(intr);
|
||||
nir_def *lod = query_samples ? NULL : intr->src[1].ssa;
|
||||
nir_texop op = query_samples ? nir_texop_texture_samples : nir_texop_txs;
|
||||
|
||||
nir_def *res =
|
||||
nir_build_tex(b, op, .texture_handle = intr->src[0].ssa, .lod = lod,
|
||||
.dim = dim, .is_array = nir_intrinsic_image_array(intr),
|
||||
.can_speculate = nir_instr_can_speculate(&intr->instr));
|
||||
|
||||
/* Cube images are implemented as 2D arrays, so we need to divide here. */
|
||||
if (dim == GLSL_SAMPLER_DIM_CUBE && res->num_components > 2 &&
|
||||
!query_samples) {
|
||||
nir_def *divided = nir_udiv_imm(b, nir_channel(b, res, 2), 6);
|
||||
res = nir_vector_insert_imm(b, res, divided, 2);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static nir_def *
|
||||
image_texel_address(nir_builder *b, nir_intrinsic_instr *intr,
|
||||
bool return_index)
|
||||
{
|
||||
/* First, calculate the address of the PBE descriptor */
|
||||
nir_def *desc_address =
|
||||
nir_load_from_texture_handle_agx(b, intr->src[0].ssa);
|
||||
|
||||
nir_def *coord = intr->src[1].ssa;
|
||||
|
||||
/* For atomics, we always infer the format. We only go down this path with
|
||||
* formatless intrinsics when lowering multisampled image stores, but that
|
||||
* uses the return_index path that ignores the block size.
|
||||
*/
|
||||
enum pipe_format format = nir_intrinsic_format(intr);
|
||||
assert(return_index || format != PIPE_FORMAT_NONE);
|
||||
|
||||
nir_def *blocksize_B = nir_imm_int(b, util_format_get_blocksize(format));
|
||||
|
||||
enum glsl_sampler_dim dim = nir_intrinsic_image_dim(intr);
|
||||
bool layered = nir_intrinsic_image_array(intr) ||
|
||||
(dim == GLSL_SAMPLER_DIM_CUBE) ||
|
||||
(dim == GLSL_SAMPLER_DIM_3D);
|
||||
|
||||
if (dim == GLSL_SAMPLER_DIM_BUF && return_index) {
|
||||
return nir_channel(b, coord, 0);
|
||||
} else if (dim == GLSL_SAMPLER_DIM_BUF) {
|
||||
return libagx_buffer_texel_address(b, desc_address, coord, blocksize_B);
|
||||
} else {
|
||||
return libagx_image_texel_address(
|
||||
b, desc_address, coord, nir_u2u32(b, intr->src[2].ssa), blocksize_B,
|
||||
nir_imm_bool(b, dim == GLSL_SAMPLER_DIM_1D),
|
||||
nir_imm_bool(b, dim == GLSL_SAMPLER_DIM_MS), nir_imm_bool(b, layered),
|
||||
nir_imm_bool(b, return_index));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
lower_buffer_image(nir_builder *b, nir_intrinsic_instr *intr)
|
||||
{
|
||||
nir_def *coord_vector = intr->src[1].ssa;
|
||||
nir_def *coord = nir_channel(b, coord_vector, 0);
|
||||
|
||||
/* If we're not bindless, assume we don't need an offset (GL driver) */
|
||||
if (intr->intrinsic == nir_intrinsic_bindless_image_load ||
|
||||
intr->intrinsic == nir_intrinsic_bindless_image_sparse_load) {
|
||||
|
||||
nir_def *desc = nir_load_from_texture_handle_agx(b, intr->src[0].ssa);
|
||||
coord = libagx_buffer_texture_offset(b, desc, coord);
|
||||
} else if (intr->intrinsic == nir_intrinsic_bindless_image_store) {
|
||||
nir_def *desc = nir_load_from_texture_handle_agx(b, intr->src[0].ssa);
|
||||
coord = libagx_buffer_image_offset(b, desc, coord);
|
||||
}
|
||||
|
||||
/* Lower the buffer load/store to a 2D image load/store, matching the 2D
|
||||
* texture/PBE descriptor the driver supplies for buffer images.
|
||||
*/
|
||||
nir_def *coord2d = coords_for_buffer_texture(b, coord);
|
||||
nir_src_rewrite(&intr->src[1], nir_pad_vector(b, coord2d, 4));
|
||||
nir_intrinsic_set_image_dim(intr, GLSL_SAMPLER_DIM_2D);
|
||||
}
|
||||
|
||||
static void
|
||||
lower_1d_image(nir_builder *b, nir_intrinsic_instr *intr)
|
||||
{
|
||||
nir_def *coord = intr->src[1].ssa;
|
||||
bool is_array = nir_intrinsic_image_array(intr);
|
||||
nir_def *zero = nir_imm_intN_t(b, 0, coord->bit_size);
|
||||
|
||||
if (is_array) {
|
||||
assert(coord->num_components >= 2);
|
||||
coord =
|
||||
nir_vec3(b, nir_channel(b, coord, 0), zero, nir_channel(b, coord, 1));
|
||||
} else {
|
||||
assert(coord->num_components >= 1);
|
||||
coord = nir_vec2(b, coord, zero);
|
||||
}
|
||||
|
||||
nir_src_rewrite(&intr->src[1], nir_pad_vector(b, coord, 4));
|
||||
nir_intrinsic_set_image_dim(intr, GLSL_SAMPLER_DIM_2D);
|
||||
}
|
||||
|
||||
/*
|
||||
* Just like for txf, we need special handling around layers (and LODs, but we
|
||||
* don't support mipmapped images yet) for robust image_loads. See
|
||||
* libagx_lower_txf_robustness for more info.
|
||||
*/
|
||||
static bool
|
||||
lower_image_load_robustness(nir_builder *b, nir_intrinsic_instr *intr)
|
||||
{
|
||||
if (nir_intrinsic_access(intr) & ACCESS_IN_BOUNDS)
|
||||
return false;
|
||||
|
||||
/* We only need to worry about array-like loads */
|
||||
enum glsl_sampler_dim dim = nir_intrinsic_image_dim(intr);
|
||||
if (!nir_intrinsic_image_array(intr) && dim != GLSL_SAMPLER_DIM_CUBE)
|
||||
return false;
|
||||
|
||||
/* Determine the coordinate component of the layer. Cubes and cube arrays
|
||||
* keep their array in their last non-array coordinate component, other
|
||||
* arrays are immediately after.
|
||||
*/
|
||||
unsigned lidx = glsl_get_sampler_dim_coordinate_components(dim);
|
||||
if (dim == GLSL_SAMPLER_DIM_CUBE)
|
||||
lidx--;
|
||||
|
||||
nir_def *coord = intr->src[1].ssa;
|
||||
nir_def *lod = nir_undef(b, 1, 16);
|
||||
nir_def *layer = nir_channel(b, coord, lidx);
|
||||
|
||||
/* image_load is effectively the same as txf, reuse the txf lower */
|
||||
nir_def *replaced = libagx_lower_txf_robustness(
|
||||
b, nir_load_from_texture_handle_agx(b, intr->src[0].ssa),
|
||||
nir_imm_bool(b, false /* lower LOD */), lod,
|
||||
nir_imm_bool(b, false /* lower min LOD */), lod,
|
||||
nir_imm_bool(b, true /* lower layer */), layer, nir_channel(b, coord, 0));
|
||||
|
||||
nir_src_rewrite(&intr->src[1], nir_vector_insert_imm(b, coord, replaced, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_images(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
case nir_intrinsic_image_load:
|
||||
case nir_intrinsic_image_store:
|
||||
case nir_intrinsic_bindless_image_load:
|
||||
case nir_intrinsic_bindless_image_sparse_load:
|
||||
case nir_intrinsic_bindless_image_store: {
|
||||
/* Legalize MSAA index */
|
||||
nir_src_rewrite(&intr->src[2], nir_u2u16(b, intr->src[2].ssa));
|
||||
|
||||
if (intr->intrinsic == nir_intrinsic_image_load ||
|
||||
intr->intrinsic == nir_intrinsic_bindless_image_load ||
|
||||
intr->intrinsic == nir_intrinsic_bindless_image_sparse_load) {
|
||||
lower_image_load_robustness(b, intr);
|
||||
}
|
||||
|
||||
switch (nir_intrinsic_image_dim(intr)) {
|
||||
case GLSL_SAMPLER_DIM_1D:
|
||||
lower_1d_image(b, intr);
|
||||
return true;
|
||||
|
||||
case GLSL_SAMPLER_DIM_BUF:
|
||||
lower_buffer_image(b, intr);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
case nir_intrinsic_bindless_image_size:
|
||||
case nir_intrinsic_bindless_image_samples:
|
||||
nir_def_rewrite_uses(
|
||||
&intr->def,
|
||||
txs_for_image(
|
||||
b, intr, intr->def.num_components, intr->def.bit_size,
|
||||
intr->intrinsic == nir_intrinsic_bindless_image_samples));
|
||||
return true;
|
||||
|
||||
case nir_intrinsic_bindless_image_texel_address:
|
||||
nir_def_rewrite_uses(&intr->def, image_texel_address(b, intr, false));
|
||||
return true;
|
||||
|
||||
case nir_intrinsic_is_sparse_texels_resident:
|
||||
/* Residency information is in bit 0, so we need to mask. Unclear what's
|
||||
* in the upper bits. For now, let's match the blob.
|
||||
*/
|
||||
nir_def_replace(
|
||||
&intr->def,
|
||||
nir_ieq_imm(b, nir_iand_imm(b, intr->src[0].ssa, 1), AGX_RESIDENT));
|
||||
return true;
|
||||
|
||||
case nir_intrinsic_sparse_residency_code_and:
|
||||
/* ior because residency codes are inverted from NIR */
|
||||
nir_def_replace(&intr->def,
|
||||
nir_ior(b, intr->src[0].ssa, intr->src[1].ssa));
|
||||
return true;
|
||||
|
||||
case nir_intrinsic_image_size:
|
||||
case nir_intrinsic_image_texel_address:
|
||||
UNREACHABLE("should've been lowered");
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Map out-of-bounds storage texel buffer accesses and multisampled image stores
|
||||
* to -1 indices, which will become an out-of-bounds hardware access. This gives
|
||||
* cheap robustness2.
|
||||
*/
|
||||
static bool
|
||||
lower_robustness(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
|
||||
{
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
case nir_intrinsic_image_deref_load:
|
||||
case nir_intrinsic_image_deref_sparse_load:
|
||||
case nir_intrinsic_image_deref_store:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
enum glsl_sampler_dim dim = nir_intrinsic_image_dim(intr);
|
||||
bool array = nir_intrinsic_image_array(intr);
|
||||
unsigned size_components = nir_image_intrinsic_coord_components(intr);
|
||||
|
||||
nir_def *deref = intr->src[0].ssa;
|
||||
nir_def *coord = intr->src[1].ssa;
|
||||
|
||||
if (dim != GLSL_SAMPLER_DIM_BUF &&
|
||||
!(dim == GLSL_SAMPLER_DIM_MS &&
|
||||
intr->intrinsic == nir_intrinsic_image_deref_store))
|
||||
return false;
|
||||
|
||||
/* Bounds check the coordinate */
|
||||
nir_def *size =
|
||||
nir_image_deref_size(b, size_components, 32, deref, nir_imm_int(b, 0),
|
||||
.image_dim = dim, .image_array = array);
|
||||
nir_def *oob = nir_bany(b, nir_uge(b, coord, size));
|
||||
|
||||
/* Bounds check the sample */
|
||||
if (dim == GLSL_SAMPLER_DIM_MS) {
|
||||
nir_def *samples = nir_image_deref_samples(b, 32, deref, .image_dim = dim,
|
||||
.image_array = array);
|
||||
|
||||
oob = nir_ior(b, oob, nir_uge(b, intr->src[2].ssa, samples));
|
||||
}
|
||||
|
||||
/* Replace the last coordinate component with a large coordinate for
|
||||
* out-of-bounds. We pick 0xFFF0 as it fits in 16-bit, and it is not signed
|
||||
* as 32-bit so we won't get in-bounds coordinates for arrays due to two's
|
||||
* complement wraparound. Additionally it still meets this requirement after
|
||||
* adding 0xF, the maximum tail offset.
|
||||
*
|
||||
* This ensures the resulting hardware coordinate is definitely
|
||||
* out-of-bounds, giving hardware-level robustness2 behaviour.
|
||||
*/
|
||||
unsigned c = size_components - 1;
|
||||
nir_def *r =
|
||||
nir_bcsel(b, oob, nir_imm_int(b, 0xFFF0), nir_channel(b, coord, c));
|
||||
|
||||
nir_src_rewrite(&intr->src[1], nir_vector_insert_imm(b, coord, r, c));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Early texture lowering passes, called by the driver before lowering
|
||||
* descriptor bindings. That means these passes operate on texture derefs. The
|
||||
* purpose is to make descriptor crawls explicit in the NIR, so that the driver
|
||||
* can accurately lower descriptors after this pass but before calling
|
||||
* the full agx_nir_lower_texture.
|
||||
*/
|
||||
bool
|
||||
agx_nir_lower_texture_early(nir_shader *s, bool support_lod_bias)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
NIR_PASS(progress, s, nir_shader_intrinsics_pass, lower_robustness,
|
||||
nir_metadata_control_flow, NULL);
|
||||
|
||||
nir_lower_tex_options lower_tex_options = {
|
||||
.lower_txp = ~0,
|
||||
.lower_invalid_implicit_lod = true,
|
||||
.lower_tg4_offsets = true,
|
||||
.lower_index_to_offset = true,
|
||||
.lower_sampler_lod_bias = support_lod_bias,
|
||||
|
||||
/* Unclear if/how mipmapped 1D textures work in the hardware. */
|
||||
.lower_1d = true,
|
||||
|
||||
/* XXX: Metal seems to handle just like 3D txd, so why doesn't it work?
|
||||
* TODO: Stop using this lowering
|
||||
*/
|
||||
.lower_txd_cube_map = true,
|
||||
};
|
||||
|
||||
NIR_PASS(progress, s, nir_lower_tex, &lower_tex_options);
|
||||
return progress;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_texture(nir_shader *s)
|
||||
{
|
||||
bool progress = false;
|
||||
|
||||
nir_tex_src_type_constraints tex_constraints = {
|
||||
[nir_tex_src_lod] = {true, 16},
|
||||
[nir_tex_src_bias] = {true, 16},
|
||||
[nir_tex_src_ms_index] = {true, 16},
|
||||
[nir_tex_src_min_lod] = {true, 16},
|
||||
[nir_tex_src_texture_offset] = {true, 16},
|
||||
[nir_tex_src_sampler_offset] = {true, 16},
|
||||
};
|
||||
|
||||
/* Insert fences before lowering image atomics, since image atomics need
|
||||
* different fencing than other image operations.
|
||||
*/
|
||||
NIR_PASS(progress, s, nir_shader_intrinsics_pass, fence_image,
|
||||
nir_metadata_control_flow, NULL);
|
||||
|
||||
NIR_PASS(progress, s, nir_lower_image_atomics_to_global, NULL, NULL);
|
||||
|
||||
NIR_PASS(progress, s, nir_shader_intrinsics_pass, legalize_image_lod,
|
||||
nir_metadata_control_flow, NULL);
|
||||
NIR_PASS(progress, s, nir_shader_intrinsics_pass, lower_images,
|
||||
nir_metadata_control_flow, NULL);
|
||||
NIR_PASS(progress, s, nir_legalize_16bit_sampler_srcs, tex_constraints);
|
||||
|
||||
/* Fold constants after nir_legalize_16bit_sampler_srcs so we can detect 0 in
|
||||
* lower_regular_texture. This is required for correctness.
|
||||
*/
|
||||
NIR_PASS(progress, s, nir_opt_constant_folding);
|
||||
|
||||
/* Lower texture sources after legalizing types (as the lowering depends on
|
||||
* 16-bit multisample indices) but before lowering queries (as the lowering
|
||||
* generates txs for array textures).
|
||||
*/
|
||||
NIR_PASS(progress, s, nir_shader_instructions_pass, lower_regular_texture,
|
||||
nir_metadata_none, NULL);
|
||||
NIR_PASS(progress, s, nir_shader_instructions_pass, lower_tex_crawl,
|
||||
nir_metadata_control_flow, NULL);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_multisampled_store(nir_builder *b, nir_intrinsic_instr *intr,
|
||||
UNUSED void *data)
|
||||
{
|
||||
b->cursor = nir_before_instr(&intr->instr);
|
||||
|
||||
if (intr->intrinsic != nir_intrinsic_bindless_image_store)
|
||||
return false;
|
||||
|
||||
if (nir_intrinsic_image_dim(intr) != GLSL_SAMPLER_DIM_MS)
|
||||
return false;
|
||||
|
||||
nir_def *index_px = nir_u2u32(b, image_texel_address(b, intr, true));
|
||||
nir_def *coord2d = coords_for_buffer_texture(b, index_px);
|
||||
|
||||
nir_src_rewrite(&intr->src[1], nir_pad_vector(b, coord2d, 4));
|
||||
nir_src_rewrite(&intr->src[2], nir_imm_int(b, 0));
|
||||
nir_intrinsic_set_image_dim(intr, GLSL_SAMPLER_DIM_2D);
|
||||
nir_intrinsic_set_image_array(intr, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_lower_multisampled_image_store(nir_shader *s)
|
||||
{
|
||||
return nir_shader_intrinsics_pass(s, lower_multisampled_store,
|
||||
nir_metadata_control_flow, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a non-bindless instruction, return whether agx_nir_lower_texture will
|
||||
* lower it to something involving a descriptor crawl. This requires the driver
|
||||
* to lower the instruction to bindless before calling agx_nir_lower_texture.
|
||||
* The implementation just enumerates the cases handled in this file.
|
||||
*/
|
||||
bool
|
||||
agx_nir_needs_texture_crawl(nir_instr *instr)
|
||||
{
|
||||
if (instr->type == nir_instr_type_intrinsic) {
|
||||
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
|
||||
|
||||
switch (intr->intrinsic) {
|
||||
/* Queries, atomics always become a crawl */
|
||||
case nir_intrinsic_image_size:
|
||||
case nir_intrinsic_image_deref_size:
|
||||
case nir_intrinsic_image_samples:
|
||||
case nir_intrinsic_image_deref_samples:
|
||||
case nir_intrinsic_image_atomic:
|
||||
case nir_intrinsic_image_deref_atomic:
|
||||
case nir_intrinsic_image_atomic_swap:
|
||||
case nir_intrinsic_image_deref_atomic_swap:
|
||||
return true;
|
||||
|
||||
/* Multisampled stores need a crawl, others do not */
|
||||
case nir_intrinsic_image_store:
|
||||
case nir_intrinsic_image_deref_store:
|
||||
return nir_intrinsic_image_dim(intr) == GLSL_SAMPLER_DIM_MS;
|
||||
|
||||
/* Array loads need a crawl, other load do not */
|
||||
case nir_intrinsic_image_load:
|
||||
return nir_intrinsic_image_array(intr) ||
|
||||
nir_intrinsic_image_dim(intr) == GLSL_SAMPLER_DIM_CUBE;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else if (instr->type == nir_instr_type_tex) {
|
||||
nir_tex_instr *tex = nir_instr_as_tex(instr);
|
||||
|
||||
/* Array textures get clamped to their size via txs */
|
||||
if (tex->is_array && !(tex->backend_flags & AGX_TEXTURE_FLAG_NO_CLAMP))
|
||||
return true;
|
||||
|
||||
switch (tex->op) {
|
||||
/* Queries always become a crawl */
|
||||
case nir_texop_txs:
|
||||
case nir_texop_texture_samples:
|
||||
case nir_texop_query_levels:
|
||||
return true;
|
||||
|
||||
/* Buffer textures need their format read and txf needs its LOD/layer
|
||||
* clamped. Buffer textures are only read through txf.
|
||||
*/
|
||||
case nir_texop_txf:
|
||||
case nir_texop_txf_ms:
|
||||
return has_nonzero_lod(tex) || tex->is_array ||
|
||||
tex->sampler_dim == GLSL_SAMPLER_DIM_BUF;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2021 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "compiler/nir/nir_builder.h"
|
||||
#include "util/macros.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "nir.h"
|
||||
#include "nir_builder_opcodes.h"
|
||||
#include "nir_intrinsics.h"
|
||||
#include "nir_intrinsics_indices.h"
|
||||
#include "nir_opcodes.h"
|
||||
|
||||
static nir_preamble_class
|
||||
preamble_class(nir_def *def)
|
||||
{
|
||||
nir_intrinsic_instr *intr = nir_def_as_intrinsic_or_null(def);
|
||||
if (!intr || (nir_intrinsic_has_desc_set(intr) &&
|
||||
nir_intrinsic_desc_set(intr) >= 32 /* encoding restriction */))
|
||||
return nir_preamble_class_general;
|
||||
|
||||
if (intr->intrinsic == nir_intrinsic_bindless_image_agx)
|
||||
return nir_preamble_class_image;
|
||||
else if (intr->intrinsic == nir_intrinsic_bindless_sampler_agx)
|
||||
return nir_preamble_class_sampler;
|
||||
else
|
||||
return nir_preamble_class_general;
|
||||
}
|
||||
|
||||
static void
|
||||
def_size(nir_def *def, unsigned *size, unsigned *align,
|
||||
nir_preamble_class *class)
|
||||
{
|
||||
unsigned bit_size = MAX2(def->bit_size, 16);
|
||||
|
||||
*size = (bit_size * def->num_components) / 16;
|
||||
*align = bit_size / 16;
|
||||
*class = preamble_class(def);
|
||||
}
|
||||
|
||||
static bool
|
||||
all_uses_float(nir_def *def)
|
||||
{
|
||||
nir_foreach_use_including_if(use, def) {
|
||||
if (nir_src_is_if(use))
|
||||
return false;
|
||||
|
||||
nir_instr *use_instr = nir_src_use_instr(use);
|
||||
if (use_instr->type != nir_instr_type_alu)
|
||||
return false;
|
||||
|
||||
nir_alu_instr *use_alu = nir_instr_as_alu(use_instr);
|
||||
unsigned src_index = ~0;
|
||||
for (unsigned i = 0; i < nir_op_infos[use_alu->op].num_inputs; i++) {
|
||||
if (&use_alu->src[i].src == use) {
|
||||
src_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(src_index != ~0);
|
||||
nir_alu_type src_type = nir_alu_type_get_base_type(
|
||||
nir_op_infos[use_alu->op].input_types[src_index]);
|
||||
|
||||
if (src_type != nir_type_float)
|
||||
return false;
|
||||
|
||||
/* No float modifiers on G13 */
|
||||
if (use_alu->op == nir_op_fmax || use_alu->op == nir_op_fmin)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static float
|
||||
alu_cost(nir_alu_instr *alu)
|
||||
{
|
||||
/* TODO: Model 64-bit better */
|
||||
if (alu->def.bit_size == 64)
|
||||
return 10.0f;
|
||||
|
||||
switch (alu->op) {
|
||||
case nir_op_fsat:
|
||||
case nir_op_f2fmp:
|
||||
case nir_op_f2f16:
|
||||
case nir_op_f2f16_rtne:
|
||||
case nir_op_fadd:
|
||||
case nir_op_fmul:
|
||||
case nir_op_ffma:
|
||||
case nir_op_iadd:
|
||||
case nir_op_inot:
|
||||
case nir_op_iand:
|
||||
case nir_op_ior:
|
||||
case nir_op_ixor:
|
||||
case nir_op_feq:
|
||||
case nir_op_flt:
|
||||
case nir_op_fge:
|
||||
case nir_op_fneu:
|
||||
case nir_op_ieq:
|
||||
case nir_op_ine:
|
||||
case nir_op_ilt:
|
||||
case nir_op_ige:
|
||||
case nir_op_ult:
|
||||
case nir_op_uge:
|
||||
case nir_op_fmin:
|
||||
case nir_op_fmax:
|
||||
case nir_op_imin:
|
||||
case nir_op_imax:
|
||||
case nir_op_umin:
|
||||
case nir_op_umax:
|
||||
case nir_op_isub:
|
||||
case nir_op_ineg:
|
||||
case nir_op_bcsel:
|
||||
case nir_op_b2b1:
|
||||
case nir_op_b2b8:
|
||||
case nir_op_b2b16:
|
||||
case nir_op_b2b32:
|
||||
case nir_op_b2i8:
|
||||
case nir_op_b2i16:
|
||||
case nir_op_b2i32:
|
||||
case nir_op_b2f16:
|
||||
case nir_op_b2f32:
|
||||
case nir_op_i2i32:
|
||||
case nir_op_i2i16:
|
||||
case nir_op_u2u32:
|
||||
case nir_op_u2u16:
|
||||
case nir_op_u2u8:
|
||||
case nir_op_i2i8:
|
||||
case nir_op_iadd_sat:
|
||||
case nir_op_isub_sat:
|
||||
case nir_op_uadd_sat:
|
||||
case nir_op_usub_sat:
|
||||
case nir_op_iabs:
|
||||
/* SCIB */
|
||||
return 1.0;
|
||||
|
||||
case nir_op_ffloor:
|
||||
case nir_op_fceil:
|
||||
case nir_op_ftrunc:
|
||||
case nir_op_fround_even:
|
||||
case nir_op_bit_count:
|
||||
case nir_op_bitfield_reverse:
|
||||
case nir_op_ufind_msb:
|
||||
case nir_op_imul:
|
||||
case nir_op_imadshl_agx:
|
||||
case nir_op_imsubshl_agx:
|
||||
case nir_op_ishl:
|
||||
case nir_op_ishr:
|
||||
case nir_op_ushr:
|
||||
case nir_op_flog2:
|
||||
case nir_op_fexp2:
|
||||
case nir_op_extr_agx:
|
||||
case nir_op_ubitfield_extract:
|
||||
case nir_op_f2i8:
|
||||
case nir_op_f2i16:
|
||||
case nir_op_f2i32:
|
||||
case nir_op_f2u8:
|
||||
case nir_op_f2u16:
|
||||
case nir_op_f2u32:
|
||||
case nir_op_i2fmp:
|
||||
case nir_op_i2f16:
|
||||
case nir_op_i2f32:
|
||||
case nir_op_u2fmp:
|
||||
case nir_op_u2f16:
|
||||
case nir_op_u2f32:
|
||||
case nir_op_interleave:
|
||||
/* IC */
|
||||
return 4.0;
|
||||
|
||||
case nir_op_frcp:
|
||||
/* IC */
|
||||
return 6.0;
|
||||
|
||||
case nir_op_frsq:
|
||||
/* IC */
|
||||
return 8.0;
|
||||
|
||||
case nir_op_fsqrt:
|
||||
/* IC + F32 */
|
||||
return 8.5;
|
||||
|
||||
case nir_op_imul_high:
|
||||
case nir_op_umul_high:
|
||||
case nir_op_imul_2x32_64:
|
||||
case nir_op_umul_2x32_64:
|
||||
/* IC */
|
||||
return 8.0;
|
||||
|
||||
case nir_op_fsin_agx:
|
||||
/* 2 IC + 1 F32 in parallel */
|
||||
return 8.5;
|
||||
|
||||
case nir_op_fneg:
|
||||
case nir_op_fabs:
|
||||
case nir_op_f2f32:
|
||||
/* Float source modifiers will be propagated */
|
||||
return all_uses_float(&alu->def) ? 0.0 : 1.0;
|
||||
|
||||
case nir_op_mov:
|
||||
case nir_op_vec2:
|
||||
case nir_op_vec3:
|
||||
case nir_op_vec4:
|
||||
case nir_op_pack_32_2x16_split:
|
||||
case nir_op_pack_64_2x32_split:
|
||||
case nir_op_unpack_64_2x32_split_x:
|
||||
case nir_op_unpack_64_2x32_split_y:
|
||||
case nir_op_unpack_32_2x16_split_x:
|
||||
case nir_op_unpack_32_2x16_split_y:
|
||||
case nir_op_extract_i8:
|
||||
case nir_op_extract_u8:
|
||||
case nir_op_extract_i16:
|
||||
case nir_op_extract_u16:
|
||||
/* We optimistically assume that moves get coalesced */
|
||||
return 0.0;
|
||||
|
||||
default:
|
||||
/* Shrug */
|
||||
return 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
static float
|
||||
instr_cost(nir_instr *instr, const void *data)
|
||||
{
|
||||
switch (instr->type) {
|
||||
case nir_instr_type_intrinsic:
|
||||
switch (nir_instr_as_intrinsic(instr)->intrinsic) {
|
||||
case nir_intrinsic_load_global:
|
||||
case nir_intrinsic_load_agx:
|
||||
case nir_intrinsic_load_global_constant:
|
||||
case nir_intrinsic_load_constant_agx:
|
||||
case nir_intrinsic_load_ubo:
|
||||
return 10.0;
|
||||
case nir_intrinsic_ddx:
|
||||
case nir_intrinsic_ddx_fine:
|
||||
case nir_intrinsic_ddx_coarse:
|
||||
case nir_intrinsic_ddy:
|
||||
case nir_intrinsic_ddy_fine:
|
||||
case nir_intrinsic_ddy_coarse:
|
||||
return 1.0;
|
||||
case nir_intrinsic_bindless_image_agx:
|
||||
case nir_intrinsic_bindless_sampler_agx:
|
||||
/* It's worth promoting even with a constant source, but it doesn't
|
||||
* turn into instructions so should be less than any other normal
|
||||
* instruction... But just enough to get over the image rewrite_cost.
|
||||
*/
|
||||
return 2.5;
|
||||
default:
|
||||
/* Assume it's a sysval or something */
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
case nir_instr_type_tex:
|
||||
/* Texturing involes lots of memory bandwidth */
|
||||
return 20.0;
|
||||
|
||||
case nir_instr_type_alu:
|
||||
return alu_cost(nir_instr_as_alu(instr));
|
||||
|
||||
default:
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
static float
|
||||
rewrite_cost(nir_def *def, const void *data)
|
||||
{
|
||||
bool mov_needed = false, vectorizable = true;
|
||||
nir_foreach_use(use, def) {
|
||||
nir_instr *parent_instr = nir_src_use_instr(use);
|
||||
if (parent_instr->type == nir_instr_type_tex) {
|
||||
/* TODO: Maybe check the source index, but biases can be uniform */
|
||||
break;
|
||||
} else if (parent_instr->type == nir_instr_type_phi) {
|
||||
/* Assume we'd eat a move anyway */
|
||||
} else if (parent_instr->type != nir_instr_type_alu) {
|
||||
mov_needed = true;
|
||||
vectorizable = false;
|
||||
break;
|
||||
} else {
|
||||
nir_alu_instr *alu = nir_instr_as_alu(parent_instr);
|
||||
if (alu->op == nir_op_vec2 || alu->op == nir_op_vec3 ||
|
||||
alu->op == nir_op_vec4) {
|
||||
mov_needed = true;
|
||||
break;
|
||||
} else if (alu->op == nir_op_mov) {
|
||||
mov_needed = true;
|
||||
vectorizable = false;
|
||||
} else {
|
||||
/* Assume for non-moves that the const is folded into the src */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mov_needed ? ((float)(def->num_components * def->bit_size) /
|
||||
(vectorizable ? 32.0 : 16.0))
|
||||
: 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
avoid_instr(const nir_instr *instr, const void *data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static const nir_opt_preamble_options preamble_options = {
|
||||
.drawid_uniform = true,
|
||||
.subgroup_size_uniform = true,
|
||||
/* not supported in hardware */
|
||||
.load_workgroup_size_allowed = false,
|
||||
.def_size = def_size,
|
||||
.instr_cost_cb = instr_cost,
|
||||
.rewrite_cost_cb = rewrite_cost,
|
||||
.avoid_instr_cb = avoid_instr,
|
||||
|
||||
/* hardware size is 512, but it's polite to leave some wiggle room to push
|
||||
* hot constants so we don't end up rematerializing all over the place.
|
||||
* 480 seems to be a sweetspot, based on a few minutes of shader-db.
|
||||
*/
|
||||
.preamble_storage_size[nir_preamble_class_general] = 480,
|
||||
|
||||
/* We have at least 32 texture state registers. TODO: check for more? */
|
||||
.preamble_storage_size[nir_preamble_class_image] = 32,
|
||||
|
||||
/* We have at least 16 sampler state registers. TODO: check for more? */
|
||||
.preamble_storage_size[nir_preamble_class_sampler] = 16,
|
||||
};
|
||||
|
||||
/*
|
||||
* Bindless image handles can't be stored to uniforms, so we move them back to
|
||||
* the main shader. Effectively un-optimizing the preamble.
|
||||
*/
|
||||
static bool
|
||||
lower_store_preamble(nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
int16_t *heaps = data;
|
||||
if (intr->intrinsic != nir_intrinsic_store_preamble ||
|
||||
nir_intrinsic_preamble_class(intr) == nir_preamble_class_image)
|
||||
return false;
|
||||
|
||||
nir_intrinsic_instr *handle = nir_src_as_intrinsic(intr->src[0]);
|
||||
if (!handle || handle->intrinsic != nir_intrinsic_bindless_image_agx)
|
||||
return false;
|
||||
|
||||
heaps[nir_intrinsic_base(intr)] = nir_intrinsic_desc_set(handle);
|
||||
nir_src_rewrite(&intr->src[0], handle->src[0].ssa);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
lower_preamble(nir_builder *b, nir_intrinsic_instr *intr, void *data)
|
||||
{
|
||||
if (intr->intrinsic == nir_intrinsic_bindless_sampler_agx) {
|
||||
/* Rematerialize bindless_sampler_agx before store_preamble with only the
|
||||
* byte offset (first source), not the sampler index.
|
||||
*/
|
||||
nir_foreach_use_safe(use, &intr->def) {
|
||||
nir_instr *parent = nir_src_use_instr(use);
|
||||
if (parent->type != nir_instr_type_intrinsic)
|
||||
continue;
|
||||
nir_intrinsic_instr *pintr = nir_instr_as_intrinsic(parent);
|
||||
if (pintr->intrinsic != nir_intrinsic_store_preamble ||
|
||||
nir_intrinsic_preamble_class(pintr) != nir_preamble_class_sampler)
|
||||
continue;
|
||||
|
||||
b->cursor = nir_before_src(use);
|
||||
nir_def *repl =
|
||||
nir_bindless_sampler_agx(b, intr->src[0].ssa, nir_undef(b, 1, 16),
|
||||
.desc_set = nir_intrinsic_desc_set(intr));
|
||||
nir_src_rewrite(use, repl);
|
||||
}
|
||||
|
||||
/* Replace other uses with just the sampler index. */
|
||||
nir_def_replace(&intr->def, intr->src[1].ssa);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (intr->intrinsic != nir_intrinsic_load_preamble)
|
||||
return false;
|
||||
|
||||
int16_t *heaps = data;
|
||||
b->cursor = nir_after_instr(&intr->instr);
|
||||
|
||||
unsigned base = nir_intrinsic_base(intr);
|
||||
nir_def *new_ = NULL;
|
||||
bool ts = nir_intrinsic_preamble_class(intr) == nir_preamble_class_image;
|
||||
bool ss = nir_intrinsic_preamble_class(intr) == nir_preamble_class_sampler;
|
||||
if (!ts && heaps[base] >= 0) {
|
||||
new_ = nir_bindless_image_agx(b, &intr->def, .desc_set = heaps[base]);
|
||||
}
|
||||
|
||||
nir_foreach_use_safe(use, &intr->def) {
|
||||
nir_instr *parent = nir_src_use_instr(use);
|
||||
|
||||
if (parent->type == nir_instr_type_intrinsic) {
|
||||
nir_intrinsic_instr *pintr = nir_instr_as_intrinsic(parent);
|
||||
|
||||
if (ts) {
|
||||
nir_rewrite_image_intrinsic(pintr, nir_imm_intN_t(b, base / 2, 16),
|
||||
nir_image_intrinsic_type_default);
|
||||
} else if (new_ != NULL &&
|
||||
pintr->intrinsic != nir_intrinsic_bindless_image_agx) {
|
||||
nir_src_rewrite(use, new_);
|
||||
}
|
||||
} else if (parent->type == nir_instr_type_tex) {
|
||||
nir_tex_instr *tex = nir_instr_as_tex(parent);
|
||||
nir_tex_src *src = (nir_tex_src *)use;
|
||||
|
||||
if (src->src_type == nir_tex_src_sampler_handle && ss) {
|
||||
nir_steal_tex_src(tex, nir_tex_src_sampler_handle);
|
||||
tex->sampler_index = base;
|
||||
} else if (src->src_type == nir_tex_src_texture_handle && ts) {
|
||||
nir_steal_tex_src(tex, nir_tex_src_texture_handle);
|
||||
tex->texture_index = base / 2;
|
||||
} else if (src->src_type == nir_tex_src_texture_handle) {
|
||||
assert(new_ != NULL);
|
||||
nir_src_rewrite(use, new_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
agx_nir_opt_preamble(nir_shader *nir, unsigned *sizes)
|
||||
{
|
||||
bool progress = false;
|
||||
NIR_PASS(progress, nir, nir_opt_preamble, &preamble_options, sizes);
|
||||
|
||||
int16_t heap[512];
|
||||
memset(heap, ~0, sizeof(heap));
|
||||
|
||||
if (progress) {
|
||||
nir_function_intrinsics_pass(nir_shader_get_preamble(nir),
|
||||
lower_store_preamble,
|
||||
nir_metadata_control_flow, heap);
|
||||
}
|
||||
|
||||
NIR_PASS(progress, nir, nir_shader_intrinsics_pass, lower_preamble,
|
||||
nir_metadata_control_flow, heap);
|
||||
|
||||
return progress;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Texture backend flags */
|
||||
#define AGX_TEXTURE_FLAG_NO_CLAMP (1 << 0)
|
||||
|
||||
/* Indicates that the sampler should be overriden to clamp to 0 instead of 1 */
|
||||
#define AGX_TEXTURE_FLAG_CLAMP_TO_0 (1 << 1)
|
||||
|
||||
/* Texel buffers lowered to (at most) 16384x16384 2D textures */
|
||||
#define AGX_TEXTURE_BUFFER_WIDTH 16384
|
||||
#define AGX_TEXTURE_BUFFER_MAX_HEIGHT 16384
|
||||
#define AGX_TEXTURE_BUFFER_MAX_SIZE \
|
||||
(AGX_TEXTURE_BUFFER_WIDTH * AGX_TEXTURE_BUFFER_MAX_HEIGHT)
|
||||
@@ -0,0 +1,39 @@
|
||||
template = """/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
<%
|
||||
def make_encoding(encoding):
|
||||
if encoding is None:
|
||||
return "{ 0 }"
|
||||
|
||||
return "{{ {}, {}, {} }}".format(hex(encoding.exact), encoding.length_short, int(encoding.extensible))
|
||||
%>
|
||||
|
||||
const struct agx_opcode_info agx_opcodes_info[AGX_NUM_OPCODES] = {
|
||||
% for opcode in opcodes:
|
||||
<%
|
||||
op = opcodes[opcode]
|
||||
imms = ["AGX_IMMEDIATE_" + imm.name.upper() for imm in op.imms]
|
||||
if len(imms) == 0:
|
||||
imms = ["0"]
|
||||
%>
|
||||
[AGX_OPCODE_${opcode.upper()}] = {
|
||||
"${opcode}", ${op.srcs}, ${op.dests}, ${" | ".join(imms)},
|
||||
${make_encoding(op.encoding)},
|
||||
AGX_SCHEDULE_CLASS_${op.schedule_class.upper()},
|
||||
${int(op.is_float)},
|
||||
${int(op.can_eliminate)},
|
||||
${int(op.can_reorder)},
|
||||
},
|
||||
% endfor
|
||||
};
|
||||
"""
|
||||
|
||||
from mako.template import Template
|
||||
from agx_opcodes import opcodes
|
||||
|
||||
print(Template(template).render(opcodes=opcodes))
|
||||
@@ -0,0 +1,90 @@
|
||||
template = """/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef _AGX_OPCODES_
|
||||
#define _AGX_OPCODES_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "util/macros.h"
|
||||
|
||||
enum agx_schedule_class {
|
||||
AGX_SCHEDULE_CLASS_INVALID,
|
||||
AGX_SCHEDULE_CLASS_NONE,
|
||||
AGX_SCHEDULE_CLASS_LOAD,
|
||||
AGX_SCHEDULE_CLASS_STORE,
|
||||
AGX_SCHEDULE_CLASS_ATOMIC,
|
||||
AGX_SCHEDULE_CLASS_COVERAGE,
|
||||
AGX_SCHEDULE_CLASS_PRELOAD,
|
||||
AGX_SCHEDULE_CLASS_BARRIER,
|
||||
};
|
||||
|
||||
/* Listing of opcodes */
|
||||
|
||||
enum agx_opcode {
|
||||
% for op in opcodes:
|
||||
AGX_OPCODE_${op.upper()},
|
||||
% endfor
|
||||
AGX_NUM_OPCODES
|
||||
};
|
||||
|
||||
% for name in enums:
|
||||
enum agx_${name} {
|
||||
% for k, v in enums[name].items():
|
||||
AGX_${name.upper()}_${v.replace('.', '_').upper()} = ${k},
|
||||
% endfor
|
||||
};
|
||||
|
||||
static inline const char *
|
||||
agx_${name}_as_str(enum agx_${name} x)
|
||||
{
|
||||
switch (x) {
|
||||
% for k, v in enums[name].items():
|
||||
case AGX_${name.upper()}_${v.replace('.', '_').upper()}: return "${v}";
|
||||
% endfor
|
||||
default: UNREACHABLE("Nonexhaustive enum");
|
||||
}
|
||||
}
|
||||
|
||||
% endfor
|
||||
|
||||
/* Runtime accessible info on each defined opcode */
|
||||
|
||||
<% assert(len(immediates) < 64); %>
|
||||
|
||||
enum agx_immediate {
|
||||
% for i, imm in enumerate(immediates):
|
||||
AGX_IMMEDIATE_${imm.upper()} = (1ull << ${i}),
|
||||
% endfor
|
||||
};
|
||||
|
||||
struct agx_encoding {
|
||||
uint64_t exact;
|
||||
unsigned length_short : 4;
|
||||
bool extensible : 1;
|
||||
};
|
||||
|
||||
struct agx_opcode_info {
|
||||
const char *name;
|
||||
unsigned nr_srcs;
|
||||
unsigned nr_dests;
|
||||
uint64_t immediates;
|
||||
struct agx_encoding encoding;
|
||||
enum agx_schedule_class schedule_class;
|
||||
bool is_float : 1;
|
||||
bool can_eliminate : 1;
|
||||
bool can_reorder : 1;
|
||||
};
|
||||
|
||||
extern const struct agx_opcode_info agx_opcodes_info[AGX_NUM_OPCODES];
|
||||
|
||||
#endif
|
||||
"""
|
||||
|
||||
from mako.template import Template
|
||||
from agx_opcodes import opcodes, immediates, enums
|
||||
|
||||
print(Template(template).render(opcodes=opcodes, immediates=immediates,
|
||||
enums=enums))
|
||||
@@ -0,0 +1,486 @@
|
||||
"""
|
||||
Copyright 2021 Alyssa Rosenzweig
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
|
||||
opcodes = {}
|
||||
immediates = {}
|
||||
enums = {}
|
||||
|
||||
VARIABLE = ~0
|
||||
|
||||
class Opcode(object):
|
||||
def __init__(self, name, dests, srcs, imms, is_float, can_eliminate,
|
||||
can_reorder, schedule_class, encoding):
|
||||
self.name = name
|
||||
self.dests = dests if dests != VARIABLE else 0
|
||||
self.srcs = srcs if srcs != VARIABLE else 0
|
||||
self.variable_srcs = (srcs == VARIABLE)
|
||||
self.variable_dests = (dests == VARIABLE)
|
||||
self.imms = imms
|
||||
self.is_float = is_float
|
||||
self.can_eliminate = can_eliminate
|
||||
self.can_reorder = can_reorder
|
||||
self.schedule_class = schedule_class
|
||||
self.encoding = encoding
|
||||
|
||||
class Immediate(object):
|
||||
def __init__(self, name, ctype):
|
||||
self.name = name
|
||||
self.ctype = ctype
|
||||
|
||||
class Encoding(object):
|
||||
def __init__(self, description):
|
||||
(exact, length_short, length_long) = description
|
||||
|
||||
# Convenience
|
||||
if length_long is None:
|
||||
length_long = length_short
|
||||
|
||||
self.exact = exact
|
||||
self.length_short = length_short
|
||||
self.extensible = length_short != length_long
|
||||
|
||||
if self.extensible:
|
||||
assert(length_long == length_short + (4 if length_short > 8 else 2))
|
||||
|
||||
def op(name, encoding, dests = 1, srcs = 0, imms = [], is_float = False,
|
||||
can_eliminate = True, can_reorder = True, schedule_class = "none"):
|
||||
encoding = Encoding(encoding) if encoding is not None else None
|
||||
|
||||
opcodes[name] = Opcode(name, dests, srcs, imms, is_float, can_eliminate,
|
||||
can_reorder, schedule_class, encoding)
|
||||
|
||||
def immediate(name, ctype = "uint32_t"):
|
||||
imm = Immediate(name, ctype)
|
||||
immediates[name] = imm
|
||||
return imm
|
||||
|
||||
def enum(name, value_dict):
|
||||
enums[name] = value_dict
|
||||
return immediate(name, "enum agx_" + name)
|
||||
|
||||
L = (1 << 15)
|
||||
_ = None
|
||||
|
||||
FORMAT = immediate("format", "enum agx_format")
|
||||
IMM = immediate("imm", "uint64_t")
|
||||
WRITEOUT = immediate("writeout")
|
||||
INDEX = immediate("index")
|
||||
COMPONENT = immediate("component")
|
||||
CHANNELS = immediate("channels")
|
||||
TRUTH_TABLE = immediate("truth_table")
|
||||
ROUND = immediate("round", "enum agx_round")
|
||||
SHIFT = immediate("shift")
|
||||
MASK = immediate("mask")
|
||||
BFI_MASK = immediate("bfi_mask")
|
||||
LOD_MODE = immediate("lod_mode", "enum agx_lod_mode")
|
||||
PIXEL_OFFSET = immediate("pixel_offset")
|
||||
STACK_SIZE = immediate("stack_size", 'int16_t')
|
||||
EXPLICIT_COORDS = immediate("explicit_coords", "bool")
|
||||
|
||||
DIM = enum("dim", {
|
||||
0: '1d',
|
||||
1: '1d_array',
|
||||
2: '2d',
|
||||
3: '2d_array',
|
||||
4: '2d_ms',
|
||||
5: '3d',
|
||||
6: 'cube',
|
||||
7: 'cube_array',
|
||||
8: '2d_ms_array',
|
||||
})
|
||||
|
||||
GATHER = enum("gather", {
|
||||
0b000: "none",
|
||||
0b001: "r",
|
||||
0b011: "g",
|
||||
0b101: "b",
|
||||
0b111: "a",
|
||||
})
|
||||
|
||||
OFFSET = immediate("offset", "bool")
|
||||
SHADOW = immediate("shadow", "bool")
|
||||
SPARSE = immediate("sparse", "bool")
|
||||
QUERY_LOD = immediate("query_lod", "bool")
|
||||
COHERENT = immediate("coherent", "bool")
|
||||
SCOREBOARD = immediate("scoreboard")
|
||||
ICOND = immediate("icond", "enum agx_icond")
|
||||
FCOND = immediate("fcond", "enum agx_fcond")
|
||||
NEST = immediate("nest")
|
||||
INVERT_COND = immediate("invert_cond")
|
||||
NEST = immediate("nest")
|
||||
TARGET = immediate("target", "agx_block *")
|
||||
ZS = immediate("zs")
|
||||
SR = enum("sr", {
|
||||
0: 'threadgroup_position_in_grid.x',
|
||||
1: 'threadgroup_position_in_grid.y',
|
||||
2: 'threadgroup_position_in_grid.z',
|
||||
4: 'threads_per_threadgroup.x',
|
||||
5: 'threads_per_threadgroup.y',
|
||||
6: 'threads_per_threadgroup.z',
|
||||
8: 'dispatch_threads_per_threadgroup.x',
|
||||
9: 'dispatch_threads_per_threadgroup.y',
|
||||
10: 'dispatch_threads_per_threadgroup.z',
|
||||
14: 'samples_log2',
|
||||
20: 'core_id',
|
||||
21: 'vm_slot',
|
||||
48: 'thread_position_in_threadgroup.x',
|
||||
49: 'thread_position_in_threadgroup.y',
|
||||
50: 'thread_position_in_threadgroup.z',
|
||||
51: 'thread_index_in_threadgroup',
|
||||
52: 'thread_index_in_subgroup',
|
||||
53: 'subgroup_index_in_threadgroup',
|
||||
56: 'active_thread_index_in_quad',
|
||||
57: 'total_active_threads_in_quad',
|
||||
58: 'active_thread_index_in_subgroup',
|
||||
59: 'total_active_threads_in_subgroup',
|
||||
60: 'coverage_mask',
|
||||
62: 'backfacing',
|
||||
63: 'is_active_thread',
|
||||
80: 'thread_position_in_grid.x',
|
||||
81: 'thread_position_in_grid.y',
|
||||
82: 'thread_position_in_grid.z',
|
||||
124: 'input_sample_mask',
|
||||
144: 'helper_op',
|
||||
146: 'helper_arg_l',
|
||||
147: 'helper_arg_h',
|
||||
})
|
||||
|
||||
ATOMIC_OPC = enum("atomic_opc", {
|
||||
0: 'add',
|
||||
1: 'sub',
|
||||
2: 'xchg',
|
||||
3: 'cmpxchg',
|
||||
4: 'umin',
|
||||
5: 'imin',
|
||||
6: 'umax',
|
||||
7: 'imax',
|
||||
8: 'and',
|
||||
9: 'or',
|
||||
10: 'xor',
|
||||
})
|
||||
|
||||
INTERPOLATION = enum("interpolation", {
|
||||
0: 'center',
|
||||
1: 'sample',
|
||||
2: 'centroid',
|
||||
# We translate sample -> sample_register at pack time for simplicity
|
||||
3: 'sample_register',
|
||||
})
|
||||
|
||||
SIMD_OP = enum("simd_op", {
|
||||
0b00000: 'and',
|
||||
0b00001: 'fadd',
|
||||
0b00010: 'or',
|
||||
0b00011: 'fmul',
|
||||
0b00100: 'xor',
|
||||
0b00101: 'fmin',
|
||||
0b00111: 'fmax',
|
||||
0b10000: 'iadd',
|
||||
0b10100: 'smin',
|
||||
0b10110: 'smax',
|
||||
0b11100: 'umin',
|
||||
0b11110: 'umax',
|
||||
})
|
||||
|
||||
FUNOP = lambda x: (x << 28)
|
||||
FUNOP_MASK = FUNOP((1 << 14) - 1)
|
||||
|
||||
def funop(name, opcode, schedule_class = "none"):
|
||||
op(name, (0x0A | (opcode << 28), 4, 6),
|
||||
srcs = 1, is_float = True, schedule_class = schedule_class)
|
||||
|
||||
def iunop(name, opcode):
|
||||
assert(opcode < 4)
|
||||
op(name, (0x3E | (opcode << 26), 6, _), srcs = 1)
|
||||
|
||||
# Listing of opcodes
|
||||
funop("floor", 0b000000)
|
||||
funop("srsqrt", 0b000001)
|
||||
funop("dfdx", 0b000100, schedule_class = "coverage")
|
||||
funop("dfdy", 0b000110, schedule_class = "coverage")
|
||||
funop("rcp", 0b001000)
|
||||
funop("rsqrt", 0b001001)
|
||||
funop("sin_pt_1", 0b001010)
|
||||
funop("log2", 0b001100)
|
||||
funop("exp2", 0b001101)
|
||||
funop("sin_pt_2", 0b001110)
|
||||
funop("ceil", 0b010000)
|
||||
funop("trunc", 0b100000)
|
||||
funop("roundeven", 0b110000)
|
||||
|
||||
iunop("bitrev", 0b01)
|
||||
iunop("popcount", 0b10)
|
||||
iunop("ffs", 0b11)
|
||||
|
||||
op("fadd", encoding = (0x2A, 4, 6), srcs = 2, is_float = True)
|
||||
op("ffma", encoding = (0x3A, 6, 8), srcs = 3, is_float = True)
|
||||
op("fmul", encoding = (0x1A, 4, 6), srcs = 2, is_float = True)
|
||||
op("hadd", encoding = (0x26, 4, 6), srcs = 2, is_float = True)
|
||||
op("hfma", encoding = (0x36, 6, 8), srcs = 3, is_float = True)
|
||||
op("hmul", encoding = (0x16, 4, 6), srcs = 2, is_float = True)
|
||||
op("mov_imm", encoding = (0x62, 6, 8), imms = [IMM])
|
||||
op("iadd", encoding = (0x0E, 8, _), srcs = 2, imms = [SHIFT])
|
||||
op("imad", encoding = (0x1E, 8, _), srcs = 3, imms = [SHIFT])
|
||||
op("bfi", encoding = (0x2E, 8, _), srcs = 3, imms = [BFI_MASK])
|
||||
op("bfeil", encoding = (0x2E | L, 8, _), srcs = 3, imms = [BFI_MASK])
|
||||
op("extr", encoding = (0x2E | (0x1 << 26), 8, _), srcs = 3, imms = [BFI_MASK])
|
||||
op("asr", encoding = (0x2E | L | (0x1 << 26), 8, _), srcs = 2)
|
||||
op("icmpsel", encoding = (0x12, 8, 10), srcs = 4, imms = [ICOND])
|
||||
op("fcmpsel", encoding = (0x02, 8, 10), srcs = 4, imms = [FCOND])
|
||||
|
||||
def subgroup_op(name, opc):
|
||||
exact = 0b01101111 | L | (opc << 29)
|
||||
op(name, encoding = (exact, 6, _), srcs = 1, imms = [SIMD_OP])
|
||||
|
||||
subgroup_op("quad_reduce", 0x0)
|
||||
subgroup_op("simd_reduce", 0x1)
|
||||
subgroup_op("quad_prefix", 0x2)
|
||||
subgroup_op("simd_prefix", 0x3)
|
||||
|
||||
for window, w_bit in [('quad_', 0), ('', 1)]:
|
||||
for s, shuffle in enumerate(['', '_xor', '_up', '_down']):
|
||||
op(f"{window}shuffle{shuffle}",
|
||||
encoding = (0b01101111 | (w_bit << 26) | (s << 38), 6, _),
|
||||
srcs = 2)
|
||||
|
||||
# Pseudo-instruction ballotting a boolean
|
||||
op(f"{window}ballot", _, srcs = 1)
|
||||
|
||||
for T, T_bit, cond in [('f', 0, FCOND), ('i', 1, ICOND)]:
|
||||
op(f"{T}cmp_{window}ballot",
|
||||
encoding = (0b0100010 | (T_bit << 4) | (w_bit << 48), 8, _),
|
||||
srcs = 2, imms = [cond, INVERT_COND])
|
||||
|
||||
# Pseudo-instructions for compares returning 1/0
|
||||
op("icmp", _, srcs = 2, imms = [ICOND, INVERT_COND])
|
||||
op("fcmp", _, srcs = 2, imms = [FCOND, INVERT_COND])
|
||||
|
||||
# sources are coordinates, LOD, texture bindless base (zero for texture state
|
||||
# registers), texture, sampler, shadow/offset
|
||||
op("texture_sample",
|
||||
encoding = (0x31, 8, 10), # XXX WRONG SIZE
|
||||
srcs = 6, imms = [DIM, LOD_MODE, MASK, SCOREBOARD, OFFSET, SHADOW,
|
||||
QUERY_LOD, GATHER, SPARSE])
|
||||
for memory, can_reorder in [("texture", True), ("image", False)]:
|
||||
coherency = [COHERENT] if not can_reorder else []
|
||||
op(f"{memory}_load", encoding = (0x71, 8, 10), # XXX WRONG SIZE
|
||||
srcs = 6, imms = [DIM, LOD_MODE, MASK, SCOREBOARD, OFFSET, SPARSE] + coherency,
|
||||
can_reorder = can_reorder,
|
||||
schedule_class = "none" if can_reorder else "load")
|
||||
|
||||
# sources are base, index
|
||||
op("device_load",
|
||||
encoding = (0x05, 6, 8),
|
||||
srcs = 2, imms = [FORMAT, MASK, SHIFT, SCOREBOARD, COHERENT], can_reorder = False,
|
||||
schedule_class = "load")
|
||||
|
||||
# sources are base (relative to workgroup memory), index
|
||||
op("local_load",
|
||||
encoding = (0b1101001, 6, 8),
|
||||
srcs = 2, imms = [FORMAT, MASK], can_reorder = False,
|
||||
schedule_class = "load")
|
||||
|
||||
# sources are value, base, index
|
||||
# TODO: Consider permitting the short form
|
||||
op("device_store",
|
||||
encoding = (0x45 | (1 << 47), 8, _),
|
||||
dests = 0, srcs = 3, imms = [FORMAT, MASK, SHIFT, SCOREBOARD, COHERENT], can_eliminate = False,
|
||||
schedule_class = "store")
|
||||
|
||||
# sources are value, base, index
|
||||
op("local_store",
|
||||
encoding = (0b0101001, 6, 8),
|
||||
dests = 0, srcs = 3, imms = [FORMAT, MASK],
|
||||
can_eliminate=False, schedule_class = "store")
|
||||
|
||||
# sources are value, index
|
||||
# TODO: Consider permitting the short form
|
||||
op("uniform_store",
|
||||
encoding = ((0b111 << 27) | 0b1000101 | (1 << 47), 8, _),
|
||||
dests = 0, srcs = 2, imms = [MASK], can_eliminate = False)
|
||||
|
||||
# sources are base, offset
|
||||
for kind, bit in [("tex", 1 << 6), ("sampler", 0)]:
|
||||
op(f"{kind}_state_store", encoding = (0b10101101 | bit | (1 << 20), 8, _),
|
||||
dests = 0, srcs = 2, imms = [IMM, SCOREBOARD], can_eliminate = False,
|
||||
schedule_class = "store")
|
||||
|
||||
# sources are value, base, index
|
||||
op("atomic",
|
||||
encoding = (0x15 | (1 << 26) | (1 << 31) | (5 << 44), 8, _),
|
||||
dests = 1, srcs = 3, imms = [ATOMIC_OPC, SCOREBOARD],
|
||||
can_eliminate = False, schedule_class = "atomic")
|
||||
|
||||
# XXX: stop hardcoding the long form
|
||||
op("local_atomic",
|
||||
encoding = (0x19 | (1 << 15) | (1 << 36) | (1 << 47), 10, _),
|
||||
dests = 1, srcs = 3, imms = [ATOMIC_OPC], schedule_class = "atomic",
|
||||
can_eliminate = False)
|
||||
|
||||
op("wait", (0x38, 2, _), dests = 0,
|
||||
can_eliminate = False, imms = [SCOREBOARD], schedule_class = "invalid")
|
||||
|
||||
for (suffix, schedule_class) in [("", "none"), ("_coverage", "coverage"), ("_barrier", "barrier")]:
|
||||
op(f"get_sr{suffix}", (0x72, 4, _), dests = 1, imms = [SR],
|
||||
schedule_class = schedule_class, can_reorder = schedule_class == "none")
|
||||
|
||||
op("sample_mask", (0x7fc1, 6, _), dests = 0, srcs = 2,
|
||||
can_eliminate = False, schedule_class = "coverage")
|
||||
|
||||
# Sources: sample mask, combined depth/stencil
|
||||
op("zs_emit", (0x41, 4, _), dests = 0, srcs = 2,
|
||||
can_eliminate = False, imms = [ZS], schedule_class = "coverage")
|
||||
|
||||
# Sources: sample mask, explicit coords (if present)
|
||||
op("ld_tile", (0x49, 8, _), dests = 1, srcs = 2,
|
||||
imms = [FORMAT, MASK, PIXEL_OFFSET, EXPLICIT_COORDS], can_reorder = False,
|
||||
schedule_class = "coverage")
|
||||
|
||||
# Sources: value, sample mask, explicit coords (if present)
|
||||
op("st_tile", (0x09, 8, _), dests = 0, srcs = 3,
|
||||
can_eliminate = False, imms = [FORMAT, MASK, PIXEL_OFFSET, EXPLICIT_COORDS],
|
||||
schedule_class = "coverage")
|
||||
|
||||
for (name, exact) in [("any", 0xC000), ("none", 0xC020), ("none_after", 0xC020)]:
|
||||
op("jmp_exec_" + name, (exact, 6, _), dests = 0, srcs = 0,
|
||||
can_eliminate = False, schedule_class = "invalid", imms = [TARGET])
|
||||
|
||||
# TODO: model implicit r0l destinations
|
||||
op("pop_exec", (0x52 | (0x3 << 9), 6, _),
|
||||
dests = 0, srcs = 0, can_eliminate = False, schedule_class = "invalid",
|
||||
imms = [NEST])
|
||||
|
||||
for is_float in [False, True]:
|
||||
mod_mask = 0 if is_float else (0x3 << 26) | (0x3 << 38)
|
||||
|
||||
for (cf, cf_op) in [("if", 0), ("else", 1), ("while", 2)]:
|
||||
name = "{}_{}cmp".format(cf, "f" if is_float else "i")
|
||||
exact = 0x42 | (0x0 if is_float else 0x10) | (cf_op << 9)
|
||||
imms = [NEST, FCOND if is_float else ICOND, INVERT_COND, TARGET]
|
||||
|
||||
op(name, (exact, 6, _), dests = 0, srcs = 2, can_eliminate = False,
|
||||
imms = imms, is_float = is_float,
|
||||
schedule_class = "preload" if cf == "else" else "invalid")
|
||||
|
||||
op("bitop", (0x7E, 6, _), srcs = 2, imms = [TRUTH_TABLE])
|
||||
op("intl", (0x3E, 6, _), srcs = 2, imms = [])
|
||||
op("convert", (0x3E | L, 6, _), srcs = 2, imms = [ROUND])
|
||||
|
||||
# Sources are the coeffient register and the sample index (if applicable)
|
||||
op("iter", (0x21, 8, _), srcs = 2, imms = [CHANNELS, INTERPOLATION])
|
||||
|
||||
# Sources are the coeffient register for the varying, the coefficient register
|
||||
# for W, and the sample index (if applicable)
|
||||
op("iterproj", (0x21, 8, _), srcs = 3, imms = [CHANNELS, INTERPOLATION])
|
||||
|
||||
op("ldcf", (0xA1, 8, _), srcs = 1, imms = [CHANNELS])
|
||||
op("st_vary", None, dests = 0, srcs = 2, can_eliminate = False)
|
||||
op("no_varyings", (0x80000051, 4, _), dests = 0, can_eliminate = False)
|
||||
op("stop", (0x88, 2, _), dests = 0, can_eliminate = False,
|
||||
schedule_class = "invalid")
|
||||
op("trap", (0x08, 2, _), dests = 0, can_eliminate = False,
|
||||
schedule_class = "invalid")
|
||||
|
||||
# These are modelled as total barriers since they can guard global memory
|
||||
# access too, and even need to be properly ordered with loads.
|
||||
op("wait_pix", (0x48, 4, _), dests = 0, imms = [WRITEOUT],
|
||||
can_eliminate = False, schedule_class = "barrier")
|
||||
op("signal_pix", (0x58, 4, _), dests = 0, imms = [WRITEOUT],
|
||||
can_eliminate = False, schedule_class = "barrier")
|
||||
|
||||
# Sources are the data vector, the coordinate vector, the LOD, the bindless
|
||||
# table if present (zero for texture state registers), and texture index.
|
||||
op("image_write", (0xF1 | (1 << 23), 6, 8), dests = 0, srcs = 5, imms
|
||||
= [DIM, COHERENT], can_eliminate = False, schedule_class = "store")
|
||||
|
||||
# Sources are the image base, image index, the offset within shared memory, and
|
||||
# the coordinates (or just the layer if implicit).
|
||||
# TODO: Do we need the short encoding?
|
||||
op("block_image_store", (0xB1, 10, _), dests = 0, srcs = 4,
|
||||
imms = [FORMAT, DIM, EXPLICIT_COORDS], can_eliminate = False, schedule_class = "store")
|
||||
|
||||
# Barriers
|
||||
op("threadgroup_barrier", (0x0068, 2, _), dests = 0, srcs = 0,
|
||||
can_eliminate = False, schedule_class = "barrier")
|
||||
|
||||
def memory_barrier(name, a, b, c):
|
||||
op(name, (0xF5 | (a << 10) | (b << 8) | (c << 12), 2, _), dests = 0, srcs = 0,
|
||||
can_eliminate = False, schedule_class = "barrier")
|
||||
|
||||
memory_barrier("memory_barrier", 1, 2, 9)
|
||||
|
||||
# TODO: Not clear what these individually are. Some might be cache flushes?
|
||||
memory_barrier("image_barrier_1", 2, 2, 10)
|
||||
memory_barrier("image_barrier_2", 3, 2, 10)
|
||||
memory_barrier("image_barrier_3", 2, 1, 10)
|
||||
memory_barrier("image_barrier_4", 3, 1, 10)
|
||||
|
||||
memory_barrier("flush_memory_to_texture", 0, 0, 4)
|
||||
|
||||
memory_barrier("memory_barrier_2", 2, 2, 9)
|
||||
memory_barrier("memory_barrier_3", 2, 1, 9)
|
||||
memory_barrier("unknown_barrier_1", 0, 3, 3)
|
||||
memory_barrier("unknown_barrier_2", 0, 3, 0)
|
||||
|
||||
# Seen with device-scope memory barriers. Again not clear what's what.
|
||||
memory_barrier("device_barrier_1", 3, 1, 9)
|
||||
memory_barrier("device_barrier_2", 3, 2, 9)
|
||||
|
||||
op("doorbell", (0x60020 | 0x28 << 32, 6, _), dests = 0,
|
||||
can_eliminate = False, can_reorder = False, imms = [IMM])
|
||||
|
||||
op("stack_unmap", (0x00075, 8, _), dests = 1, srcs = 0, can_eliminate = False, can_reorder = False, imms = [IMM])
|
||||
op("stack_map", (0x10075, 8, _), dests = 0, srcs = 1, can_eliminate = False, can_reorder = False, imms = [IMM])
|
||||
|
||||
op("stack_adjust", encoding = (0x10100b5, 8, _),
|
||||
dests = 0, srcs = 0, can_eliminate = False, can_reorder = False,
|
||||
imms = [STACK_SIZE], schedule_class = "store")
|
||||
|
||||
# source is offset
|
||||
op("stack_load", encoding = (0x35, 6, 8),
|
||||
srcs = 1, imms = [FORMAT, MASK, SCOREBOARD], can_reorder = False,
|
||||
schedule_class = "load")
|
||||
|
||||
# sources are value and offset
|
||||
op("stack_store", encoding = (0xb5, 6, 8),
|
||||
dests = 0, srcs = 2, imms = [FORMAT, MASK, SCOREBOARD],
|
||||
can_eliminate=False, schedule_class = "store")
|
||||
|
||||
# Convenient aliases.
|
||||
op("mov", _, srcs = 1)
|
||||
op("not", _, srcs = 1)
|
||||
op("signext", _, srcs = 1)
|
||||
|
||||
op("collect", _, srcs = VARIABLE)
|
||||
op("split", _, srcs = 1, dests = VARIABLE)
|
||||
op("phi", _, srcs = VARIABLE, schedule_class = "preload")
|
||||
|
||||
# The srcs double as destinations. Only deals in registers. This is generated by
|
||||
# parallel copy lowering and lowered soon after. We need this as a dedicated
|
||||
# instruction only for RA validation.
|
||||
op("swap", _, dests = 0, srcs = 2)
|
||||
|
||||
op("unit_test", _, dests = 0, srcs = 1, can_eliminate = False)
|
||||
|
||||
# Like mov, but takes a register and can only appear at the start. Guaranteed
|
||||
# to be coalesced during RA, rather than lowered to a real move.
|
||||
op("preload", _, srcs = 1, schedule_class = "preload")
|
||||
|
||||
# Opposite of preload. Exports a scalar value to a particular register at the
|
||||
# end of the shader part. Must only appear after the logical end of the exit
|
||||
# block, this avoids special casing the source's liveness. Logically all exports
|
||||
# happen in parallel at the end of the shader part.
|
||||
op("export", _, dests = 0, srcs = 1, imms = [IMM], can_eliminate = False,
|
||||
schedule_class = "invalid")
|
||||
|
||||
# Pseudo-instructions to set the nesting counter. Lowers to r0l writes after RA.
|
||||
op("begin_cf", _, dests = 0, can_eliminate = False)
|
||||
op("break", _, dests = 0, imms = [NEST, TARGET], can_eliminate = False,
|
||||
schedule_class = "invalid")
|
||||
|
||||
for (name, is_float) in [("break_if_icmp", False), ("break_if_fcmp", True)]:
|
||||
op(name, _, dests = 0, srcs = 2,
|
||||
imms = [NEST, INVERT_COND, FCOND if is_float else ICOND, TARGET],
|
||||
can_eliminate = False, schedule_class = "invalid")
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/list.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/*
|
||||
* Replaces conditional breaks:
|
||||
*
|
||||
* if_cmp x, y, n=1 {
|
||||
* break #value
|
||||
* }
|
||||
* pop_exec n=1
|
||||
*
|
||||
* with break_if_*cmp pseudo-instructions for further optimization. This
|
||||
* assumes agx_opt_empty_else has already run.
|
||||
*/
|
||||
|
||||
static void
|
||||
match_block(agx_context *ctx, agx_block *block)
|
||||
{
|
||||
agx_instr *if_ = agx_last_instr(block);
|
||||
if (!if_ ||
|
||||
(if_->op != AGX_OPCODE_IF_ICMP && if_->op != AGX_OPCODE_IF_FCMP) ||
|
||||
if_->nest != 1)
|
||||
return;
|
||||
|
||||
/* If's fallthrough to the then */
|
||||
agx_block *then_block = agx_next_block(block);
|
||||
assert(block->successors[0] == then_block && "successors for if");
|
||||
|
||||
/* We're searching for a single block then, so the next block is else */
|
||||
agx_block *else_block = agx_next_block(then_block);
|
||||
if (block->successors[1] != else_block)
|
||||
return;
|
||||
|
||||
if (!list_is_singular(&then_block->instructions) ||
|
||||
!list_is_singular(&else_block->instructions))
|
||||
return;
|
||||
|
||||
agx_instr *break_ = agx_last_instr(then_block);
|
||||
agx_instr *pop = agx_last_instr(else_block);
|
||||
|
||||
if (break_->op != AGX_OPCODE_BREAK || pop->op != AGX_OPCODE_POP_EXEC ||
|
||||
pop->nest != 1)
|
||||
return;
|
||||
|
||||
/* Find the successor of the if */
|
||||
agx_block *after = else_block->successors[0];
|
||||
assert(else_block->successors[1] == NULL && "single successor");
|
||||
|
||||
/* We are dropping one level of nesting (the if) when rewriting */
|
||||
assert(break_->nest >= 1 && "must at least break out of the if");
|
||||
unsigned new_nest = break_->nest - 1;
|
||||
|
||||
/* Rewrite */
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_block(after));
|
||||
|
||||
if (if_->op == AGX_OPCODE_IF_FCMP) {
|
||||
agx_break_if_fcmp(&b, if_->src[0], if_->src[1], new_nest,
|
||||
if_->invert_cond, if_->fcond, break_->target);
|
||||
} else {
|
||||
agx_break_if_icmp(&b, if_->src[0], if_->src[1], new_nest,
|
||||
if_->invert_cond, if_->icond, break_->target);
|
||||
}
|
||||
|
||||
agx_remove_instruction(if_);
|
||||
agx_remove_instruction(break_);
|
||||
agx_remove_instruction(pop);
|
||||
}
|
||||
|
||||
void
|
||||
agx_opt_break_if(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_block(ctx, block) {
|
||||
match_block(ctx, block);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/bitset.h"
|
||||
#include "util/half_float.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/*
|
||||
* AGX can convert 16-bit sources to 32-bit for free, so it's beneficial to
|
||||
* compact 32-bit constants down to 16-bit when doing so is lossless. This
|
||||
* reduces register pressure (GPR or uniform, depending on whether the constant
|
||||
* is promoted).
|
||||
*/
|
||||
void
|
||||
agx_opt_compact_constants(agx_context *ctx)
|
||||
{
|
||||
/* TODO: Handle ints too */
|
||||
BITSET_WORD *src_float = calloc(ctx->alloc, sizeof(BITSET_WORD));
|
||||
BITSET_WORD *src_other = calloc(ctx->alloc, sizeof(BITSET_WORD));
|
||||
BITSET_WORD *replaced = calloc(ctx->alloc, sizeof(BITSET_WORD));
|
||||
|
||||
/* Analyze the types that we read constants as */
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (agx_is_float_src(I, s))
|
||||
BITSET_SET(src_float, I->src[s].value);
|
||||
else
|
||||
BITSET_SET(src_other, I->src[s].value);
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
if (I->op == AGX_OPCODE_MOV_IMM && I->dest[0].size == AGX_SIZE_32) {
|
||||
unsigned v = I->dest[0].value;
|
||||
|
||||
if (BITSET_TEST(src_float, v) && !BITSET_TEST(src_other, v)) {
|
||||
/* Try to compact to f16 */
|
||||
uint16_t compact = _mesa_float_to_half(uif(I->imm));
|
||||
|
||||
if (I->imm == fui(_mesa_half_to_float(compact))) {
|
||||
I->dest[0].size = AGX_SIZE_16;
|
||||
I->imm = compact;
|
||||
BITSET_SET(replaced, v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (BITSET_TEST(replaced, I->src[s].value)) {
|
||||
I->src[s].size = AGX_SIZE_16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(replaced);
|
||||
free(src_float);
|
||||
free(src_other);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* Copyright 2014 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/compiler.h"
|
||||
#include "agx_builder.h"
|
||||
|
||||
#define XXH_INLINE_ALL
|
||||
#include "util/xxhash.h"
|
||||
|
||||
/*
|
||||
* This pass handles CSE'ing repeated expressions created in the process of
|
||||
* translating from NIR. Also, currently this is intra-block only, to make it
|
||||
* work over multiple block we'd need to bring forward dominance calculation.
|
||||
*/
|
||||
|
||||
static inline uint32_t
|
||||
HASH(uint32_t hash, unsigned data)
|
||||
{
|
||||
return XXH32(&data, sizeof(data), hash);
|
||||
}
|
||||
|
||||
/* Hash an instruction. XXH32 isn't too speedy, so this is hotspot. */
|
||||
static uint32_t
|
||||
hash_instr(const void *data)
|
||||
{
|
||||
const agx_instr *I = data;
|
||||
uint32_t hash = 0;
|
||||
|
||||
/* Explicitly skip destinations, except for size and type */
|
||||
agx_foreach_dest(I, d) {
|
||||
hash = HASH(hash, ((uint32_t)I->dest[d].type) |
|
||||
(((uint32_t)I->dest[d].size) << 16));
|
||||
}
|
||||
|
||||
/* Hash the source array as-is */
|
||||
hash = XXH32(I->src, sizeof(agx_index) * I->nr_srcs, hash);
|
||||
|
||||
/* Hash everything else in the instruction starting from the opcode */
|
||||
hash = XXH32(&I->op, sizeof(agx_instr) - offsetof(agx_instr, op), hash);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static bool
|
||||
instrs_equal(const void *_i1, const void *_i2)
|
||||
{
|
||||
const agx_instr *i1 = _i1, *i2 = _i2;
|
||||
|
||||
if (i1->op != i2->op)
|
||||
return false;
|
||||
if (i1->nr_srcs != i2->nr_srcs)
|
||||
return false;
|
||||
if (i1->nr_dests != i2->nr_dests)
|
||||
return false;
|
||||
|
||||
/* Explicitly skip everything but size and type */
|
||||
agx_foreach_dest(i1, d) {
|
||||
if (i1->dest[d].type != i2->dest[d].type)
|
||||
return false;
|
||||
if (i1->dest[d].size != i2->dest[d].size)
|
||||
return false;
|
||||
}
|
||||
|
||||
agx_foreach_src(i1, s) {
|
||||
agx_index s1 = i1->src[s], s2 = i2->src[s];
|
||||
|
||||
if (memcmp(&s1, &s2, sizeof(s1)) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i1->imm != i2->imm)
|
||||
return false;
|
||||
if (i1->invert_cond != i2->invert_cond)
|
||||
return false;
|
||||
if (i1->dim != i2->dim)
|
||||
return false;
|
||||
if (i1->offset != i2->offset)
|
||||
return false;
|
||||
if (i1->shadow != i2->shadow)
|
||||
return false;
|
||||
if (i1->shift != i2->shift)
|
||||
return false;
|
||||
if (i1->saturate != i2->saturate)
|
||||
return false;
|
||||
if (i1->mask != i2->mask)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Determines what instructions the above routines have to handle */
|
||||
static bool
|
||||
instr_can_cse(const agx_instr *I)
|
||||
{
|
||||
return agx_opcodes_info[I->op].can_eliminate &&
|
||||
agx_opcodes_info[I->op].can_reorder;
|
||||
}
|
||||
|
||||
void
|
||||
agx_opt_cse(agx_context *ctx)
|
||||
{
|
||||
struct set *instr_set = _mesa_set_create(NULL, hash_instr, instrs_equal);
|
||||
agx_index *replacement = malloc(sizeof(agx_index) * ctx->alloc);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
memset(replacement, 0, sizeof(agx_index) * ctx->alloc);
|
||||
_mesa_set_clear(instr_set, NULL);
|
||||
|
||||
agx_foreach_instr_in_block(block, instr) {
|
||||
/* Rewrite as we go so we converge locally in 1 iteration */
|
||||
agx_foreach_ssa_src(instr, s) {
|
||||
agx_index repl = replacement[instr->src[s].value];
|
||||
if (!agx_is_null(repl))
|
||||
agx_replace_src(instr, s, repl);
|
||||
}
|
||||
|
||||
if (!instr_can_cse(instr))
|
||||
continue;
|
||||
|
||||
bool found;
|
||||
struct set_entry *entry =
|
||||
_mesa_set_search_or_add(instr_set, instr, &found);
|
||||
if (found) {
|
||||
const agx_instr *match = entry->key;
|
||||
|
||||
agx_foreach_dest(instr, d) {
|
||||
replacement[instr->dest[d].value] = match->dest[d];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(replacement);
|
||||
_mesa_set_destroy(instr_set, NULL);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/list.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/*
|
||||
* Detect blocks with the sole contents:
|
||||
*
|
||||
* else n=1
|
||||
* pop_exec n=1
|
||||
*
|
||||
* The else instruction is a no-op. To see that, consider the pseudocode for the
|
||||
* sequence of operations "else n=1; pop_exec n=1":
|
||||
*
|
||||
* # else n=1
|
||||
* if r0l == 0:
|
||||
* r0l = 1
|
||||
* elif r0l == 1:
|
||||
* if [...]:
|
||||
* r0l = 0
|
||||
* else:
|
||||
* r0l = 1
|
||||
* exec_mask[thread] = (r0l == 0)
|
||||
*
|
||||
* # pop_exec n=1
|
||||
* if r0l > 0:
|
||||
* r0l -= 1
|
||||
* exec_mask[thread] = (r0l == 0)
|
||||
*
|
||||
* That logic code simplifies to:
|
||||
*
|
||||
* if r0l > 0:
|
||||
* r0l = r0l - 1
|
||||
* exec_mask[thread] = (r0l == 0)
|
||||
*
|
||||
* which is just "pop_exec n=1".
|
||||
*
|
||||
* Therefore, this pass detects these blocks and deletes the else instruction.
|
||||
* This has the effect of removing empty else blocks. Logically, that creates
|
||||
* critical edges, so this pass can only run late (post-RA).
|
||||
*
|
||||
* The pass itself uses a simple state machine for pattern matching.
|
||||
*/
|
||||
|
||||
enum block_state {
|
||||
STATE_ELSE = 0,
|
||||
STATE_POP_EXEC,
|
||||
STATE_DONE,
|
||||
|
||||
/* Must be last */
|
||||
STATE_NONE,
|
||||
};
|
||||
|
||||
static enum block_state
|
||||
state_for_instr(const agx_instr *I)
|
||||
{
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_ELSE_ICMP:
|
||||
case AGX_OPCODE_ELSE_FCMP:
|
||||
return (I->nest == 1) ? STATE_ELSE : STATE_NONE;
|
||||
|
||||
case AGX_OPCODE_POP_EXEC:
|
||||
return (I->nest == 1) ? STATE_POP_EXEC : STATE_NONE;
|
||||
|
||||
default:
|
||||
return STATE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
match_block(agx_block *blk)
|
||||
{
|
||||
enum block_state state = STATE_ELSE;
|
||||
|
||||
agx_foreach_instr_in_block(blk, I) {
|
||||
if (state_for_instr(I) == state)
|
||||
state++;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return (state == STATE_DONE);
|
||||
}
|
||||
|
||||
void
|
||||
agx_opt_empty_else(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_block(ctx, blk) {
|
||||
if (match_block(blk)) {
|
||||
agx_instr *else_instr = agx_first_instr(blk);
|
||||
assert(state_for_instr(else_instr) == STATE_ELSE && "block matched");
|
||||
|
||||
agx_remove_instruction(else_instr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/*
|
||||
* AGX control flow instructions predicate out threads. No forward branches are
|
||||
* inserted during instruction selection, only backwards branches at the end of
|
||||
* loops exist before this pass. This means, prior to this pass, we would always
|
||||
* execute both sides of an if.
|
||||
*
|
||||
* To improve performance, this pass inserts conservative forward branches after
|
||||
* if_*cmp, else_*cmp, and break_if_*cmp instructions, jumping the
|
||||
* subgroup to their logical destination if all threads in the subgroup are
|
||||
* inactive. This has the effect of skipping over the unexecuted half of an if.
|
||||
* That means this pass is critical for control flow performance.
|
||||
*/
|
||||
|
||||
/* Estimated cost of inserting a jmp_exec_none. This value is tuned to Dolphin
|
||||
* ubershaders. It needs to be retuned in lockstep with changes to the cost
|
||||
* estimation heuristic.
|
||||
*/
|
||||
#define COST_JMP (19)
|
||||
|
||||
static uint32_t
|
||||
cost_instr(agx_instr *I)
|
||||
{
|
||||
/* TODO: Better heuristic */
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_DEVICE_LOAD:
|
||||
return 10;
|
||||
case AGX_OPCODE_TEXTURE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_SAMPLE:
|
||||
return 50;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Estimate the cost between the instruction and the branch target. This is an
|
||||
* input for our heuristic. The branch target is guaranteed to be a forward
|
||||
* branch.
|
||||
*/
|
||||
static uint32_t
|
||||
cost_between(agx_context *ctx, agx_block *from, agx_instr *from_I,
|
||||
agx_block *target, bool skip_to_end_of_target)
|
||||
{
|
||||
uint32_t cost = 0;
|
||||
|
||||
/* Consider the cost in the rest of this block */
|
||||
if (from_I != agx_last_instr(from)) {
|
||||
agx_foreach_instr_in_block_from(from, J, from_I) {
|
||||
/* If we reach the end, we're done */
|
||||
if (from == target && skip_to_end_of_target &&
|
||||
J == agx_last_instr(target))
|
||||
break;
|
||||
|
||||
cost += cost_instr(J);
|
||||
}
|
||||
}
|
||||
|
||||
if (from == target)
|
||||
return cost;
|
||||
|
||||
/* Consider the cost in the subsequent blocks */
|
||||
agx_foreach_block_from(ctx, from, block) {
|
||||
if (block == from)
|
||||
continue;
|
||||
|
||||
if (block == target && !skip_to_end_of_target)
|
||||
break;
|
||||
|
||||
agx_foreach_instr_in_block(block, I) {
|
||||
if (block == target && I == agx_last_instr(target))
|
||||
break;
|
||||
|
||||
cost += cost_instr(I);
|
||||
}
|
||||
|
||||
if (block == target) {
|
||||
assert(skip_to_end_of_target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
static void
|
||||
try_insert_jmp(agx_context *ctx, agx_block *from, agx_instr *from_I,
|
||||
agx_block *target, bool skip_to_end_of_target,
|
||||
unsigned inverse_probability)
|
||||
{
|
||||
agx_builder b = agx_init_builder(ctx, agx_after_instr(from_I));
|
||||
|
||||
/* If the control flow instruction was only inserted for its side effects,
|
||||
* there is nowhere to jump. Bail.
|
||||
*/
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
/* If we do not insert a jump, we execute the predicated instructions
|
||||
* unconditionally, with an expected cost C.
|
||||
*
|
||||
* If we do insert a jump, then we pay the cost J of the jump, AND if we do
|
||||
* not take the jump, also the cost of the instructions C. The expected cost
|
||||
* if we insert a jump is therefore J + P(not all threads inactive) C.
|
||||
*
|
||||
* Therefore, we should insert a jump if:
|
||||
*
|
||||
* J + P(not all threads inactive) C < C
|
||||
*
|
||||
* To model the implicit (i-cache, etc) costs of inserting a jump
|
||||
* instruction, we tie break conservatively, comparing with < instead of <=.
|
||||
*
|
||||
* Rearranging terms, we should NOT insert a jump if:
|
||||
*
|
||||
* C < J / P(all threads inactive).
|
||||
*/
|
||||
uint32_t cost_instructions =
|
||||
cost_between(ctx, from, from_I, target, skip_to_end_of_target);
|
||||
|
||||
if (cost_instructions < COST_JMP * inverse_probability)
|
||||
return;
|
||||
|
||||
/* It looks like inserting a jump will be a win. Do so. */
|
||||
if (skip_to_end_of_target)
|
||||
agx_jmp_exec_none_after(&b, target);
|
||||
else
|
||||
agx_jmp_exec_none(&b, target);
|
||||
}
|
||||
|
||||
void
|
||||
agx_opt_jmp_none(agx_context *ctx)
|
||||
{
|
||||
agx_foreach_block(ctx, blk) {
|
||||
/* Handle the beginning of blocks */
|
||||
agx_instr *first_ = agx_first_instr(blk);
|
||||
if (first_ && (first_->op == AGX_OPCODE_ELSE_ICMP ||
|
||||
first_->op == AGX_OPCODE_ELSE_FCMP)) {
|
||||
|
||||
/* The target of the else is the last block of the else, so we skip
|
||||
* to the end of the block (to start execution with the pop_exec).
|
||||
*/
|
||||
try_insert_jmp(ctx, blk, first_, first_->target, true, 2);
|
||||
} else if (first_ &&
|
||||
(first_->op == AGX_OPCODE_BREAK_IF_ICMP ||
|
||||
first_->op == AGX_OPCODE_BREAK_IF_FCMP) &&
|
||||
first_->nest == 1) {
|
||||
/* The target of the break is the block immediately after the end of
|
||||
* the loop, so jump to the end of the previous block to get the
|
||||
* appropriate pop_exec.
|
||||
*
|
||||
* Also, note we only do this for nest=1 to ensure we don't insert
|
||||
* jumps inside if-statements inside breaks. We can't insert a
|
||||
* jmp_exec_none inside the if because it would break out of the loop
|
||||
* for threads that are still running the loop but merely predicated
|
||||
* out due to the if-condition. This is similarly why we don't bother
|
||||
* handling unconditional break.
|
||||
*
|
||||
* TODO: This is not optimal, but fixing this would require
|
||||
* considerably more CFG gymnastics.
|
||||
*/
|
||||
agx_block *target = agx_prev_block(first_->target);
|
||||
try_insert_jmp(ctx, blk, first_, target, true, 10);
|
||||
}
|
||||
|
||||
/* Handle end of block instructions */
|
||||
agx_foreach_instr_in_block_rev(blk, I) {
|
||||
if (!instr_after_logical_end(I))
|
||||
break;
|
||||
|
||||
if (I->op == AGX_OPCODE_IF_ICMP || I->op == AGX_OPCODE_IF_FCMP) {
|
||||
try_insert_jmp(ctx, blk, I, I->target, false, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2023 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "util/bitset.h"
|
||||
#include "util/hash_table.h"
|
||||
#include "util/ralloc.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/*
|
||||
* Information about a constant, indexed by its 64-bit value. This describes the
|
||||
* value, not the move that generated it. If there are multiple moves in the
|
||||
* shader with the same immediate value, they resolve to the same constant.
|
||||
*/
|
||||
struct constant_info {
|
||||
uint64_t value;
|
||||
|
||||
/* Number of uses of the constant that could be promoted */
|
||||
unsigned nr_promotable_uses;
|
||||
|
||||
/* If we push, the uniform used */
|
||||
uint16_t uniform;
|
||||
|
||||
/* Alignment in 16-bit units needed for the constant */
|
||||
uint8_t align_16;
|
||||
|
||||
/* True if the constant was promoted to a uniform */
|
||||
bool promoted;
|
||||
};
|
||||
|
||||
/*
|
||||
* Choosing constants to promote is similar to the 0-1 knapsack problem. We use
|
||||
* a well-known heuristic: sort by benefit divided by size. We approximate
|
||||
* benefit by use count.
|
||||
*/
|
||||
static int
|
||||
constant_priority(const struct constant_info *const info)
|
||||
{
|
||||
int size = info->align_16;
|
||||
assert(size == 1 || size == 2 || size == 4);
|
||||
int inverse_size = (size == 1) ? 4 : (size == 2) ? 2 : 1;
|
||||
|
||||
return info->nr_promotable_uses * inverse_size;
|
||||
}
|
||||
|
||||
static int
|
||||
priority_compare(const void *A_, const void *B_)
|
||||
{
|
||||
const struct constant_info *const *A = A_;
|
||||
const struct constant_info *const *B = B_;
|
||||
|
||||
/* This is backwards from qsort's documentation, because we want descending
|
||||
* order and qsort returns ascending.
|
||||
*/
|
||||
return constant_priority(*B) - constant_priority(*A);
|
||||
}
|
||||
|
||||
static void
|
||||
record_use(void *memctx, struct hash_table_u64 *constants, uint64_t imm,
|
||||
enum agx_size size)
|
||||
{
|
||||
struct constant_info *info = _mesa_hash_table_u64_search(constants, imm);
|
||||
|
||||
if (!info) {
|
||||
info = rzalloc(memctx, struct constant_info);
|
||||
info->value = imm;
|
||||
_mesa_hash_table_u64_insert(constants, imm, info);
|
||||
}
|
||||
|
||||
info->nr_promotable_uses++;
|
||||
info->align_16 = MAX2(info->align_16, agx_size_align_16(size));
|
||||
}
|
||||
|
||||
static void
|
||||
pass(agx_context *ctx, void *memctx)
|
||||
{
|
||||
/* Map from SSA indices to struct constant_info */
|
||||
struct hash_table_u64 *constants = _mesa_hash_table_u64_create(memctx);
|
||||
|
||||
/* Map from SSA indices to immediate values */
|
||||
uint64_t *values = rzalloc_array(memctx, uint64_t, ctx->alloc);
|
||||
|
||||
/* Set of SSA indices that map to immediate values */
|
||||
BITSET_WORD *is_immediate = BITSET_RZALLOC(memctx, ctx->alloc);
|
||||
|
||||
/* Gather constant definitions and use */
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
if (I->op == AGX_OPCODE_MOV_IMM) {
|
||||
assert(I->dest[0].type == AGX_INDEX_NORMAL);
|
||||
BITSET_SET(is_immediate, I->dest[0].value);
|
||||
values[I->dest[0].value] = I->imm;
|
||||
} else {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (BITSET_TEST(is_immediate, I->src[s].value) &&
|
||||
agx_instr_accepts_uniform(I->op, s, ctx->out->push_count,
|
||||
I->src[s].size)) {
|
||||
|
||||
record_use(memctx, constants, values[I->src[s].value],
|
||||
I->src[s].size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Early exit if there were no constants */
|
||||
unsigned nr_nodes = _mesa_hash_table_u64_num_entries(constants);
|
||||
if (nr_nodes == 0)
|
||||
return;
|
||||
|
||||
/* Collect nodes that are promotable */
|
||||
struct constant_info **flat =
|
||||
rzalloc_array(memctx, struct constant_info *, nr_nodes);
|
||||
|
||||
unsigned flat_count = 0;
|
||||
hash_table_u64_foreach(constants, entry) {
|
||||
flat[flat_count++] = entry.data;
|
||||
}
|
||||
|
||||
/* Select constants. Even when we can promote everything, sorting keeps hot
|
||||
* constants in lower uniforms, required by some instructions.
|
||||
*/
|
||||
qsort(flat, flat_count, sizeof(*flat), priority_compare);
|
||||
|
||||
/* Promote as many constants as we can */
|
||||
for (unsigned i = 0; i < flat_count; ++i) {
|
||||
struct constant_info *info = flat[i];
|
||||
assert(info->nr_promotable_uses > 0);
|
||||
|
||||
/* Try to assign a uniform */
|
||||
unsigned uniform = ALIGN_POT(ctx->out->push_count, info->align_16);
|
||||
unsigned new_count = uniform + info->align_16;
|
||||
if (new_count > AGX_NUM_UNIFORMS)
|
||||
break;
|
||||
|
||||
/* Set the base for the first constant. This avoids uploading an extra
|
||||
* zero if we needed to pad, compared to doing this before the loop. Same
|
||||
* shader code, but slightly more efficient USC record.
|
||||
*/
|
||||
if (i == 0) {
|
||||
ctx->out->rodata.base_uniform = uniform;
|
||||
}
|
||||
|
||||
info->uniform = uniform;
|
||||
info->promoted = true;
|
||||
ctx->out->push_count = new_count;
|
||||
|
||||
unsigned size_B = info->align_16 * 2;
|
||||
unsigned uniform_offs = uniform - ctx->out->rodata.base_uniform;
|
||||
assert(uniform_offs < ARRAY_SIZE(ctx->rodata));
|
||||
memcpy(&ctx->rodata[uniform_offs], &info->value, size_B);
|
||||
|
||||
ctx->out->rodata.size_16 = new_count - ctx->out->rodata.base_uniform;
|
||||
}
|
||||
|
||||
/* Promote in the IR */
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (!BITSET_TEST(is_immediate, I->src[s].value))
|
||||
continue;
|
||||
|
||||
struct constant_info *info =
|
||||
_mesa_hash_table_u64_search(constants, values[I->src[s].value]);
|
||||
|
||||
if (info && info->promoted &&
|
||||
agx_instr_accepts_uniform(I->op, s, info->uniform,
|
||||
I->src[s].size)) {
|
||||
|
||||
agx_replace_src(I, s, agx_uniform(info->uniform, I->src[s].size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_opt_promote_constants(agx_context *ctx)
|
||||
{
|
||||
/* We do not promote constants in preambles since it's pointless and wastes
|
||||
* uniform slots.
|
||||
*/
|
||||
if (ctx->is_preamble)
|
||||
return;
|
||||
|
||||
void *memctx = ralloc_context(NULL);
|
||||
pass(ctx, memctx);
|
||||
ralloc_free(memctx);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2025 Valve Corporation
|
||||
* Copyright 2019-2022 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/bitset.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "shader_enums.h"
|
||||
|
||||
static void
|
||||
postra_liveness_ins(BITSET_WORD *live, agx_instr *I)
|
||||
{
|
||||
agx_foreach_reg_dest(I, d) {
|
||||
unsigned reg = I->dest[d].value;
|
||||
BITSET_CLEAR_COUNT(live, reg, agx_index_size_16(I->dest[d]));
|
||||
}
|
||||
|
||||
agx_foreach_reg_src(I, s) {
|
||||
unsigned reg = I->src[s].value;
|
||||
BITSET_SET_COUNT(live, reg, agx_index_size_16(I->src[s]));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Globally, liveness analysis uses a fixed-point algorithm based on a
|
||||
* worklist. We initialize a work list with the exit block. We iterate the work
|
||||
* list to compute live_in from live_out for each block on the work list,
|
||||
* adding the predecessors of the block to the work list if we made progress.
|
||||
*/
|
||||
static void
|
||||
postra_liveness(agx_context *ctx)
|
||||
{
|
||||
u_worklist worklist;
|
||||
agx_worklist_init(ctx, &worklist);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
BITSET_ZERO(block->reg_live_in);
|
||||
BITSET_ZERO(block->reg_live_out);
|
||||
|
||||
agx_worklist_push_tail(&worklist, block);
|
||||
}
|
||||
|
||||
while (!u_worklist_is_empty(&worklist)) {
|
||||
/* Pop off in reverse order since liveness is backwards */
|
||||
agx_block *blk = agx_worklist_pop_tail(&worklist);
|
||||
|
||||
/* Calculate liveness locally */
|
||||
agx_foreach_successor(blk, succ) {
|
||||
BITSET_OR(blk->reg_live_out, blk->reg_live_out, succ->reg_live_in);
|
||||
}
|
||||
|
||||
BITSET_DECLARE(live, AGX_NUM_REGS);
|
||||
memcpy(live, blk->reg_live_out, sizeof(live));
|
||||
|
||||
agx_foreach_instr_in_block_rev(blk, ins) {
|
||||
postra_liveness_ins(live, ins);
|
||||
}
|
||||
|
||||
if (BITSET_EQUAL(blk->reg_live_in, live))
|
||||
continue;
|
||||
|
||||
/* We made progress, so we need to reprocess the predecessors */
|
||||
memcpy(blk->reg_live_in, live, sizeof(live));
|
||||
agx_foreach_predecessor(blk, pred) {
|
||||
agx_worklist_push_head(&worklist, *pred);
|
||||
}
|
||||
}
|
||||
|
||||
u_worklist_fini(&worklist);
|
||||
}
|
||||
|
||||
static bool
|
||||
writes_reg(const agx_instr *I, unsigned reg)
|
||||
{
|
||||
agx_foreach_reg_dest(I, d) {
|
||||
unsigned count = agx_index_size_16(I->dest[d]);
|
||||
|
||||
if (reg >= I->dest[d].value && (reg - I->dest[d].value) < count)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark last-use sources to allow the hardware to discard from the register
|
||||
* cache. Last use information follows immediately from (post-RA) liveness
|
||||
* analysis: a register is dead immediately after its last use.
|
||||
*
|
||||
* Mark cache hints on sources/destinations to encourage the hardware to make
|
||||
* better use of the register cache. This is a simple local analysis.
|
||||
*/
|
||||
void
|
||||
agx_opt_register_cache(agx_context *ctx)
|
||||
{
|
||||
/* Analyze the shader globally */
|
||||
postra_liveness(ctx);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
/* Live-set at each point in the program */
|
||||
BITSET_DECLARE(live, AGX_NUM_REGS);
|
||||
memcpy(live, block->reg_live_out, sizeof(live));
|
||||
|
||||
/* Set of registers read "soon" by an ALU instruction. These are
|
||||
* candidates for the .cache bit.
|
||||
*/
|
||||
BITSET_DECLARE(alu_reads, AGX_NUM_REGS) = {0};
|
||||
|
||||
agx_foreach_instr_in_block_rev(block, I) {
|
||||
agx_foreach_reg_dest(I, d) {
|
||||
uint64_t reg = I->dest[d].value;
|
||||
unsigned nr = agx_index_size_16(I->dest[d]);
|
||||
|
||||
I->dest[d].cache = BITSET_TEST(alu_reads, I->dest[d].value);
|
||||
BITSET_CLEAR_COUNT(alu_reads, reg, nr);
|
||||
}
|
||||
|
||||
agx_foreach_reg_src(I, s) {
|
||||
I->src[s].cache = BITSET_TEST(alu_reads, I->src[s].value);
|
||||
}
|
||||
|
||||
agx_foreach_reg_src(I, s) {
|
||||
uint64_t reg = I->src[s].value;
|
||||
unsigned nr = agx_index_size_16(I->src[s]);
|
||||
|
||||
/* If the register dead after this instruction, it's the last use.
|
||||
* That includes if the register is overwritten this cycle, but that
|
||||
* won't show up in the liveness analysis.
|
||||
*/
|
||||
bool lu = !BITSET_TEST_COUNT(live, reg, nr) || writes_reg(I, reg);
|
||||
|
||||
/* Handling divergent blocks would require physical CFG awareness.
|
||||
* Just bail for now, skipping this pass won't affect correctness.
|
||||
*/
|
||||
I->src[s].discard = lu && !block->divergent;
|
||||
|
||||
/* Mark any source read by an ALU instruction in the same block as
|
||||
* wanting a .cache hint. This is better than just marking
|
||||
* everything, although it overly hints for very long blocks and
|
||||
* underhints for registers used across block boundaries. It's
|
||||
* probably good enough, though, and it's not clear how to do much
|
||||
* better given our limited understanding of the hardware.
|
||||
*/
|
||||
if (agx_is_alu(I))
|
||||
BITSET_SET_COUNT(alu_reads, reg, nr);
|
||||
|
||||
assert(!(I->src[s].discard && I->src[s].cache));
|
||||
}
|
||||
|
||||
postra_liveness_ins(live, I);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/lut.h"
|
||||
#include "util/macros.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/* AGX peephole optimizer responsible for instruction combining. It operates in
|
||||
* a forward direction and a backward direction, in each case traversing in
|
||||
* source order. SSA means the forward pass satisfies the invariant:
|
||||
*
|
||||
* Every def is visited before any of its uses.
|
||||
*
|
||||
* Dually, the backend pass satisfies the invariant:
|
||||
*
|
||||
* Every use of a def is visited before the def.
|
||||
*
|
||||
* This means the forward pass can propagate modifiers forward, whereas the
|
||||
* backwards pass propagates modifiers backward. Consider an example:
|
||||
*
|
||||
* 1 = fabs 0
|
||||
* 2 = fround 1
|
||||
* 3 = fsat 1
|
||||
*
|
||||
* The forwards pass would propagate the fabs to the fround (since we can
|
||||
* lookup the fabs from the fround source and do the replacement). By contrast
|
||||
* the backwards pass would propagate the fsat back to the fround (since when
|
||||
* we see the fround we know it has only a single user, fsat). Propagatable
|
||||
* instruction have natural directions (like pushforwards and pullbacks).
|
||||
*
|
||||
* We are careful to update the tracked state whenever we modify an instruction
|
||||
* to ensure the passes are linear-time and converge in a single iteration.
|
||||
*
|
||||
* Size conversions are worth special discussion. Consider the snippet:
|
||||
*
|
||||
* 2 = fadd 0, 1
|
||||
* 3 = f2f16 2
|
||||
* 4 = fround 3
|
||||
*
|
||||
* A priori, we can move the f2f16 in either direction. But it's not equal --
|
||||
* if we move it up to the fadd, we get FP16 for two instructions, whereas if
|
||||
* we push it into the fround, we effectively get FP32 for two instructions. So
|
||||
* f2f16 is backwards. Likewise, consider
|
||||
*
|
||||
* 2 = fadd 0, 1
|
||||
* 3 = f2f32 1
|
||||
* 4 = fround 3
|
||||
*
|
||||
* This time if we move f2f32 up to the fadd, we get FP32 for two, but if we
|
||||
* move it down to the fround, we get FP16 to too. So f2f32 is backwards.
|
||||
*/
|
||||
|
||||
static bool
|
||||
agx_is_fmov(agx_instr *def)
|
||||
{
|
||||
return (def->op == AGX_OPCODE_FADD || def->op == AGX_OPCODE_HADD) &&
|
||||
agx_is_equiv(def->src[1], agx_negzero());
|
||||
}
|
||||
|
||||
/* Compose floating-point modifiers with floating-point sources */
|
||||
|
||||
static agx_index
|
||||
agx_compose_float_src(agx_index to, agx_index from)
|
||||
{
|
||||
if (to.abs) {
|
||||
from.neg = false;
|
||||
from.abs = true;
|
||||
}
|
||||
|
||||
from.neg ^= to.neg;
|
||||
|
||||
return from;
|
||||
}
|
||||
|
||||
static void
|
||||
agx_optimizer_fmov(agx_instr **defs, agx_instr *ins)
|
||||
{
|
||||
agx_foreach_ssa_src(ins, s) {
|
||||
agx_index src = ins->src[s];
|
||||
agx_instr *def = defs[src.value];
|
||||
|
||||
if (def == NULL)
|
||||
continue; /* happens for phis in loops */
|
||||
if (!agx_is_fmov(def))
|
||||
continue;
|
||||
if (def->saturate)
|
||||
continue;
|
||||
if (ins->op == AGX_OPCODE_FCMPSEL && s >= 2)
|
||||
continue;
|
||||
|
||||
/* We can fold f2f32 into 32-bit instructions, but we can't fold f2f16
|
||||
* into 16-bit instructions, since the latter would implicitly promote to
|
||||
* a 32-bit instruction which is not exact.
|
||||
*/
|
||||
assert(def->src[0].size == AGX_SIZE_32 ||
|
||||
def->src[0].size == AGX_SIZE_16);
|
||||
assert(src.size == AGX_SIZE_32 || src.size == AGX_SIZE_16);
|
||||
|
||||
if (src.size == AGX_SIZE_16 && def->src[0].size == AGX_SIZE_32)
|
||||
continue;
|
||||
|
||||
ins->src[s] = agx_compose_float_src(src, def->src[0]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
image_write_source_can_be_immediate(agx_instr *I, unsigned s)
|
||||
{
|
||||
bool block = I->op == AGX_OPCODE_BLOCK_IMAGE_STORE;
|
||||
assert(I->op == AGX_OPCODE_IMAGE_WRITE || block);
|
||||
|
||||
/* LOD can always be immediate. Actually, it's just zero so far, we don't
|
||||
* support nonzero LOD for images yet.
|
||||
*/
|
||||
if (s == 2 && !block)
|
||||
return true;
|
||||
|
||||
/* If the "bindless" source (source 3) is an immediate, it means we don't
|
||||
* have a bindless image, instead we have a texture state index. We're
|
||||
* allowed to have immediate texture state registers (source 4). However,
|
||||
* we're not allowed to have immediate bindless offsets (also source 4).
|
||||
*/
|
||||
unsigned base = block ? 0 : 3;
|
||||
bool is_texture_state = (I->src[base].type == AGX_INDEX_IMMEDIATE);
|
||||
if (s == (base + 1) && is_texture_state)
|
||||
return true;
|
||||
|
||||
/* Otherwise, must be from a register */
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
agx_optimizer_inline_imm(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
agx_index src = I->src[s];
|
||||
if (src.neg)
|
||||
continue;
|
||||
|
||||
agx_instr *def = defs[src.value];
|
||||
if (!def || def->op != AGX_OPCODE_MOV_IMM)
|
||||
continue;
|
||||
|
||||
uint8_t value = def->imm;
|
||||
uint16_t value_u16 = def->imm;
|
||||
|
||||
bool float_src = agx_is_float_src(I, s);
|
||||
|
||||
if (I->op == AGX_OPCODE_ST_TILE && s == 0)
|
||||
continue;
|
||||
if (I->op == AGX_OPCODE_ZS_EMIT && s != 0)
|
||||
continue;
|
||||
if (I->op == AGX_OPCODE_TEXTURE_SAMPLE && s != 4)
|
||||
continue;
|
||||
if ((I->op == AGX_OPCODE_DEVICE_STORE ||
|
||||
I->op == AGX_OPCODE_LOCAL_STORE || I->op == AGX_OPCODE_ATOMIC ||
|
||||
I->op == AGX_OPCODE_LOCAL_ATOMIC) &&
|
||||
s != 2)
|
||||
continue;
|
||||
if (I->op == AGX_OPCODE_ST_VARY && s != 0)
|
||||
continue;
|
||||
if ((I->op == AGX_OPCODE_LOCAL_LOAD || I->op == AGX_OPCODE_DEVICE_LOAD ||
|
||||
I->op == AGX_OPCODE_STACK_STORE) &&
|
||||
s != 1)
|
||||
continue;
|
||||
if (I->op == AGX_OPCODE_SPLIT)
|
||||
continue;
|
||||
|
||||
if ((I->op == AGX_OPCODE_IMAGE_WRITE ||
|
||||
I->op == AGX_OPCODE_BLOCK_IMAGE_STORE) &&
|
||||
!image_write_source_can_be_immediate(I, s))
|
||||
continue;
|
||||
|
||||
if (float_src) {
|
||||
bool fp16 = (def->dest[0].size == AGX_SIZE_16);
|
||||
assert(fp16 || (def->dest[0].size == AGX_SIZE_32));
|
||||
|
||||
float f = fp16 ? _mesa_half_to_float(def->imm) : uif(def->imm);
|
||||
if (!agx_minifloat_exact(f))
|
||||
continue;
|
||||
|
||||
I->src[s] = agx_immediate_f(f);
|
||||
} else if (value == def->imm) {
|
||||
I->src[s] = agx_immediate(value);
|
||||
} else if (value_u16 == def->imm && agx_allows_16bit_immediate(I)) {
|
||||
I->src[s] = agx_abs(agx_immediate(value_u16));
|
||||
} else if ((I->op == AGX_OPCODE_IADD || I->op == AGX_OPCODE_IMAD) &&
|
||||
s == agx_negate_src_index(I)) {
|
||||
unsigned bits = agx_size_align_16(def->dest[0].size) * 16;
|
||||
uint64_t mask = BITFIELD64_MASK(bits);
|
||||
uint64_t negated = (-def->imm) & mask;
|
||||
value = negated;
|
||||
|
||||
/* Try to negate the immediate */
|
||||
if (value == negated) {
|
||||
I->src[s] = agx_neg(agx_immediate(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse not into and/or/xor. Specifically, acts on not and fuses:
|
||||
*
|
||||
* not(and(x, y) -> nand(x, y)
|
||||
* not(or(x, y) -> nor(x, y)
|
||||
* not(xor(x, y) -> xnor(x, y)
|
||||
*/
|
||||
static bool
|
||||
agx_optimizer_not(agx_instr *I, agx_instr *use)
|
||||
{
|
||||
/* Check for bit op and use of not op */
|
||||
if (I->op != AGX_OPCODE_BITOP || use->op != AGX_OPCODE_NOT)
|
||||
return false;
|
||||
|
||||
/* Remap operation to the appropriate one */
|
||||
I->truth_table ^= 0xF;
|
||||
I->dest[0] = use->dest[0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_optimizer_fmov_rev(agx_instr *I, agx_instr *use)
|
||||
{
|
||||
if (!agx_is_fmov(use))
|
||||
return false;
|
||||
if (use->src[0].neg || use->src[0].abs)
|
||||
return false;
|
||||
|
||||
/* We can fold f2f16 into 32-bit instructions, but we can't fold f2f32 into
|
||||
* 16-bit instructions, since the latter would implicitly promote to a 32-bit
|
||||
* instruction which is not exact.
|
||||
*/
|
||||
assert(use->dest[0].size == AGX_SIZE_32 || use->dest[0].size == AGX_SIZE_16);
|
||||
assert(I->dest[0].size == AGX_SIZE_32 || I->dest[0].size == AGX_SIZE_16);
|
||||
|
||||
if (I->dest[0].size == AGX_SIZE_16 && use->dest[0].size == AGX_SIZE_32)
|
||||
return false;
|
||||
|
||||
/* saturate(saturate(x)) = saturate(x) */
|
||||
I->saturate |= use->saturate;
|
||||
I->dest[0] = use->dest[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_icond_is_unsigned(enum agx_icond cond)
|
||||
{
|
||||
switch (cond) {
|
||||
case AGX_ICOND_UEQ:
|
||||
case AGX_ICOND_ULT:
|
||||
case AGX_ICOND_UGT:
|
||||
return true;
|
||||
|
||||
case AGX_ICOND_SEQ:
|
||||
case AGX_ICOND_SLT:
|
||||
case AGX_ICOND_SGT:
|
||||
return false;
|
||||
}
|
||||
|
||||
UNREACHABLE("invalid condition");
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_supports_zext(agx_instr *I, unsigned s)
|
||||
{
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_IADD:
|
||||
case AGX_OPCODE_IMAD:
|
||||
case AGX_OPCODE_INTL:
|
||||
case AGX_OPCODE_FFS:
|
||||
case AGX_OPCODE_BITREV:
|
||||
case AGX_OPCODE_BFI:
|
||||
case AGX_OPCODE_BFEIL:
|
||||
case AGX_OPCODE_EXTR:
|
||||
case AGX_OPCODE_BITOP:
|
||||
case AGX_OPCODE_WHILE_ICMP:
|
||||
case AGX_OPCODE_IF_ICMP:
|
||||
case AGX_OPCODE_ELSE_ICMP:
|
||||
case AGX_OPCODE_BREAK_IF_ICMP:
|
||||
case AGX_OPCODE_ICMP_BALLOT:
|
||||
case AGX_OPCODE_ICMP_QUAD_BALLOT:
|
||||
return true;
|
||||
|
||||
case AGX_OPCODE_ICMP:
|
||||
case AGX_OPCODE_ICMPSEL:
|
||||
/* Only the comparisons can be extended, not the selection. And we can
|
||||
* only zero-extend with unsigned comparison. Presumably the hardware
|
||||
* sign-extends with signed comparisons but we don't handle that yet.
|
||||
*/
|
||||
return (s < 2) && agx_icond_is_unsigned(I->icond);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
agx_optimizer_copyprop(agx_context *ctx, agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
agx_index src = I->src[s];
|
||||
agx_instr *def = defs[src.value];
|
||||
|
||||
if (def == NULL)
|
||||
continue; /* happens for phis in loops */
|
||||
if (def->op != AGX_OPCODE_MOV)
|
||||
continue;
|
||||
|
||||
/* At the moment, not all instructions support size conversions. Notably
|
||||
* RA pseudo instructions don't handle size conversions. This should be
|
||||
* refined in the future.
|
||||
*/
|
||||
if (def->src[0].size != src.size &&
|
||||
!(def->src[0].size < src.size && agx_supports_zext(I, s)))
|
||||
continue;
|
||||
|
||||
/* Optimize split(64-bit uniform) so we can get better copyprop of the
|
||||
* 32-bit uniform parts. This helps reduce moves with 64-bit uniforms.
|
||||
*/
|
||||
if (I->op == AGX_OPCODE_SPLIT && def->src[0].type == AGX_INDEX_UNIFORM &&
|
||||
src.size == AGX_SIZE_64 && I->dest[0].size == AGX_SIZE_32) {
|
||||
|
||||
assert(I->nr_dests == 2 && "decomposing a 64-bit scalar");
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
agx_index lo = def->src[0];
|
||||
lo.size = AGX_SIZE_32;
|
||||
|
||||
agx_index hi = lo;
|
||||
hi.value += 2 /* half of 64-bits = 32-bits = 2 x 16-bits */;
|
||||
|
||||
defs[I->dest[0].value] = agx_mov_to(&b, I->dest[0], lo);
|
||||
defs[I->dest[1].value] = agx_mov_to(&b, I->dest[1], hi);
|
||||
|
||||
agx_remove_instruction(I);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Immediate inlining happens elsewhere */
|
||||
if (def->src[0].type == AGX_INDEX_IMMEDIATE)
|
||||
continue;
|
||||
|
||||
/* ALU instructions cannot take 64-bit */
|
||||
if (def->src[0].size == AGX_SIZE_64 &&
|
||||
!(I->op == AGX_OPCODE_DEVICE_LOAD && s == 0) &&
|
||||
!(I->op == AGX_OPCODE_DEVICE_STORE && s == 1) &&
|
||||
!(I->op == AGX_OPCODE_ATOMIC && s == 1))
|
||||
continue;
|
||||
|
||||
agx_replace_src(I, s, def->src[0]);
|
||||
|
||||
/* If we are zero-extending into an instruction that distinguishes sign
|
||||
* and zero extend, make sure we pick zero-extend.
|
||||
*/
|
||||
if (def->src[0].size < src.size &&
|
||||
(I->op == AGX_OPCODE_IMAD || I->op == AGX_OPCODE_IADD)) {
|
||||
|
||||
assert(agx_supports_zext(I, s));
|
||||
I->src[s].abs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse conditions into if. Specifically, acts on if_icmp and fuses:
|
||||
*
|
||||
* if_icmp(cmp(x, y, *), 0, ne/eq) -> if_cmp(x, y, *)
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_if_cmp(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
/* Check for unfused if */
|
||||
if (!agx_is_equiv(I->src[1], agx_zero()) || I->icond != AGX_ICOND_UEQ ||
|
||||
I->src[0].type != AGX_INDEX_NORMAL)
|
||||
return;
|
||||
|
||||
/* Check for condition */
|
||||
agx_instr *def = defs[I->src[0].value];
|
||||
if (def->op != AGX_OPCODE_ICMP && def->op != AGX_OPCODE_FCMP)
|
||||
return;
|
||||
|
||||
/* Fuse */
|
||||
I->src[0] = def->src[0];
|
||||
I->src[1] = def->src[1];
|
||||
I->invert_cond = def->invert_cond ^ !I->invert_cond;
|
||||
|
||||
if (def->op == AGX_OPCODE_ICMP) {
|
||||
I->op = AGX_OPCODE_IF_ICMP;
|
||||
I->icond = def->icond;
|
||||
} else {
|
||||
I->op = AGX_OPCODE_IF_FCMP;
|
||||
I->fcond = def->fcond;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse invert into if. Acts on if_icmp and fuses:
|
||||
*
|
||||
* if_icmp(xor(x, 1), 0, ne) -> if_cmp(x, 0, eq)
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_if_not(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
/* Check for unfused if */
|
||||
if (!agx_is_equiv(I->src[1], agx_zero()) || I->icond != AGX_ICOND_UEQ ||
|
||||
I->src[0].type != AGX_INDEX_NORMAL)
|
||||
return;
|
||||
|
||||
/* Check for invert */
|
||||
agx_instr *def = defs[I->src[0].value];
|
||||
if (def->op != AGX_OPCODE_BITOP ||
|
||||
!agx_is_equiv(def->src[1], agx_immediate(1)) ||
|
||||
def->truth_table != UTIL_LUT2(a ^ b))
|
||||
return;
|
||||
|
||||
/* Fuse */
|
||||
I->src[0] = def->src[0];
|
||||
I->invert_cond = !I->invert_cond;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse conditions into select. Specifically, acts on icmpsel and fuses:
|
||||
*
|
||||
* icmpsel(cmp(x, y, *), 0, z, w, eq) -> cmpsel(x, y, w, z, *)
|
||||
*
|
||||
* Care must be taken to invert the condition by swapping cmpsel arguments.
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_cmpsel(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
/* Check for unfused select */
|
||||
if (!agx_is_equiv(I->src[1], agx_zero()) || I->icond != AGX_ICOND_UEQ ||
|
||||
I->src[0].type != AGX_INDEX_NORMAL)
|
||||
return;
|
||||
|
||||
/* Check for condition */
|
||||
agx_instr *def = defs[I->src[0].value];
|
||||
if (def->op != AGX_OPCODE_ICMP && def->op != AGX_OPCODE_FCMP)
|
||||
return;
|
||||
|
||||
/* Fuse */
|
||||
I->src[0] = def->src[0];
|
||||
I->src[1] = def->src[1];
|
||||
|
||||
/* In the unfused select, the condition is inverted due to the form:
|
||||
*
|
||||
* (cond == 0) ? x : y
|
||||
*
|
||||
* So we need to swap the arguments when fusing to become cond ? y : x. If
|
||||
* the condition was supposed to be inverted, we don't swap since it's
|
||||
* already inverted. cmpsel does not have an invert_cond bit to use.
|
||||
*/
|
||||
if (!def->invert_cond) {
|
||||
agx_index temp = I->src[2];
|
||||
I->src[2] = I->src[3];
|
||||
I->src[3] = temp;
|
||||
}
|
||||
|
||||
if (def->op == AGX_OPCODE_ICMP) {
|
||||
I->op = AGX_OPCODE_ICMPSEL;
|
||||
I->icond = def->icond;
|
||||
} else {
|
||||
I->op = AGX_OPCODE_FCMPSEL;
|
||||
I->fcond = def->fcond;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse conditions into ballots:
|
||||
*
|
||||
* ballot(cmp(x, y)) -> ballot_cmp(x, y)
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_ballot(agx_context *ctx, agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
if (I->src[0].type != AGX_INDEX_NORMAL)
|
||||
return;
|
||||
|
||||
agx_instr *def = defs[I->src[0].value];
|
||||
if (!def || (def->op != AGX_OPCODE_ICMP && def->op != AGX_OPCODE_FCMP))
|
||||
return;
|
||||
|
||||
bool quad = I->op == AGX_OPCODE_QUAD_BALLOT;
|
||||
assert(quad || I->op == AGX_OPCODE_BALLOT);
|
||||
|
||||
/* Replace with a fused instruction since the # of sources changes */
|
||||
agx_builder b = agx_init_builder(ctx, agx_before_instr(I));
|
||||
|
||||
agx_instr *fused = agx_icmp_ballot_to(
|
||||
&b, I->dest[0], def->src[0], def->src[1], def->icond, def->invert_cond);
|
||||
|
||||
if (def->op == AGX_OPCODE_ICMP) {
|
||||
fused->op = quad ? AGX_OPCODE_ICMP_QUAD_BALLOT : AGX_OPCODE_ICMP_BALLOT;
|
||||
} else {
|
||||
fused->op = quad ? AGX_OPCODE_FCMP_QUAD_BALLOT : AGX_OPCODE_FCMP_BALLOT;
|
||||
}
|
||||
|
||||
agx_remove_instruction(I);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse not srcs into bitop.
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_bitop(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
agx_index src = I->src[s];
|
||||
agx_instr *def = defs[src.value];
|
||||
|
||||
/* If we find a not, select new operation and fuse */
|
||||
if (def->op == AGX_OPCODE_NOT) {
|
||||
I->truth_table = util_lut2_invert_source(I->truth_table, s);
|
||||
I->src[s] = def->src[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuse sign-extends into addition-like instructions:
|
||||
*/
|
||||
static void
|
||||
agx_optimizer_signext(agx_instr **defs, agx_instr *I)
|
||||
{
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
agx_index src = I->src[s];
|
||||
agx_instr *def = defs[src.value];
|
||||
|
||||
if (def == NULL || def->op != AGX_OPCODE_SIGNEXT)
|
||||
continue;
|
||||
|
||||
agx_replace_src(I, s, def->src[0]);
|
||||
assert(!I->src[s].abs && "sign-extended");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_optimizer_forward(agx_context *ctx)
|
||||
{
|
||||
agx_instr **defs = calloc(ctx->alloc, sizeof(*defs));
|
||||
|
||||
agx_foreach_instr_global_safe(ctx, I) {
|
||||
struct agx_opcode_info info = agx_opcodes_info[I->op];
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
defs[I->dest[d].value] = I;
|
||||
}
|
||||
|
||||
/* Optimize moves */
|
||||
agx_optimizer_copyprop(ctx, defs, I);
|
||||
|
||||
/* Propagate fmov down */
|
||||
if (info.is_float || I->op == AGX_OPCODE_FCMPSEL ||
|
||||
I->op == AGX_OPCODE_FCMP)
|
||||
agx_optimizer_fmov(defs, I);
|
||||
|
||||
/* Inline immediates if we can. TODO: systematic */
|
||||
if (I->op != AGX_OPCODE_COLLECT && I->op != AGX_OPCODE_IMAGE_LOAD &&
|
||||
I->op != AGX_OPCODE_TEXTURE_LOAD &&
|
||||
I->op != AGX_OPCODE_UNIFORM_STORE && I->op != AGX_OPCODE_EXPORT)
|
||||
agx_optimizer_inline_imm(defs, I);
|
||||
|
||||
if (I->op == AGX_OPCODE_IF_ICMP) {
|
||||
agx_optimizer_if_not(defs, I);
|
||||
agx_optimizer_if_cmp(defs, I);
|
||||
} else if (I->op == AGX_OPCODE_ICMPSEL) {
|
||||
agx_optimizer_cmpsel(defs, I);
|
||||
} else if (I->op == AGX_OPCODE_BALLOT ||
|
||||
I->op == AGX_OPCODE_QUAD_BALLOT) {
|
||||
agx_optimizer_ballot(ctx, defs, I);
|
||||
} else if (I->op == AGX_OPCODE_BITOP) {
|
||||
agx_optimizer_bitop(defs, I);
|
||||
} else if (I->op == AGX_OPCODE_IADD || I->op == AGX_OPCODE_IMAD) {
|
||||
agx_optimizer_signext(defs, I);
|
||||
}
|
||||
}
|
||||
|
||||
free(defs);
|
||||
}
|
||||
|
||||
static void
|
||||
record_use(agx_instr **uses, BITSET_WORD *multiple, agx_instr *I, unsigned src)
|
||||
{
|
||||
unsigned v = I->src[src].value;
|
||||
|
||||
if (uses[v])
|
||||
BITSET_SET(multiple, v);
|
||||
else
|
||||
uses[v] = I;
|
||||
}
|
||||
|
||||
void
|
||||
agx_optimizer_backward(agx_context *ctx)
|
||||
{
|
||||
agx_instr **uses = calloc(ctx->alloc, sizeof(*uses));
|
||||
BITSET_WORD *multiple = BITSET_CALLOC(ctx->alloc);
|
||||
|
||||
agx_foreach_block_rev(ctx, block) {
|
||||
/* Phi sources are logically read at the end of predecessor, so process
|
||||
* our source in our successors' phis firsts. This ensures we set
|
||||
* `multiple` correctly with phi sources.
|
||||
*/
|
||||
agx_foreach_successor(block, succ) {
|
||||
unsigned s = agx_predecessor_index(succ, block);
|
||||
|
||||
agx_foreach_phi_in_block(succ, phi) {
|
||||
record_use(uses, multiple, phi, s);
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_instr_in_block_rev(block, I) {
|
||||
struct agx_opcode_info info = agx_opcodes_info[I->op];
|
||||
|
||||
/* Skip phis, they're handled specially */
|
||||
if (I->op == AGX_OPCODE_PHI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
record_use(uses, multiple, I, s);
|
||||
}
|
||||
|
||||
if (info.nr_dests != 1)
|
||||
continue;
|
||||
|
||||
if (I->dest[0].type != AGX_INDEX_NORMAL)
|
||||
continue;
|
||||
|
||||
agx_instr *use = uses[I->dest[0].value];
|
||||
|
||||
if (!use || BITSET_TEST(multiple, I->dest[0].value))
|
||||
continue;
|
||||
|
||||
if (agx_optimizer_not(I, use)) {
|
||||
agx_remove_instruction(use);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Destination has a single use, try to propagate */
|
||||
if (info.is_float && agx_optimizer_fmov_rev(I, use)) {
|
||||
agx_remove_instruction(use);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(uses);
|
||||
free(multiple);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_compile.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/* Table describing the relationship between registers pressure and thread
|
||||
* count. Each entry describes a maximum number of registers and the associated
|
||||
* best-case thread count.
|
||||
*
|
||||
* Sorted in ascending order of maximum registers for easy lookup.
|
||||
*/
|
||||
static const struct agx_occupancy occupancies[] = {
|
||||
{104, 1024}, {112, 896}, {128, 832}, {136, 768}, {144, 704},
|
||||
{160, 640}, {184, 576}, {208, 512}, {232, 448}, {256, 384},
|
||||
};
|
||||
|
||||
struct agx_occupancy
|
||||
agx_occupancy_for_register_count(unsigned halfregs)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(occupancies); ++i) {
|
||||
unsigned max = occupancies[i].max_registers;
|
||||
assert((i == 0 || max > occupancies[i - 1].max_registers) && "ascending");
|
||||
|
||||
if (halfregs <= max)
|
||||
return occupancies[i];
|
||||
}
|
||||
|
||||
UNREACHABLE("Register count must be less than the maximum");
|
||||
}
|
||||
|
||||
unsigned
|
||||
agx_max_registers_for_occupancy(unsigned occupancy)
|
||||
{
|
||||
unsigned max_regs = 0;
|
||||
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(occupancies); ++i) {
|
||||
if (occupancy <= occupancies[i].max_threads)
|
||||
max_regs = occupancies[i].max_registers;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
assert(max_regs > 0 && "Thread count must be less than the maximum");
|
||||
return max_regs;
|
||||
}
|
||||
|
||||
/* Crude cycle model for G13G */
|
||||
enum alu_unit {
|
||||
NONE,
|
||||
SCIB,
|
||||
IC,
|
||||
F32,
|
||||
F16,
|
||||
};
|
||||
|
||||
struct alu_timing {
|
||||
enum alu_unit unit;
|
||||
unsigned latency;
|
||||
unsigned tp;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
struct alu_timing op_timings[] = {
|
||||
[AGX_OPCODE_FFMA] = { F32, 2, 1 },
|
||||
[AGX_OPCODE_FADD] = { F32, 2, 1 },
|
||||
[AGX_OPCODE_FMUL] = { F32, 2, 1 },
|
||||
|
||||
[AGX_OPCODE_HFMA] = { F16, 2, 1 },
|
||||
[AGX_OPCODE_HADD] = { F16, 2, 1 },
|
||||
[AGX_OPCODE_HMUL] = { F16, 2, 1 },
|
||||
|
||||
[AGX_OPCODE_MOV_IMM] = { SCIB, 1, 1 },
|
||||
[AGX_OPCODE_BITOP] = { SCIB, 2, 1 }, /* tp might be 2 for 32-bit / no $? */
|
||||
[AGX_OPCODE_ICMPSEL] = { SCIB, 2, 1 },
|
||||
[AGX_OPCODE_FCMPSEL] = { SCIB, 2, 1 },
|
||||
[AGX_OPCODE_IADD] = { SCIB, 2, 1 },
|
||||
|
||||
[AGX_OPCODE_GET_SR] = { SCIB, 2, 2 },
|
||||
[AGX_OPCODE_GET_SR_BARRIER] = { SCIB, 2, 2 },
|
||||
[AGX_OPCODE_GET_SR_COVERAGE] = { SCIB, 2, 2 },
|
||||
|
||||
[AGX_OPCODE_IMAD] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_BFI] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_BFEIL] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_EXTR] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_ASR] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_FLOOR] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_SIN_PT_1] = { IC, 3, 2 },
|
||||
[AGX_OPCODE_SIN_PT_2] = { IC, 5, 2 },
|
||||
[AGX_OPCODE_LOG2] = { IC, 5, 2 },
|
||||
[AGX_OPCODE_EXP2] = { IC, 5, 2 },
|
||||
[AGX_OPCODE_RCP] = { IC, 5, 3 },
|
||||
[AGX_OPCODE_RSQRT] = { IC, 6, 4 },
|
||||
[AGX_OPCODE_SRSQRT] = { IC, 6, 4 },
|
||||
|
||||
// XXX: check this
|
||||
[AGX_OPCODE_SIMD_PREFIX] = { SCIB, 18, 18 },
|
||||
[AGX_OPCODE_SIMD_REDUCE] = { SCIB, 24, 24 },
|
||||
[AGX_OPCODE_QUAD_PREFIX] = { SCIB, 18, 18 },
|
||||
[AGX_OPCODE_QUAD_REDUCE] = { SCIB, 24, 24 },
|
||||
[AGX_OPCODE_SHUFFLE] = { SCIB, 5, 2 },
|
||||
[AGX_OPCODE_QUAD_SHUFFLE] = { SCIB, 5, 2 },
|
||||
|
||||
[AGX_OPCODE_ICMP_BALLOT] = { SCIB, 5, 2 },
|
||||
[AGX_OPCODE_FCMP_BALLOT] = { SCIB, 5, 2 },
|
||||
[AGX_OPCODE_ICMP_QUAD_BALLOT] = { SCIB, 4, 2 },
|
||||
[AGX_OPCODE_FCMP_QUAD_BALLOT] = { SCIB, 4, 2 },
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
static struct alu_timing
|
||||
alu_timing(const agx_instr *I)
|
||||
{
|
||||
return I->op < ARRAY_SIZE(op_timings) ? op_timings[I->op]
|
||||
: (struct alu_timing){0};
|
||||
}
|
||||
|
||||
bool
|
||||
agx_is_alu(const agx_instr *I)
|
||||
{
|
||||
return alu_timing(I).unit != NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Model non-ALU instructions, latency, register cache, 64-bit, etc.
|
||||
*/
|
||||
struct agx_cycle_estimate
|
||||
agx_estimate_cycles(agx_context *ctx)
|
||||
{
|
||||
struct agx_cycle_estimate est = {0};
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
struct alu_timing alu = alu_timing(I);
|
||||
|
||||
if (alu.unit == IC) {
|
||||
est.ic += alu.tp * 2;
|
||||
|
||||
/* In addition to the IC work, IC ops appear to be dispatched along
|
||||
* with SCIB.
|
||||
*/
|
||||
est.f_scib += 1;
|
||||
} else if (alu.unit) {
|
||||
est.f_scib += alu.tp;
|
||||
} else {
|
||||
/* TODO */
|
||||
}
|
||||
}
|
||||
|
||||
/* IC and F/SCIB run in parallel across warps */
|
||||
est.alu = MAX2(est.ic, est.f_scib);
|
||||
return est;
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* Copyright 2022 Collabora Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/* Bottom-up local scheduler to reduce register pressure */
|
||||
|
||||
#include "util/dag.h"
|
||||
#include "util/sparse_bitset.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
struct sched_ctx {
|
||||
/* Dependency graph */
|
||||
struct dag *dag;
|
||||
|
||||
/* Live set */
|
||||
struct u_sparse_bitset live;
|
||||
};
|
||||
|
||||
struct sched_node {
|
||||
struct dag_node dag;
|
||||
|
||||
/* Instruction this node represents */
|
||||
agx_instr *instr;
|
||||
};
|
||||
|
||||
static void
|
||||
add_dep(struct sched_node *a, struct sched_node *b)
|
||||
{
|
||||
assert(a != b && "no self-dependencies");
|
||||
|
||||
if (a && b)
|
||||
dag_add_edge(&a->dag, &b->dag, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
serialize(struct sched_node *a, struct sched_node **b)
|
||||
{
|
||||
add_dep(a, *b);
|
||||
*b = a;
|
||||
}
|
||||
|
||||
static struct dag *
|
||||
create_dag(agx_context *ctx, agx_block *block, void *memctx)
|
||||
{
|
||||
struct dag *dag = dag_create(ctx);
|
||||
|
||||
struct sched_node **last_write =
|
||||
calloc(ctx->alloc, sizeof(struct sched_node *));
|
||||
struct sched_node *coverage = NULL;
|
||||
struct sched_node *preload = NULL;
|
||||
|
||||
/* Last memory load, to serialize stores against */
|
||||
struct sched_node *memory_load = NULL;
|
||||
|
||||
/* Last memory store, to serialize loads and stores against */
|
||||
struct sched_node *memory_store = NULL;
|
||||
|
||||
agx_foreach_instr_in_block(block, I) {
|
||||
/* Don't touch control flow */
|
||||
if (instr_after_logical_end(I))
|
||||
break;
|
||||
|
||||
struct sched_node *node = rzalloc(memctx, struct sched_node);
|
||||
node->instr = I;
|
||||
dag_init_node(dag, &node->dag);
|
||||
|
||||
/* Reads depend on writes, no other hazards in SSA */
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
add_dep(node, last_write[I->src[s].value]);
|
||||
}
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
assert(I->dest[d].value < ctx->alloc);
|
||||
last_write[I->dest[d].value] = node;
|
||||
}
|
||||
|
||||
/* Classify the instruction and add dependencies according to the class */
|
||||
enum agx_schedule_class dep = agx_opcodes_info[I->op].schedule_class;
|
||||
assert(dep != AGX_SCHEDULE_CLASS_INVALID && "invalid instruction seen");
|
||||
|
||||
bool barrier = dep == AGX_SCHEDULE_CLASS_BARRIER;
|
||||
bool discards =
|
||||
I->op == AGX_OPCODE_SAMPLE_MASK || I->op == AGX_OPCODE_ZS_EMIT;
|
||||
|
||||
if (dep == AGX_SCHEDULE_CLASS_STORE)
|
||||
add_dep(node, memory_load);
|
||||
else if (dep == AGX_SCHEDULE_CLASS_ATOMIC || barrier)
|
||||
serialize(node, &memory_load);
|
||||
|
||||
if (dep == AGX_SCHEDULE_CLASS_LOAD || dep == AGX_SCHEDULE_CLASS_STORE ||
|
||||
dep == AGX_SCHEDULE_CLASS_ATOMIC || barrier)
|
||||
serialize(node, &memory_store);
|
||||
|
||||
if (dep == AGX_SCHEDULE_CLASS_COVERAGE || barrier)
|
||||
serialize(node, &coverage);
|
||||
|
||||
/* Make sure side effects happen before a discard */
|
||||
if (discards)
|
||||
add_dep(node, memory_store);
|
||||
|
||||
if (dep == AGX_SCHEDULE_CLASS_PRELOAD)
|
||||
serialize(node, &preload);
|
||||
else
|
||||
add_dep(node, preload);
|
||||
}
|
||||
|
||||
free(last_write);
|
||||
|
||||
return dag;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the change in register pressure from scheduling a given
|
||||
* instruction. Equivalently, calculate the difference in the number of live
|
||||
* registers before and after the instruction, given the live set after the
|
||||
* instruction. This calculation follows immediately from the dataflow
|
||||
* definition of liveness:
|
||||
*
|
||||
* live_in = (live_out - KILL) + GEN
|
||||
*/
|
||||
static signed
|
||||
calculate_pressure_delta(agx_instr *I, struct u_sparse_bitset *live)
|
||||
{
|
||||
signed delta = 0;
|
||||
|
||||
/* Destinations must be unique */
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
if (u_sparse_bitset_test(live, I->dest[d].value))
|
||||
delta -= agx_index_size_16(I->dest[d]);
|
||||
}
|
||||
|
||||
agx_foreach_ssa_src(I, src) {
|
||||
/* Filter duplicates */
|
||||
bool dupe = false;
|
||||
|
||||
for (unsigned i = 0; i < src; ++i) {
|
||||
if (agx_is_equiv(I->src[i], I->src[src])) {
|
||||
dupe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dupe && !u_sparse_bitset_test(live, I->src[src].value))
|
||||
delta += agx_index_size_16(I->src[src]);
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
/*
|
||||
* Choose the next instruction, bottom-up. For now we use a simple greedy
|
||||
* heuristic: choose the instruction that has the best effect on liveness, while
|
||||
* hoisting sample_mask.
|
||||
*/
|
||||
static struct sched_node *
|
||||
choose_instr(struct sched_ctx *s)
|
||||
{
|
||||
int32_t min_delta = INT32_MAX;
|
||||
struct sched_node *best = NULL;
|
||||
|
||||
list_for_each_entry(struct sched_node, n, &s->dag->heads, dag.link) {
|
||||
/* Heuristic: hoist sample_mask/zs_emit. This allows depth/stencil tests
|
||||
* to run earlier, and potentially to discard the entire quad invocation
|
||||
* earlier, reducing how much redundant fragment shader we run.
|
||||
*
|
||||
* Since we schedule backwards, we make that happen by only choosing
|
||||
* sample_mask when all other instructions have been exhausted.
|
||||
*/
|
||||
if (n->instr->op == AGX_OPCODE_SAMPLE_MASK ||
|
||||
n->instr->op == AGX_OPCODE_ZS_EMIT) {
|
||||
|
||||
if (!best) {
|
||||
best = n;
|
||||
assert(min_delta == INT32_MAX);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Heuristic: sink wait_pix to increase parallelism. Since wait_pix does
|
||||
* not read or write registers, this has no effect on pressure.
|
||||
*/
|
||||
if (n->instr->op == AGX_OPCODE_WAIT_PIX)
|
||||
return n;
|
||||
|
||||
int32_t delta = calculate_pressure_delta(n->instr, &s->live);
|
||||
|
||||
if (delta < min_delta) {
|
||||
best = n;
|
||||
min_delta = delta;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
static void
|
||||
pressure_schedule_block(agx_context *ctx, agx_block *block, struct sched_ctx *s)
|
||||
{
|
||||
/* off by a constant, that's ok */
|
||||
signed pressure = 0;
|
||||
signed orig_max_pressure = 0;
|
||||
unsigned nr_ins = 0;
|
||||
|
||||
u_sparse_bitset_dup(&s->live, &block->live_out);
|
||||
|
||||
agx_foreach_instr_in_block_rev(block, I) {
|
||||
pressure += calculate_pressure_delta(I, &s->live);
|
||||
orig_max_pressure = MAX2(pressure, orig_max_pressure);
|
||||
agx_liveness_ins_update(&s->live, I);
|
||||
nr_ins++;
|
||||
}
|
||||
|
||||
u_sparse_bitset_dup(&s->live, &block->live_out);
|
||||
|
||||
/* off by a constant, that's ok */
|
||||
signed max_pressure = 0;
|
||||
pressure = 0;
|
||||
|
||||
struct sched_node **schedule = calloc(nr_ins, sizeof(struct sched_node *));
|
||||
nr_ins = 0;
|
||||
|
||||
while (!list_is_empty(&s->dag->heads)) {
|
||||
struct sched_node *node = choose_instr(s);
|
||||
pressure += calculate_pressure_delta(node->instr, &s->live);
|
||||
max_pressure = MAX2(pressure, max_pressure);
|
||||
dag_prune_head(s->dag, &node->dag);
|
||||
|
||||
schedule[nr_ins++] = node;
|
||||
agx_liveness_ins_update(&s->live, node->instr);
|
||||
}
|
||||
|
||||
/* Bail if it looks like it's worse */
|
||||
if (max_pressure >= orig_max_pressure) {
|
||||
free(schedule);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Apply the schedule */
|
||||
for (unsigned i = 0; i < nr_ins; ++i) {
|
||||
agx_remove_instruction(schedule[i]->instr);
|
||||
list_add(&schedule[i]->instr->link, &block->instructions);
|
||||
}
|
||||
|
||||
free(schedule);
|
||||
}
|
||||
|
||||
void
|
||||
agx_pressure_schedule(agx_context *ctx)
|
||||
{
|
||||
agx_compute_liveness(ctx);
|
||||
void *memctx = ralloc_context(ctx);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
struct sched_ctx sctx = {.dag = create_dag(ctx, block, memctx)};
|
||||
u_sparse_bitset_init(&sctx.live, ctx->alloc, memctx);
|
||||
|
||||
pressure_schedule_block(ctx, block, &sctx);
|
||||
}
|
||||
|
||||
/* Clean up after liveness analysis */
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s)
|
||||
I->src[s].kill = false;
|
||||
}
|
||||
|
||||
ralloc_free(memctx);
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* Copyright 2019-2020 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/lut.h"
|
||||
#include "util/u_math.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
static void
|
||||
agx_print_sized(char prefix, unsigned value, enum agx_size size, FILE *fp)
|
||||
{
|
||||
switch (size) {
|
||||
case AGX_SIZE_16:
|
||||
fprintf(fp, "%c%u%c", prefix, value >> 1, (value & 1) ? 'h' : 'l');
|
||||
return;
|
||||
case AGX_SIZE_32:
|
||||
assert((value & 1) == 0);
|
||||
fprintf(fp, "%c%u", prefix, value >> 1);
|
||||
return;
|
||||
case AGX_SIZE_64:
|
||||
assert((value & 1) == 0);
|
||||
fprintf(fp, "%c%u:%c%u", prefix, value >> 1, prefix, (value >> 1) + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
UNREACHABLE("Invalid size");
|
||||
}
|
||||
|
||||
static void
|
||||
agx_print_reg(agx_index index, unsigned value, FILE *fp)
|
||||
{
|
||||
agx_print_sized('r', value, index.size, fp);
|
||||
|
||||
if (agx_channels(index) > 1) {
|
||||
unsigned last =
|
||||
value + agx_size_align_16(index.size) * (agx_channels(index) - 1);
|
||||
|
||||
fprintf(fp, "...");
|
||||
|
||||
if (index.memory)
|
||||
fprintf(fp, "m");
|
||||
agx_print_sized('r', last, index.size, fp);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_print_index(agx_index index, bool is_float, FILE *fp)
|
||||
{
|
||||
if (index.memory)
|
||||
fprintf(fp, "m");
|
||||
|
||||
switch (index.type) {
|
||||
case AGX_INDEX_NULL:
|
||||
fprintf(fp, "_");
|
||||
return;
|
||||
|
||||
case AGX_INDEX_NORMAL:
|
||||
if (index.kill)
|
||||
fprintf(fp, "*");
|
||||
|
||||
fprintf(fp, "%u", index.value);
|
||||
break;
|
||||
|
||||
case AGX_INDEX_IMMEDIATE:
|
||||
if (is_float) {
|
||||
assert(index.value < 0x100);
|
||||
fprintf(fp, "#%f", agx_minifloat_decode(index.value));
|
||||
} else {
|
||||
fprintf(fp, "#%u", index.value);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AGX_INDEX_UNDEF:
|
||||
fprintf(fp, "undef");
|
||||
break;
|
||||
|
||||
case AGX_INDEX_UNIFORM:
|
||||
agx_print_sized('u', index.value, index.size, fp);
|
||||
break;
|
||||
|
||||
case AGX_INDEX_REGISTER:
|
||||
if (index.cache)
|
||||
fprintf(fp, "$");
|
||||
|
||||
if (index.discard)
|
||||
fprintf(fp, "^");
|
||||
|
||||
agx_print_reg(index, index.value, fp);
|
||||
break;
|
||||
|
||||
default:
|
||||
UNREACHABLE("Invalid index type");
|
||||
}
|
||||
|
||||
if (index.type == AGX_INDEX_NORMAL) {
|
||||
/* Print length suffixes if not implied */
|
||||
if (index.size == AGX_SIZE_16)
|
||||
fprintf(fp, "h");
|
||||
else if (index.size == AGX_SIZE_64)
|
||||
fprintf(fp, "d");
|
||||
|
||||
/* Print assigned register if we have one */
|
||||
if (index.has_reg) {
|
||||
fprintf(fp, "(");
|
||||
if (index.memory)
|
||||
fprintf(fp, "m");
|
||||
|
||||
agx_print_reg(index, index.reg, fp);
|
||||
fprintf(fp, ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (index.abs)
|
||||
fprintf(fp, ".abs");
|
||||
|
||||
if (index.neg)
|
||||
fprintf(fp, ".neg");
|
||||
}
|
||||
|
||||
static const char *lut2_to_function[16] = {
|
||||
"zero", "nor", "andn2", "not2", "andn1", "not1", "xor", "nand",
|
||||
"and", "xnor", "mov1", "orn2", "mov2", "orn1", "or", "one",
|
||||
};
|
||||
|
||||
void
|
||||
agx_print_instr(const agx_instr *I, FILE *fp)
|
||||
{
|
||||
assert(I->op < AGX_NUM_OPCODES);
|
||||
struct agx_opcode_info info = agx_opcodes_info[I->op];
|
||||
bool print_comma = false;
|
||||
|
||||
if (I->op == AGX_OPCODE_BITOP) {
|
||||
info.name = lut2_to_function[I->truth_table];
|
||||
info.immediates &= ~AGX_IMMEDIATE_TRUTH_TABLE;
|
||||
}
|
||||
|
||||
fprintf(fp, " ");
|
||||
|
||||
agx_foreach_dest(I, d) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
agx_print_index(I->dest[d], false, fp);
|
||||
}
|
||||
|
||||
if (I->nr_dests) {
|
||||
fprintf(fp, " = ");
|
||||
print_comma = false;
|
||||
}
|
||||
|
||||
fprintf(fp, "%s", info.name);
|
||||
|
||||
if (I->saturate)
|
||||
fprintf(fp, ".sat");
|
||||
|
||||
if (I->last)
|
||||
fprintf(fp, ".last");
|
||||
|
||||
fprintf(fp, " ");
|
||||
|
||||
agx_foreach_src(I, s) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
agx_print_index(I->src[s],
|
||||
agx_opcodes_info[I->op].is_float &&
|
||||
!(s >= 2 && I->op == AGX_OPCODE_FCMPSEL),
|
||||
fp);
|
||||
}
|
||||
|
||||
if (I->mask) {
|
||||
fprintf(fp, ", ");
|
||||
|
||||
for (unsigned i = 0; i < 4; ++i) {
|
||||
if (I->mask & (1 << i))
|
||||
fprintf(fp, "%c", "xyzw"[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Do better for enums, truth tables, etc */
|
||||
if (info.immediates) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
fprintf(fp, "#0x%" PRIx64, I->imm);
|
||||
if (I->imm && util_is_probably_float(I->imm)) {
|
||||
fprintf(fp, " /* %f */", uif(I->imm));
|
||||
}
|
||||
}
|
||||
|
||||
if (info.immediates & AGX_IMMEDIATE_DIM) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
fputs(agx_dim_as_str(I->dim), fp);
|
||||
}
|
||||
|
||||
if (info.immediates & AGX_IMMEDIATE_SCOREBOARD) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
fprintf(fp, "slot %u", I->scoreboard);
|
||||
}
|
||||
|
||||
if (info.immediates & AGX_IMMEDIATE_NEST) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
fprintf(fp, "n=%u", I->nest);
|
||||
}
|
||||
|
||||
if ((info.immediates & AGX_IMMEDIATE_INVERT_COND) && I->invert_cond) {
|
||||
if (print_comma)
|
||||
fprintf(fp, ", ");
|
||||
else
|
||||
print_comma = true;
|
||||
|
||||
fprintf(fp, "inv");
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
void
|
||||
agx_print_block(const agx_block *block, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "block%u {\n", block->index);
|
||||
|
||||
agx_foreach_instr_in_block(block, ins)
|
||||
agx_print_instr(ins, fp);
|
||||
|
||||
fprintf(fp, "}");
|
||||
|
||||
if (block->successors[0]) {
|
||||
fprintf(fp, " -> ");
|
||||
|
||||
agx_foreach_successor(block, succ)
|
||||
fprintf(fp, "block%u ", succ->index);
|
||||
}
|
||||
|
||||
if (block->predecessors.size) {
|
||||
fprintf(fp, " from");
|
||||
|
||||
agx_foreach_predecessor(block, pred)
|
||||
fprintf(fp, " block%u", (*pred)->index);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n\n");
|
||||
}
|
||||
|
||||
void
|
||||
agx_print_shader(const agx_context *ctx, FILE *fp)
|
||||
{
|
||||
agx_foreach_block(ctx, block)
|
||||
agx_print_block(block, fp);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2024 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Reindex SSA to reduce memory usage */
|
||||
|
||||
void
|
||||
agx_reindex_ssa(agx_context *ctx)
|
||||
{
|
||||
unsigned *remap = calloc(ctx->alloc, sizeof(*remap));
|
||||
|
||||
ctx->alloc = 0;
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
assert(!remap[I->dest[d].value] && "input is SSA");
|
||||
remap[I->dest[d].value] = ctx->alloc++;
|
||||
I->dest[d].value = remap[I->dest[d].value];
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
I->src[s].value = remap[I->src[s].value];
|
||||
}
|
||||
}
|
||||
|
||||
free(remap);
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* Copyright 2023 Valve Corporation
|
||||
* Copyright 2022 Collabora Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/*
|
||||
* Implementation of "Simple and Efficient
|
||||
* Construction of Static Single Assignment Form", also by Braun et al.
|
||||
* https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf
|
||||
*/
|
||||
|
||||
#include "util/hash_table.h"
|
||||
#include "util/ralloc.h"
|
||||
#include "util/u_dynarray.h"
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
struct repair_block {
|
||||
/* For a loop header, whether phis operands have been added */
|
||||
bool sealed;
|
||||
|
||||
/* Sparse map: variable name -> agx_index.
|
||||
*
|
||||
* Definition of a variable at the end of the block.
|
||||
*/
|
||||
struct hash_table_u64 *defs;
|
||||
};
|
||||
|
||||
struct repair_ctx {
|
||||
agx_context *shader;
|
||||
|
||||
/* Number of variables */
|
||||
unsigned n;
|
||||
|
||||
/* Information on blocks indexed in source order */
|
||||
struct repair_block *blocks;
|
||||
};
|
||||
|
||||
static inline struct repair_block *
|
||||
repair_block(struct repair_ctx *ctx, agx_block *block)
|
||||
{
|
||||
return &ctx->blocks[block->index];
|
||||
}
|
||||
|
||||
static void
|
||||
record_write(struct repair_ctx *ctx, agx_block *block, unsigned node,
|
||||
agx_index val)
|
||||
{
|
||||
assert(node < ctx->n);
|
||||
struct hash_table_u64 *defs = repair_block(ctx, block)->defs;
|
||||
_mesa_hash_table_u64_insert(defs, node,
|
||||
ralloc_memdup(defs, &val, sizeof(val)));
|
||||
}
|
||||
|
||||
static void add_phi_operands(struct repair_ctx *ctx, agx_block *block,
|
||||
agx_instr *phi, agx_index node);
|
||||
|
||||
static agx_index
|
||||
resolve_read(struct repair_ctx *ctx, agx_block *block, agx_index node)
|
||||
{
|
||||
struct repair_block *rb = repair_block(ctx, block);
|
||||
|
||||
/* Local value numbering */
|
||||
assert(node.type == AGX_INDEX_NORMAL);
|
||||
agx_index *local = _mesa_hash_table_u64_search(rb->defs, node.value);
|
||||
|
||||
if (local) {
|
||||
assert(!agx_is_null(*local));
|
||||
return *local;
|
||||
}
|
||||
|
||||
/* Global value numbering. readValueRecursive in the paper */
|
||||
unsigned nr_preds = agx_num_predecessors(block);
|
||||
agx_index val;
|
||||
|
||||
assert(nr_preds > 0);
|
||||
|
||||
/* Loop headers are not in the "sealedBlock" set in the paper. To
|
||||
* handle, we insert an incomplete phi to be filled in after the rest of
|
||||
* the loop is processed.
|
||||
*/
|
||||
if (block->loop_header && !rb->sealed) {
|
||||
val = agx_temp_like(ctx->shader, node);
|
||||
agx_builder b = agx_init_builder(ctx->shader, agx_before_block(block));
|
||||
agx_instr *phi = agx_phi_to(&b, val, nr_preds);
|
||||
phi->shadow = true;
|
||||
|
||||
/* Stash the variable in for an intrusive incompletePhis map */
|
||||
phi->imm = node.value + 1;
|
||||
} else if (nr_preds == 1) {
|
||||
/* No phi needed */
|
||||
agx_block *pred =
|
||||
*util_dynarray_element(&block->predecessors, agx_block *, 0);
|
||||
val = resolve_read(ctx, pred, node);
|
||||
} else {
|
||||
/* Insert phi first to break cycles */
|
||||
val = agx_temp_like(ctx->shader, node);
|
||||
agx_builder b = agx_init_builder(ctx->shader, agx_before_block(block));
|
||||
agx_instr *phi = agx_phi_to(&b, val, nr_preds);
|
||||
phi->shadow = true;
|
||||
record_write(ctx, block, node.value, val);
|
||||
add_phi_operands(ctx, block, phi, node);
|
||||
}
|
||||
|
||||
assert(!agx_is_null(val));
|
||||
record_write(ctx, block, node.value, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void
|
||||
add_phi_operands(struct repair_ctx *ctx, agx_block *block, agx_instr *phi,
|
||||
agx_index node)
|
||||
{
|
||||
/* Add phi operands */
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
unsigned s = agx_predecessor_index(block, *pred);
|
||||
phi->src[s] = resolve_read(ctx, *pred, node);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
seal_block(struct repair_ctx *ctx, agx_block *block)
|
||||
{
|
||||
agx_foreach_phi_in_block(block, phi) {
|
||||
/* We use phi->imm as a sideband to pass the variable name. */
|
||||
if (phi->imm) {
|
||||
agx_index var = agx_get_vec_index(phi->imm - 1, phi->dest[0].size,
|
||||
agx_channels(phi->dest[0]));
|
||||
var.memory = phi->dest[0].memory;
|
||||
add_phi_operands(ctx, block, phi, var);
|
||||
phi->imm = 0;
|
||||
}
|
||||
}
|
||||
|
||||
repair_block(ctx, block)->sealed = true;
|
||||
}
|
||||
|
||||
static void
|
||||
seal_loop_headers(struct repair_ctx *ctx, struct agx_block *exit)
|
||||
{
|
||||
agx_foreach_successor(exit, succ) {
|
||||
/* Only loop headers need to be sealed late */
|
||||
if (!succ->loop_header)
|
||||
continue;
|
||||
|
||||
/* Check if all predecessors have been processed */
|
||||
bool any_unprocessed = false;
|
||||
|
||||
agx_foreach_predecessor(succ, P) {
|
||||
if ((*P)->index > exit->index) {
|
||||
any_unprocessed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Seal once all predecessors are processed */
|
||||
if (!any_unprocessed)
|
||||
seal_block(ctx, succ);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
agx_opt_trivial_phi(agx_context *ctx)
|
||||
{
|
||||
agx_index *remap = calloc(ctx->alloc, sizeof(*remap));
|
||||
for (;;) {
|
||||
bool progress = false;
|
||||
memset(remap, 0, ctx->alloc * sizeof(*remap));
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
agx_foreach_phi_in_block_safe(block, phi) {
|
||||
agx_index same = agx_null();
|
||||
bool all_same = true;
|
||||
|
||||
agx_foreach_src(phi, s) {
|
||||
/* Only optimize trivial phis with normal sources. It is possible
|
||||
* to optimize something like `phi #0, #0` but...
|
||||
*
|
||||
* 1. It would inadvently propagate constants which may be
|
||||
* invalid. Copyprop knows the rules for this, but we don't here.
|
||||
*
|
||||
* 2. These trivial phis should be optimized at the NIR level.
|
||||
* This pass is just to clean up spilling.
|
||||
*
|
||||
* So skip them for correctness in case NIR misses something
|
||||
* (which can happen depending on pass order).
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* TODO: Handle cycles faster.
|
||||
*/
|
||||
if (phi->src[s].type != AGX_INDEX_NORMAL ||
|
||||
agx_is_null(remap[phi->src[s].value])) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Same value or self-reference */
|
||||
if (agx_is_equiv(phi->src[s], same) ||
|
||||
agx_is_equiv(phi->src[s], phi->dest[0]))
|
||||
continue;
|
||||
|
||||
if (!agx_is_null(same)) {
|
||||
all_same = false;
|
||||
break;
|
||||
}
|
||||
|
||||
same = phi->src[s];
|
||||
}
|
||||
|
||||
if (all_same) {
|
||||
remap[phi->dest[0].value] = same;
|
||||
agx_remove_instruction(phi);
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!progress)
|
||||
break;
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (!agx_is_null(remap[I->src[s].value])) {
|
||||
agx_replace_src(I, s, remap[I->src[s].value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(remap);
|
||||
}
|
||||
|
||||
void
|
||||
agx_repair_ssa(agx_context *ctx)
|
||||
{
|
||||
struct repair_block *blocks =
|
||||
rzalloc_array(NULL, struct repair_block, ctx->num_blocks);
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
struct repair_block *rb = &blocks[block->index];
|
||||
rb->defs = _mesa_hash_table_u64_create(blocks);
|
||||
}
|
||||
|
||||
unsigned n = ctx->alloc;
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
struct repair_ctx rctx = {
|
||||
.shader = ctx,
|
||||
.n = n,
|
||||
.blocks = blocks,
|
||||
};
|
||||
|
||||
agx_foreach_instr_in_block(block, I) {
|
||||
/* Repair SSA for the instruction */
|
||||
if (I->op != AGX_OPCODE_PHI) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
assert(I->src[s].value < n);
|
||||
agx_replace_src(I, s, resolve_read(&rctx, block, I->src[s]));
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
unsigned handle = I->dest[d].value;
|
||||
|
||||
/* Skip phis that we just created when processing loops */
|
||||
if (handle >= n) {
|
||||
assert(I->op == AGX_OPCODE_PHI);
|
||||
continue;
|
||||
}
|
||||
|
||||
I->dest[d] =
|
||||
agx_replace_index(I->dest[d], agx_temp_like(ctx, I->dest[d]));
|
||||
|
||||
record_write(&rctx, block, handle, I->dest[d]);
|
||||
}
|
||||
}
|
||||
|
||||
seal_loop_headers(&rctx, block);
|
||||
}
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
agx_foreach_phi_in_block(block, phi) {
|
||||
/* The kill bit is invalid (and meaningless) for phis. Liveness
|
||||
* analysis does not produce it. However, we're ingesting broken SSA
|
||||
* where we can have random kill bits set on phis. Strip them as part
|
||||
* of the SSA repair.
|
||||
*
|
||||
* The register allocator depends on this for correctness.
|
||||
*/
|
||||
phi->dest[0].kill = false;
|
||||
|
||||
agx_foreach_src(phi, s) {
|
||||
phi->src[s].kill = false;
|
||||
}
|
||||
|
||||
/* Skip the phis that we just created */
|
||||
if (phi->shadow) {
|
||||
phi->shadow = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
agx_foreach_ssa_src(phi, s) {
|
||||
/* Phis (uniquely) read their sources in their corresponding
|
||||
* predecessors, so chain through for that.
|
||||
*/
|
||||
agx_block *read_block =
|
||||
*util_dynarray_element(&block->predecessors, agx_block *, s);
|
||||
|
||||
assert(phi->src[s].value < n);
|
||||
|
||||
struct repair_ctx rctx = {
|
||||
.shader = ctx,
|
||||
.n = n,
|
||||
.blocks = blocks,
|
||||
};
|
||||
|
||||
agx_replace_src(phi, s,
|
||||
resolve_read(&rctx, read_block, phi->src[s]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ralloc_free(blocks);
|
||||
|
||||
agx_opt_trivial_phi(ctx);
|
||||
agx_reindex_ssa(ctx);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "util/compiler.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_debug.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/* Validatation doesn't make sense in release builds */
|
||||
#ifndef NDEBUG
|
||||
|
||||
#define agx_validate_assert(stmt) \
|
||||
if (!(stmt)) { \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/*
|
||||
* If a block contains phi nodes, they must come at the start of the block. If a
|
||||
* block contains control flow, it must come at the beginning/end as applicable.
|
||||
* Therefore the form of a valid block is:
|
||||
*
|
||||
* Control flow instructions (else)
|
||||
* Phi nodes
|
||||
* General instructions
|
||||
* Control flow instructions (except else)
|
||||
*
|
||||
* Validate that this form is satisfied.
|
||||
*/
|
||||
enum agx_block_state {
|
||||
AGX_BLOCK_STATE_CF_ELSE = 0,
|
||||
AGX_BLOCK_STATE_PHI = 1,
|
||||
AGX_BLOCK_STATE_BODY = 2,
|
||||
AGX_BLOCK_STATE_CF = 3
|
||||
};
|
||||
|
||||
static bool
|
||||
agx_validate_block_form(agx_block *block)
|
||||
{
|
||||
enum agx_block_state state = AGX_BLOCK_STATE_CF_ELSE;
|
||||
|
||||
agx_foreach_instr_in_block(block, I) {
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_PRELOAD:
|
||||
case AGX_OPCODE_ELSE_ICMP:
|
||||
case AGX_OPCODE_ELSE_FCMP:
|
||||
agx_validate_assert(state == AGX_BLOCK_STATE_CF_ELSE);
|
||||
break;
|
||||
|
||||
case AGX_OPCODE_PHI:
|
||||
agx_validate_assert(state == AGX_BLOCK_STATE_CF_ELSE ||
|
||||
state == AGX_BLOCK_STATE_PHI);
|
||||
|
||||
state = AGX_BLOCK_STATE_PHI;
|
||||
break;
|
||||
|
||||
case AGX_OPCODE_EXPORT:
|
||||
agx_validate_assert(agx_num_successors(block) == 0);
|
||||
state = AGX_BLOCK_STATE_CF;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (instr_after_logical_end(I)) {
|
||||
state = AGX_BLOCK_STATE_CF;
|
||||
} else {
|
||||
agx_validate_assert(state != AGX_BLOCK_STATE_CF);
|
||||
state = AGX_BLOCK_STATE_BODY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only moves and phis use stack. Phis cannot use moves due to their
|
||||
* parallel nature, so we allow phis to take memory, later lowered to moves.
|
||||
*/
|
||||
static bool
|
||||
is_stack_valid(agx_instr *I)
|
||||
{
|
||||
return (I->op == AGX_OPCODE_MOV) || (I->op == AGX_OPCODE_PHI);
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_validate_sources(agx_instr *I)
|
||||
{
|
||||
agx_foreach_src(I, s) {
|
||||
agx_index src = I->src[s];
|
||||
|
||||
if (src.type == AGX_INDEX_IMMEDIATE) {
|
||||
agx_validate_assert(!src.kill);
|
||||
agx_validate_assert(!src.cache);
|
||||
agx_validate_assert(!src.discard);
|
||||
|
||||
bool ldst = agx_allows_16bit_immediate(I);
|
||||
|
||||
/* Immediates are encoded as 8-bit (16-bit for memory load/store). For
|
||||
* integers, they extend to 16-bit. For floating point, they are 8-bit
|
||||
* minifloats. The 8-bit minifloats are a strict subset of 16-bit
|
||||
* standard floats, so we treat them as such in the IR, with an
|
||||
* implicit f16->f32 for 32-bit floating point operations.
|
||||
*/
|
||||
agx_validate_assert(src.size == AGX_SIZE_16);
|
||||
agx_validate_assert(src.value < (1 << (ldst ? 16 : 8)));
|
||||
} else if (I->op == AGX_OPCODE_COLLECT && !agx_is_null(src)) {
|
||||
agx_validate_assert(src.size == I->src[0].size);
|
||||
} else if (I->op == AGX_OPCODE_PHI) {
|
||||
agx_validate_assert(src.size == I->dest[0].size);
|
||||
agx_validate_assert(!agx_is_null(src));
|
||||
}
|
||||
|
||||
agx_validate_assert(!src.memory || is_stack_valid(I));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_validate_defs(agx_instr *I, BITSET_WORD *defs)
|
||||
{
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
/* Skip phis, they're special in loop headers */
|
||||
if (I->op == AGX_OPCODE_PHI)
|
||||
break;
|
||||
|
||||
/* Sources must be defined before their use */
|
||||
if (!BITSET_TEST(defs, I->src[s].value))
|
||||
return false;
|
||||
}
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
/* Static single assignment */
|
||||
if (BITSET_TEST(defs, I->dest[d].value))
|
||||
return false;
|
||||
|
||||
BITSET_SET(defs, I->dest[d].value);
|
||||
|
||||
if (I->dest[d].memory && !is_stack_valid(I))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Returns number of registers written by an instruction */
|
||||
static unsigned
|
||||
agx_write_registers(const agx_instr *I, unsigned d)
|
||||
{
|
||||
unsigned size = agx_size_align_16(I->dest[d].size);
|
||||
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_MOV:
|
||||
case AGX_OPCODE_PHI:
|
||||
/* Tautological */
|
||||
return agx_index_size_16(I->dest[d]);
|
||||
|
||||
case AGX_OPCODE_ITER:
|
||||
case AGX_OPCODE_ITERPROJ:
|
||||
assert(1 <= I->channels && I->channels <= 4);
|
||||
return I->channels * size;
|
||||
|
||||
case AGX_OPCODE_IMAGE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_SAMPLE:
|
||||
/* Even when masked out, these clobber 4 registers.
|
||||
*
|
||||
* TODO: Figure out the sparse interaction.
|
||||
*/
|
||||
return (I->sparse ? 8 : 4) * size;
|
||||
|
||||
case AGX_OPCODE_DEVICE_LOAD:
|
||||
case AGX_OPCODE_LOCAL_LOAD:
|
||||
case AGX_OPCODE_STACK_LOAD:
|
||||
case AGX_OPCODE_LD_TILE:
|
||||
/* Can write 16-bit or 32-bit. Anything logically 64-bit is already
|
||||
* expanded to 32-bit in the mask.
|
||||
*/
|
||||
return util_bitcount(I->mask) * MIN2(size, 2);
|
||||
|
||||
case AGX_OPCODE_LDCF:
|
||||
return 6;
|
||||
case AGX_OPCODE_COLLECT:
|
||||
return I->nr_srcs * agx_size_align_16(I->src[0].size);
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
struct dim_info {
|
||||
unsigned comps;
|
||||
bool array;
|
||||
};
|
||||
|
||||
static struct dim_info
|
||||
agx_dim_info(enum agx_dim dim)
|
||||
{
|
||||
switch (dim) {
|
||||
case AGX_DIM_1D:
|
||||
return (struct dim_info){1, false};
|
||||
case AGX_DIM_1D_ARRAY:
|
||||
return (struct dim_info){1, true};
|
||||
case AGX_DIM_2D:
|
||||
return (struct dim_info){2, false};
|
||||
case AGX_DIM_2D_ARRAY:
|
||||
return (struct dim_info){2, true};
|
||||
case AGX_DIM_2D_MS:
|
||||
return (struct dim_info){3, false};
|
||||
case AGX_DIM_3D:
|
||||
return (struct dim_info){3, false};
|
||||
case AGX_DIM_CUBE:
|
||||
return (struct dim_info){3, false};
|
||||
case AGX_DIM_CUBE_ARRAY:
|
||||
return (struct dim_info){3, true};
|
||||
case AGX_DIM_2D_MS_ARRAY:
|
||||
return (struct dim_info){2, true};
|
||||
default:
|
||||
UNREACHABLE("invalid dim");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return number of registers required for coordinates for a texture/image
|
||||
* instruction. We handle layer + sample index as 32-bit even when only the
|
||||
* lower 16-bits are present. LOD queries do not take a layer.
|
||||
*/
|
||||
static unsigned
|
||||
agx_coordinate_registers(const agx_instr *I, enum agx_size size)
|
||||
{
|
||||
struct dim_info dim = agx_dim_info(I->dim);
|
||||
bool has_array = !I->query_lod;
|
||||
|
||||
return agx_size_align_16(size) * (dim.comps + (has_array && dim.array));
|
||||
}
|
||||
|
||||
static unsigned
|
||||
agx_read_registers(const agx_instr *I, unsigned s)
|
||||
{
|
||||
unsigned size = agx_size_align_16(I->src[s].size);
|
||||
|
||||
switch (I->op) {
|
||||
case AGX_OPCODE_MOV:
|
||||
case AGX_OPCODE_EXPORT:
|
||||
/* Tautological */
|
||||
return agx_index_size_16(I->src[0]);
|
||||
|
||||
case AGX_OPCODE_PHI:
|
||||
if (I->src[s].type == AGX_INDEX_IMMEDIATE)
|
||||
return size;
|
||||
else
|
||||
return agx_index_size_16(I->dest[0]);
|
||||
|
||||
case AGX_OPCODE_SPLIT:
|
||||
return I->nr_dests * agx_size_align_16(agx_split_width(I));
|
||||
|
||||
case AGX_OPCODE_UNIFORM_STORE:
|
||||
if (s == 0)
|
||||
return util_bitcount(I->mask) * size;
|
||||
else
|
||||
return size;
|
||||
|
||||
case AGX_OPCODE_DEVICE_STORE:
|
||||
case AGX_OPCODE_LOCAL_STORE:
|
||||
case AGX_OPCODE_STACK_STORE:
|
||||
case AGX_OPCODE_ST_TILE:
|
||||
/* See agx_write_registers */
|
||||
if (s == 0)
|
||||
return util_bitcount(I->mask) * MIN2(size, 2);
|
||||
else if (s == 2 && I->explicit_coords)
|
||||
return 2;
|
||||
else
|
||||
return size;
|
||||
|
||||
case AGX_OPCODE_ZS_EMIT:
|
||||
if (s == 1) {
|
||||
/* Depth (bit 0) is fp32, stencil (bit 1) is u16 in the hw but we pad
|
||||
* up to u32 for simplicity
|
||||
*/
|
||||
bool z = !!(I->zs & 1);
|
||||
bool s = !!(I->zs & 2);
|
||||
assert(z || s);
|
||||
|
||||
return (z && s) ? 4 : z ? 2 : 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
case AGX_OPCODE_IMAGE_WRITE:
|
||||
if (s == 0)
|
||||
return 4 * size /* data */;
|
||||
else if (s == 1)
|
||||
return agx_coordinate_registers(I, I->src[1].size);
|
||||
else
|
||||
return size;
|
||||
|
||||
case AGX_OPCODE_IMAGE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_LOAD:
|
||||
case AGX_OPCODE_TEXTURE_SAMPLE:
|
||||
if (s == 0) {
|
||||
return agx_coordinate_registers(I, I->src[0].size);
|
||||
} else if (s == 1) {
|
||||
/* LOD */
|
||||
if (I->lod_mode == AGX_LOD_MODE_LOD_GRAD ||
|
||||
I->lod_mode == AGX_LOD_MODE_LOD_GRAD_MIN) {
|
||||
|
||||
/* Technically only 16-bit but we model as 32-bit to keep the IR
|
||||
* simple, since the gradient is otherwise 32-bit.
|
||||
*/
|
||||
unsigned min = I->lod_mode == AGX_LOD_MODE_LOD_GRAD_MIN ? 2 : 0;
|
||||
|
||||
switch (I->dim) {
|
||||
case AGX_DIM_1D:
|
||||
case AGX_DIM_1D_ARRAY:
|
||||
return (2 * 2 * 1) + min;
|
||||
case AGX_DIM_2D:
|
||||
case AGX_DIM_2D_ARRAY:
|
||||
case AGX_DIM_2D_MS_ARRAY:
|
||||
case AGX_DIM_2D_MS:
|
||||
return (2 * 2 * 2) + min;
|
||||
case AGX_DIM_CUBE:
|
||||
case AGX_DIM_CUBE_ARRAY:
|
||||
case AGX_DIM_3D:
|
||||
return (2 * 2 * 3) + min;
|
||||
}
|
||||
|
||||
UNREACHABLE("Invalid texture dimension");
|
||||
} else if (I->lod_mode == AGX_LOD_MODE_AUTO_LOD_BIAS_MIN) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else if (s == 5) {
|
||||
/* Compare/offset */
|
||||
return 2 * ((!!I->shadow) + (!!I->offset));
|
||||
} else {
|
||||
return size;
|
||||
}
|
||||
|
||||
case AGX_OPCODE_BLOCK_IMAGE_STORE:
|
||||
if (s == 3 && I->explicit_coords)
|
||||
return agx_coordinate_registers(I, I->src[3].size);
|
||||
else
|
||||
return size;
|
||||
|
||||
case AGX_OPCODE_ATOMIC:
|
||||
case AGX_OPCODE_LOCAL_ATOMIC:
|
||||
if (s == 0 && I->atomic_opc == AGX_ATOMIC_OPC_CMPXCHG)
|
||||
return size * 2;
|
||||
else
|
||||
return size;
|
||||
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Type check the dimensionality of sources and destinations. */
|
||||
static bool
|
||||
agx_validate_width(agx_context *ctx)
|
||||
{
|
||||
bool succ = true;
|
||||
enum agx_size *sizes = calloc(ctx->alloc, sizeof(*sizes));
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_dest(I, d) {
|
||||
unsigned exp = agx_write_registers(I, d);
|
||||
unsigned act =
|
||||
agx_channels(I->dest[d]) * agx_size_align_16(I->dest[d].size);
|
||||
|
||||
if (exp != act) {
|
||||
succ = false;
|
||||
fprintf(stderr, "destination %u, expected width %u, got width %u\n",
|
||||
d, exp, act);
|
||||
agx_print_instr(I, stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
if (I->dest[d].type == AGX_INDEX_NORMAL)
|
||||
sizes[I->dest[d].value] = I->dest[d].size;
|
||||
}
|
||||
|
||||
agx_foreach_src(I, s) {
|
||||
if (I->src[s].type == AGX_INDEX_NULL)
|
||||
continue;
|
||||
|
||||
unsigned exp = agx_read_registers(I, s);
|
||||
unsigned act =
|
||||
agx_channels(I->src[s]) * agx_size_align_16(I->src[s].size);
|
||||
|
||||
if (exp != act) {
|
||||
succ = false;
|
||||
fprintf(stderr, "source %u, expected width %u, got width %u\n", s,
|
||||
exp, act);
|
||||
agx_print_instr(I, stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check sources after all defs processed for proper backedge handling */
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
if (sizes[I->src[s].value] != I->src[s].size) {
|
||||
succ = false;
|
||||
fprintf(stderr, "source %u, expected el size %u, got el size %u\n",
|
||||
s, agx_size_align_16(sizes[I->src[s].value]),
|
||||
agx_size_align_16(I->src[s].size));
|
||||
agx_print_instr(I, stderr);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(sizes);
|
||||
return succ;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_validate_predecessors(agx_block *block)
|
||||
{
|
||||
/* Loop headers (only) have predecessors that are later in source form */
|
||||
bool has_later_preds = false;
|
||||
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
if ((*pred)->index >= block->index)
|
||||
has_later_preds = true;
|
||||
}
|
||||
|
||||
if (has_later_preds && !block->loop_header)
|
||||
return false;
|
||||
|
||||
/* Successors and predecessors are found together */
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
bool found = false;
|
||||
|
||||
agx_foreach_successor((*pred), succ) {
|
||||
if (succ == block)
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_validate_sr(const agx_instr *I)
|
||||
{
|
||||
bool none = (I->op == AGX_OPCODE_GET_SR);
|
||||
bool coverage = (I->op == AGX_OPCODE_GET_SR_COVERAGE);
|
||||
bool barrier = (I->op == AGX_OPCODE_GET_SR_BARRIER);
|
||||
|
||||
/* Filter get_sr instructions */
|
||||
if (!(none || coverage || barrier))
|
||||
return true;
|
||||
|
||||
switch (I->sr) {
|
||||
case AGX_SR_ACTIVE_THREAD_INDEX_IN_QUAD:
|
||||
case AGX_SR_ACTIVE_THREAD_INDEX_IN_SUBGROUP:
|
||||
case AGX_SR_TOTAL_ACTIVE_THREADS_IN_QUAD:
|
||||
case AGX_SR_TOTAL_ACTIVE_THREADS_IN_SUBGROUP:
|
||||
case AGX_SR_COVERAGE_MASK:
|
||||
case AGX_SR_IS_ACTIVE_THREAD:
|
||||
return coverage;
|
||||
|
||||
case AGX_SR_HELPER_OP:
|
||||
case AGX_SR_HELPER_ARG_L:
|
||||
case AGX_SR_HELPER_ARG_H:
|
||||
return barrier;
|
||||
|
||||
default:
|
||||
return none;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
agx_validate(agx_context *ctx, const char *after)
|
||||
{
|
||||
bool fail = false;
|
||||
|
||||
if (agx_compiler_debug & AGX_DBG_NOVALIDATE)
|
||||
return;
|
||||
|
||||
int last_index = -1;
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
if ((int)block->index < last_index) {
|
||||
fprintf(stderr, "Out-of-order block index %d vs %d after %s\n",
|
||||
block->index, last_index, after);
|
||||
agx_print_block(block, stderr);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
last_index = block->index;
|
||||
|
||||
if (!agx_validate_block_form(block)) {
|
||||
fprintf(stderr, "Invalid block form after %s\n", after);
|
||||
agx_print_block(block, stderr);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (!agx_validate_predecessors(block)) {
|
||||
fprintf(stderr, "Invalid loop header flag after %s\n", after);
|
||||
agx_print_block(block, stderr);
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
BITSET_WORD *defs = BITSET_CALLOC(ctx->alloc);
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
if (!agx_validate_defs(I, defs)) {
|
||||
fprintf(stderr, "Invalid defs after %s\n", after);
|
||||
agx_print_instr(I, stderr);
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* agx_validate_defs skips phi sources, so validate them now */
|
||||
agx_foreach_block(ctx, block) {
|
||||
agx_foreach_phi_in_block(block, phi) {
|
||||
agx_foreach_ssa_src(phi, s) {
|
||||
if (!BITSET_TEST(defs, phi->src[s].value)) {
|
||||
fprintf(stderr, "Undefined phi source %u after %s\n",
|
||||
phi->src[s].value, after);
|
||||
agx_print_instr(phi, stderr);
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(defs);
|
||||
}
|
||||
|
||||
agx_foreach_instr_global(ctx, I) {
|
||||
if (!agx_validate_sources(I)) {
|
||||
fprintf(stderr, "Invalid sources form after %s\n", after);
|
||||
agx_print_instr(I, stderr);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (!agx_validate_sr(I)) {
|
||||
fprintf(stderr, "Invalid SR after %s\n", after);
|
||||
agx_print_instr(I, stdout);
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!agx_validate_width(ctx)) {
|
||||
fprintf(stderr, "Invalid vectors after %s\n", after);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
agx_print_shader(ctx, stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* NDEBUG */
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_opcodes.h"
|
||||
|
||||
/* Validatation doesn't make sense in release builds */
|
||||
#ifndef NDEBUG
|
||||
|
||||
/* Represents a single 16-bit slice of an SSA var */
|
||||
struct var_offset {
|
||||
uint32_t var;
|
||||
uint8_t offset;
|
||||
uint8_t defined;
|
||||
uint8_t pad[2];
|
||||
};
|
||||
static_assert(sizeof(struct var_offset) == 8);
|
||||
|
||||
static struct var_offset
|
||||
var_index(agx_index idx, uint32_t offset)
|
||||
{
|
||||
assert(idx.type == AGX_INDEX_NORMAL);
|
||||
|
||||
return (struct var_offset){
|
||||
.var = idx.value,
|
||||
.offset = offset,
|
||||
.defined = true,
|
||||
};
|
||||
}
|
||||
|
||||
static struct var_offset
|
||||
var_undef()
|
||||
{
|
||||
return (struct var_offset){.defined = false};
|
||||
}
|
||||
|
||||
static bool
|
||||
vars_equal(struct var_offset x, struct var_offset y)
|
||||
{
|
||||
return x.defined && y.defined && x.var == y.var && x.offset == y.offset;
|
||||
}
|
||||
|
||||
/* Represents the contents of a single register file */
|
||||
struct regfile {
|
||||
struct var_offset r[RA_CLASSES][AGX_NUM_MODELED_REGS];
|
||||
};
|
||||
|
||||
static void
|
||||
print_regfile(struct regfile *file, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "regfile: \n");
|
||||
for (enum ra_class cls = 0; cls < RA_CLASSES; ++cls) {
|
||||
for (unsigned r = 0; r < AGX_NUM_MODELED_REGS; ++r) {
|
||||
struct var_offset v = file->r[cls][r];
|
||||
|
||||
if (v.defined) {
|
||||
fprintf(fp, " %c%u = %u[%u]\n", cls == RA_MEM ? 'm' : 'h', r,
|
||||
v.var, v.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
#define agx_validate_assert(file, I, s, offs, stmt) \
|
||||
if (!(stmt)) { \
|
||||
fprintf(stderr, "failed to validate RA source %u offs %u: " #stmt "\n", \
|
||||
s, offs); \
|
||||
agx_print_instr(I, stderr); \
|
||||
print_regfile(file, stderr); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
static void
|
||||
copy_reg(struct regfile *file, agx_index dst, agx_index src)
|
||||
{
|
||||
assert(dst.type == AGX_INDEX_REGISTER);
|
||||
assert(src.type == AGX_INDEX_REGISTER);
|
||||
|
||||
enum ra_class dst_cls = ra_class_for_index(dst);
|
||||
enum ra_class src_cls = ra_class_for_index(src);
|
||||
|
||||
for (uint8_t offs = 0; offs < agx_index_size_16(dst); ++offs) {
|
||||
assert(dst.value + offs < ARRAY_SIZE(file->r[dst_cls]));
|
||||
file->r[dst_cls][dst.value + offs] = file->r[src_cls][src.value + offs];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
swap_regs(struct regfile *file, agx_index a, agx_index b)
|
||||
{
|
||||
assert(a.type == AGX_INDEX_REGISTER);
|
||||
assert(b.type == AGX_INDEX_REGISTER);
|
||||
|
||||
enum ra_class a_cls = ra_class_for_index(a);
|
||||
enum ra_class b_cls = ra_class_for_index(b);
|
||||
|
||||
unsigned size = agx_index_size_16(a);
|
||||
assert(size == agx_index_size_16(b));
|
||||
|
||||
for (uint8_t offs = 0; offs < size; ++offs) {
|
||||
assert(a.value + offs < ARRAY_SIZE(file->r[a_cls]));
|
||||
assert(b.value + offs < ARRAY_SIZE(file->r[b_cls]));
|
||||
|
||||
struct var_offset tmp = file->r[a_cls][a.value + offs];
|
||||
file->r[a_cls][a.value + offs] = file->r[b_cls][b.value + offs];
|
||||
file->r[b_cls][b.value + offs] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
record_dest(struct regfile *file, agx_index idx)
|
||||
{
|
||||
assert(idx.type == AGX_INDEX_NORMAL && idx.has_reg);
|
||||
enum ra_class cls = ra_class_for_index(idx);
|
||||
|
||||
for (uint8_t offs = 0; offs < agx_index_size_16(idx); ++offs) {
|
||||
assert(idx.reg + offs < ARRAY_SIZE(file->r[cls]));
|
||||
file->r[cls][idx.reg + offs] = var_index(idx, offs);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_src(agx_instr *I, unsigned s, struct regfile *file, agx_index idx)
|
||||
{
|
||||
assert(idx.type == AGX_INDEX_NORMAL && idx.has_reg);
|
||||
enum ra_class cls = ra_class_for_index(idx);
|
||||
|
||||
for (uint8_t offs = 0; offs < agx_index_size_16(idx); ++offs) {
|
||||
assert(idx.reg + offs < ARRAY_SIZE(file->r[cls]));
|
||||
struct var_offset actual = file->r[cls][idx.reg + offs];
|
||||
|
||||
agx_validate_assert(file, I, s, offs, actual.defined);
|
||||
agx_validate_assert(file, I, s, offs, actual.var == idx.value);
|
||||
agx_validate_assert(file, I, s, offs, actual.offset == offs);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_block(agx_context *ctx, agx_block *block, struct regfile *blocks)
|
||||
{
|
||||
struct regfile *file = &blocks[block->index];
|
||||
bool success = true;
|
||||
|
||||
/* Pathological shaders can end up with loop headers that have only a single
|
||||
* predecessor and act like normal blocks. Validate them as such, since RA
|
||||
* treats them as such implicitly. Affects:
|
||||
*
|
||||
* dEQP-VK.graphicsfuzz.spv-stable-mergesort-dead-code
|
||||
*/
|
||||
bool loop_header = block->loop_header && agx_num_predecessors(block) > 1;
|
||||
|
||||
/* Initialize the register file based on predecessors. This only works in
|
||||
* non-loop headers, since loop headers have unprocessed predecessors.
|
||||
* However, loop headers phi-declare everything instead of using implicit
|
||||
* live-in sources, so that's ok.
|
||||
*/
|
||||
if (!loop_header) {
|
||||
bool first_pred = true;
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
struct regfile *pred_file = &blocks[(*pred)->index];
|
||||
|
||||
for (enum ra_class cls = 0; cls < RA_CLASSES; ++cls) {
|
||||
for (unsigned r = 0; r < AGX_NUM_MODELED_REGS; ++r) {
|
||||
if (first_pred)
|
||||
file->r[cls][r] = pred_file->r[cls][r];
|
||||
else if (!vars_equal(file->r[cls][r], pred_file->r[cls][r]))
|
||||
file->r[cls][r] = var_undef();
|
||||
}
|
||||
}
|
||||
|
||||
first_pred = false;
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_instr_in_block(block, I) {
|
||||
/* Phis are special since they happen along the edge */
|
||||
if (I->op != AGX_OPCODE_PHI) {
|
||||
agx_foreach_ssa_src(I, s) {
|
||||
success &= validate_src(I, s, file, I->src[s]);
|
||||
}
|
||||
}
|
||||
|
||||
agx_foreach_ssa_dest(I, d) {
|
||||
record_dest(file, I->dest[d]);
|
||||
}
|
||||
|
||||
/* Lowered live range splits don't have SSA associated, handle
|
||||
* directly at the register level.
|
||||
*/
|
||||
if (I->op == AGX_OPCODE_MOV && I->dest[0].type == AGX_INDEX_REGISTER &&
|
||||
I->src[0].type == AGX_INDEX_REGISTER) {
|
||||
|
||||
copy_reg(file, I->dest[0], I->src[0]);
|
||||
} else if (I->op == AGX_OPCODE_SWAP) {
|
||||
swap_regs(file, I->src[0], I->src[1]);
|
||||
} else if (I->op == AGX_OPCODE_PHI &&
|
||||
I->dest[0].type == AGX_INDEX_REGISTER) {
|
||||
/* Register-only phis which resolve to the same variable in all blocks.
|
||||
* This is generated for edge case live range splits.
|
||||
*/
|
||||
assert(!I->dest[0].memory);
|
||||
assert(!loop_header);
|
||||
for (uint8_t offs = 0; offs < agx_index_size_16(I->dest[0]); ++offs) {
|
||||
bool all_same = true;
|
||||
bool first = true;
|
||||
struct var_offset same = var_undef();
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
unsigned idx = agx_predecessor_index(block, *pred);
|
||||
agx_index src = I->src[idx];
|
||||
|
||||
assert(!src.memory);
|
||||
if (src.type != AGX_INDEX_REGISTER) {
|
||||
all_same = false;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct regfile *pred_file = &blocks[(*pred)->index];
|
||||
struct var_offset var = pred_file->r[RA_GPR][src.value + offs];
|
||||
all_same &= first || vars_equal(var, same);
|
||||
same = var;
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (all_same) {
|
||||
file->r[RA_GPR][I->dest[0].value + offs] = same;
|
||||
}
|
||||
}
|
||||
} else if (I->op == AGX_OPCODE_PHI && I->dest[0].has_reg &&
|
||||
!loop_header) {
|
||||
/* Phis which resolve to the same variable in all blocks.
|
||||
* This is generated for live range splits.
|
||||
*/
|
||||
enum ra_class cls = ra_class_for_index(I->dest[0]);
|
||||
for (uint8_t offs = 0; offs < agx_index_size_16(I->dest[0]); ++offs) {
|
||||
bool all_same = true;
|
||||
bool first = true;
|
||||
struct var_offset same = var_undef();
|
||||
agx_foreach_predecessor(block, pred) {
|
||||
unsigned idx = agx_predecessor_index(block, *pred);
|
||||
agx_index src = I->src[idx];
|
||||
|
||||
assert(ra_class_for_index(src) == cls);
|
||||
if (!src.has_reg) {
|
||||
all_same = false;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct regfile *pred_file = &blocks[(*pred)->index];
|
||||
struct var_offset var = pred_file->r[cls][src.reg + offs];
|
||||
all_same &= first || vars_equal(var, same);
|
||||
same = var;
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (all_same && I->dest[0].value == I->src[0].value) {
|
||||
file->r[cls][I->dest[0].reg + offs] = same;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* After processing a block, process the block's source in its successors'
|
||||
* phis. These happen on the edge so we have all the information here, even
|
||||
* with backedges.
|
||||
*/
|
||||
agx_foreach_successor(block, succ) {
|
||||
unsigned idx = agx_predecessor_index(succ, block);
|
||||
|
||||
agx_foreach_phi_in_block(succ, phi) {
|
||||
if (phi->src[idx].type == AGX_INDEX_NORMAL) {
|
||||
success &= validate_src(phi, idx, file, phi->src[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
agx_validate_ra(agx_context *ctx)
|
||||
{
|
||||
bool succ = true;
|
||||
struct regfile *blocks = calloc(ctx->num_blocks, sizeof(*blocks));
|
||||
|
||||
agx_foreach_block(ctx, block) {
|
||||
succ &= validate_block(ctx, block, blocks);
|
||||
}
|
||||
|
||||
if (!succ) {
|
||||
agx_print_shader(ctx, stderr);
|
||||
UNREACHABLE("invalid RA");
|
||||
}
|
||||
|
||||
free(blocks);
|
||||
}
|
||||
|
||||
#endif /* NDEBUG */
|
||||
@@ -0,0 +1,124 @@
|
||||
# Copyright 2018 Rob Clark
|
||||
# Copyright 2019 Collabora, Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
libasahi_agx_files = files(
|
||||
'agx_compile.c',
|
||||
'agx_dce.c',
|
||||
'agx_liveness.c',
|
||||
'agx_insert_waits.c',
|
||||
'agx_nir_lower_address.c',
|
||||
'agx_nir_lower_cull_distance.c',
|
||||
'agx_nir_lower_fminmax.c',
|
||||
'agx_nir_lower_frag_sidefx.c',
|
||||
'agx_nir_lower_sample_mask.c',
|
||||
'agx_nir_lower_discard_zs_emit.c',
|
||||
'agx_nir_lower_interpolation.c',
|
||||
'agx_nir_lower_shared_bitsize.c',
|
||||
'agx_nir_lower_subgroups.c',
|
||||
'agx_nir_lower_texture.c',
|
||||
'agx_nir_opt_preamble.c',
|
||||
'agx_lower_64bit.c',
|
||||
'agx_lower_divergent_shuffle.c',
|
||||
'agx_lower_parallel_copy.c',
|
||||
'agx_lower_pseudo.c',
|
||||
'agx_lower_spill.c',
|
||||
'agx_lower_uniform_sources.c',
|
||||
'agx_pack.c',
|
||||
'agx_performance.c',
|
||||
'agx_print.c',
|
||||
'agx_pressure_schedule.c',
|
||||
'agx_ir.c',
|
||||
'agx_opt_cse.c',
|
||||
'agx_opt_break_if.c',
|
||||
'agx_opt_empty_else.c',
|
||||
'agx_opt_jmp_none.c',
|
||||
'agx_opt_compact_constants.c',
|
||||
'agx_opt_promote_constants.c',
|
||||
'agx_opt_register_cache.c',
|
||||
'agx_optimizer.c',
|
||||
'agx_repair_ssa.c',
|
||||
'agx_reindex_ssa.c',
|
||||
'agx_spill.c',
|
||||
'agx_register_allocate.c',
|
||||
'agx_validate.c',
|
||||
'agx_validate_ra.c',
|
||||
)
|
||||
|
||||
agx_nir_algebraic_c = custom_target(
|
||||
input : 'agx_nir_algebraic.py',
|
||||
output : 'agx_nir_algebraic.c',
|
||||
command : [
|
||||
prog_python, '@INPUT@', '-p', dir_compiler_nir,
|
||||
],
|
||||
capture : true,
|
||||
depend_files : nir_algebraic_depends,
|
||||
)
|
||||
|
||||
agx_opcodes_h = custom_target(
|
||||
input : ['agx_opcodes.h.py'],
|
||||
output : 'agx_opcodes.h',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
depend_files : files('agx_opcodes.py'),
|
||||
)
|
||||
|
||||
idep_agx_opcodes_h = declare_dependency(
|
||||
sources : [agx_opcodes_h],
|
||||
include_directories : include_directories('.'),
|
||||
)
|
||||
|
||||
agx_opcodes_c = custom_target(
|
||||
input : ['agx_opcodes.c.py'],
|
||||
output : 'agx_opcodes.c',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
depend_files : files('agx_opcodes.py'),
|
||||
)
|
||||
|
||||
agx_builder_h = custom_target(
|
||||
input : ['agx_builder.h.py'],
|
||||
output : 'agx_builder.h',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
depend_files : files('agx_opcodes.py'),
|
||||
)
|
||||
|
||||
idep_agx_builder_h = declare_dependency(
|
||||
sources : [agx_builder_h],
|
||||
include_directories : include_directories('.'),
|
||||
)
|
||||
|
||||
libasahi_compiler = static_library(
|
||||
'asahi_compiler',
|
||||
[libasahi_agx_files, agx_opcodes_c, agx_nir_algebraic_c],
|
||||
dependencies: [idep_nir, idep_agx_opcodes_h, idep_agx_builder_h, idep_mesautil, idep_libagx],
|
||||
link_with : [libagx2_disasm],
|
||||
c_args : [no_override_init_args, '-Wno-c2x-extensions'],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
build_by_default : false,
|
||||
)
|
||||
|
||||
if with_tests
|
||||
test(
|
||||
'agx_tests',
|
||||
executable(
|
||||
'agx_tests',
|
||||
files(
|
||||
'test/test-compact-constants.cpp',
|
||||
'test/test-minifloat.cpp',
|
||||
'test/test-optimizer.cpp',
|
||||
'test/test-lower-pseudo.cpp',
|
||||
'test/test-lower-parallel-copy.cpp',
|
||||
'test/test-lower-spill.cpp',
|
||||
'test/test-repair-ssa.cpp',
|
||||
),
|
||||
c_args : [c_msvc_compat_args, no_override_init_args],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
dependencies: [idep_gtest, idep_nir, idep_agx_opcodes_h, idep_agx_builder_h, idep_mesautil],
|
||||
link_with : [libasahi_compiler],
|
||||
),
|
||||
suite : ['asahi'],
|
||||
protocol : 'gtest',
|
||||
)
|
||||
endif
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2020-2021 Collabora Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __AGX_TEST_H
|
||||
#define __AGX_TEST_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
|
||||
/* Helper to generate a agx_builder suitable for creating test instructions */
|
||||
static inline agx_builder *
|
||||
agx_test_builder(void *memctx)
|
||||
{
|
||||
agx_context *ctx = rzalloc(memctx, agx_context);
|
||||
list_inithead(&ctx->blocks);
|
||||
|
||||
agx_block *blk = rzalloc(ctx, agx_block);
|
||||
blk->predecessors = UTIL_DYNARRAY_INIT;
|
||||
ctx->num_blocks = 1;
|
||||
|
||||
list_addtail(&blk->link, &ctx->blocks);
|
||||
list_inithead(&blk->instructions);
|
||||
|
||||
agx_builder *b = rzalloc(memctx, agx_builder);
|
||||
b->shader = ctx;
|
||||
b->cursor = agx_after_block(blk);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static inline agx_block *
|
||||
agx_test_block(agx_context *ctx)
|
||||
{
|
||||
agx_block *blk = rzalloc(ctx, agx_block);
|
||||
|
||||
util_dynarray_init(&blk->predecessors, blk);
|
||||
list_addtail(&blk->link, &ctx->blocks);
|
||||
list_inithead(&blk->instructions);
|
||||
|
||||
blk->index = ctx->num_blocks++;
|
||||
|
||||
return blk;
|
||||
}
|
||||
|
||||
/* Helper to compare for logical equality of instructions. Need to compare the
|
||||
* pointers, then compare raw data.
|
||||
*/
|
||||
static inline bool
|
||||
agx_instr_equal(agx_instr *A, agx_instr *B)
|
||||
{
|
||||
unsigned pointers = sizeof(struct list_head) + sizeof(agx_index *) * 2;
|
||||
|
||||
if (A->nr_srcs != B->nr_srcs)
|
||||
return false;
|
||||
|
||||
if (memcmp(A->src, B->src, A->nr_srcs * sizeof(agx_index)))
|
||||
return false;
|
||||
|
||||
if (A->nr_dests != B->nr_dests)
|
||||
return false;
|
||||
|
||||
if (memcmp(A->dest, B->dest, A->nr_dests * sizeof(agx_index)))
|
||||
return false;
|
||||
|
||||
return memcmp((uint8_t *)A + pointers, (uint8_t *)B + pointers,
|
||||
sizeof(agx_instr) - pointers) == 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
agx_block_equal(agx_block *A, agx_block *B)
|
||||
{
|
||||
if (list_length(&A->instructions) != list_length(&B->instructions))
|
||||
return false;
|
||||
|
||||
list_pair_for_each_entry(agx_instr, insA, insB, &A->instructions,
|
||||
&B->instructions, link) {
|
||||
if (!agx_instr_equal(insA, insB))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
agx_shader_equal(agx_context *A, agx_context *B)
|
||||
{
|
||||
if (list_length(&A->blocks) != list_length(&B->blocks))
|
||||
return false;
|
||||
|
||||
list_pair_for_each_entry(agx_block, blockA, blockB, &A->blocks, &B->blocks,
|
||||
link) {
|
||||
if (!agx_block_equal(blockA, blockB))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define ASSERT_SHADER_EQUAL(A, B) \
|
||||
if (!agx_shader_equal(A, B)) { \
|
||||
ADD_FAILURE(); \
|
||||
fprintf(stderr, "Pass produced unexpected results"); \
|
||||
fprintf(stderr, " Actual:\n"); \
|
||||
agx_print_shader(A, stderr); \
|
||||
fprintf(stderr, " Expected:\n"); \
|
||||
agx_print_shader(B, stderr); \
|
||||
fprintf(stderr, "\n"); \
|
||||
}
|
||||
|
||||
#define INSTRUCTION_CASE(instr, expected, pass) \
|
||||
do { \
|
||||
agx_builder *A = agx_test_builder(mem_ctx); \
|
||||
agx_builder *B = agx_test_builder(mem_ctx); \
|
||||
{ \
|
||||
agx_builder *b = A; \
|
||||
instr; \
|
||||
} \
|
||||
{ \
|
||||
agx_builder *b = B; \
|
||||
expected; \
|
||||
} \
|
||||
pass(A->shader); \
|
||||
ASSERT_SHADER_EQUAL(A->shader, B->shader); \
|
||||
} while (0)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define CASE(instr, expected, size) \
|
||||
INSTRUCTION_CASE( \
|
||||
{ \
|
||||
UNUSED agx_index out = agx_temp(b->shader, AGX_SIZE_##size); \
|
||||
instr; \
|
||||
agx_unit_test(b, out); \
|
||||
}, \
|
||||
{ \
|
||||
UNUSED agx_index out = agx_temp(b->shader, AGX_SIZE_##size); \
|
||||
expected; \
|
||||
agx_unit_test(b, out); \
|
||||
}, \
|
||||
agx_opt_compact_constants)
|
||||
|
||||
#define NEGCASE(instr, size) CASE(instr, instr, size)
|
||||
#define CASE32(instr, expected) CASE(instr, expected, 32)
|
||||
#define NEGCASE32(instr) NEGCASE(instr, 32)
|
||||
|
||||
class CompactConstants : public testing::Test {
|
||||
protected:
|
||||
CompactConstants()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
|
||||
wx = agx_register(0, AGX_SIZE_32);
|
||||
}
|
||||
|
||||
~CompactConstants()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
agx_index wx;
|
||||
};
|
||||
|
||||
TEST_F(CompactConstants, FP32)
|
||||
{
|
||||
CASE32(agx_fadd_to(b, out, wx, agx_mov_imm(b, 32, fui(32768.0))),
|
||||
agx_fadd_to(b, out, wx, agx_mov_imm(b, 16, 0x7800)));
|
||||
}
|
||||
|
||||
TEST_F(CompactConstants, InexactFP32)
|
||||
{
|
||||
NEGCASE32(agx_fadd_to(b, out, wx, agx_mov_imm(b, 32, fui(32769.0))));
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define CASE(copies, expected) \
|
||||
do { \
|
||||
agx_builder *A = agx_test_builder(mem_ctx); \
|
||||
agx_builder *B = agx_test_builder(mem_ctx); \
|
||||
\
|
||||
agx_emit_parallel_copies(A, copies, ARRAY_SIZE(copies)); \
|
||||
\
|
||||
{ \
|
||||
agx_builder *b = B; \
|
||||
expected; \
|
||||
} \
|
||||
\
|
||||
ASSERT_SHADER_EQUAL(A->shader, B->shader); \
|
||||
} while (0)
|
||||
|
||||
class LowerParallelCopy : public testing::Test {
|
||||
protected:
|
||||
LowerParallelCopy()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
}
|
||||
|
||||
~LowerParallelCopy()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
};
|
||||
|
||||
TEST_F(LowerParallelCopy, UnrelatedCopies)
|
||||
{
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 0, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 4, .src = agx_register(6, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_32), agx_register(6, AGX_SIZE_32));
|
||||
});
|
||||
|
||||
struct agx_copy test_2[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 4, .src = agx_register(5, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test_2, {
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_16), agx_register(5, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, RelatedSource)
|
||||
{
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 0, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 4, .src = agx_register(2, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
});
|
||||
|
||||
struct agx_copy test_2[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 4, .src = agx_register(1, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test_2, {
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, DependentCopies)
|
||||
{
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 0, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 4, .src = agx_register(0, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_32), agx_register(0, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
});
|
||||
|
||||
struct agx_copy test_2[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 4, .src = agx_register(0, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test_2, {
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_16), agx_register(0, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, ManyDependentCopies)
|
||||
{
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 0, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 4, .src = agx_register(0, AGX_SIZE_32)},
|
||||
{.dest = 8, .src = agx_register(6, AGX_SIZE_32)},
|
||||
{.dest = 6, .src = agx_register(4, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_mov_to(b, agx_register(8, AGX_SIZE_32), agx_register(6, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(6, AGX_SIZE_32), agx_register(4, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_32), agx_register(0, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
});
|
||||
|
||||
struct agx_copy test_2[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 2, .src = agx_register(0, AGX_SIZE_16)},
|
||||
{.dest = 4, .src = agx_register(3, AGX_SIZE_16)},
|
||||
{.dest = 3, .src = agx_register(2, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test_2, {
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_16), agx_register(3, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(3, AGX_SIZE_16), agx_register(2, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(2, AGX_SIZE_16), agx_register(0, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, Swap)
|
||||
{
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 0, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 2, .src = agx_register(0, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_swap(b, agx_register(0, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
});
|
||||
|
||||
struct agx_copy test_2[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 1, .src = agx_register(0, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test_2, {
|
||||
agx_swap(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, Cycle3)
|
||||
{
|
||||
struct agx_copy test[] = {
|
||||
{.dest = 0, .src = agx_register(1, AGX_SIZE_16)},
|
||||
{.dest = 1, .src = agx_register(2, AGX_SIZE_16)},
|
||||
{.dest = 2, .src = agx_register(0, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test, {
|
||||
agx_swap(b, agx_register(0, AGX_SIZE_16), agx_register(1, AGX_SIZE_16));
|
||||
agx_swap(b, agx_register(1, AGX_SIZE_16), agx_register(2, AGX_SIZE_16));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, Immediate64)
|
||||
{
|
||||
agx_index imm = agx_immediate(10);
|
||||
imm.size = AGX_SIZE_64;
|
||||
|
||||
struct agx_copy test_1[] = {
|
||||
{.dest = 4, .src = imm},
|
||||
};
|
||||
|
||||
CASE(test_1, {
|
||||
agx_mov_imm_to(b, agx_register(4, AGX_SIZE_32), 10);
|
||||
agx_mov_imm_to(b, agx_register(6, AGX_SIZE_32), 0);
|
||||
});
|
||||
}
|
||||
|
||||
/* Test case from Hack et al */
|
||||
TEST_F(LowerParallelCopy, TwoSwaps)
|
||||
{
|
||||
struct agx_copy test[] = {
|
||||
{.dest = 4, .src = agx_register(2, AGX_SIZE_32)},
|
||||
{.dest = 6, .src = agx_register(4, AGX_SIZE_32)},
|
||||
{.dest = 2, .src = agx_register(6, AGX_SIZE_32)},
|
||||
{.dest = 8, .src = agx_register(8, AGX_SIZE_32)},
|
||||
};
|
||||
|
||||
CASE(test, {
|
||||
agx_swap(b, agx_register(4, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
agx_swap(b, agx_register(6, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, VectorizeAlignedHalfRegs)
|
||||
{
|
||||
struct agx_copy test[] = {
|
||||
{.dest = 0, .src = agx_register(10, AGX_SIZE_16)},
|
||||
{.dest = 1, .src = agx_register(11, AGX_SIZE_16)},
|
||||
{.dest = 2, .src = agx_uniform(8, AGX_SIZE_16)},
|
||||
{.dest = 3, .src = agx_uniform(9, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test, {
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32),
|
||||
agx_register(10, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(2, AGX_SIZE_32), agx_uniform(8, AGX_SIZE_32));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerParallelCopy, StackCopies)
|
||||
{
|
||||
struct agx_copy test[] = {
|
||||
{.dest = 21, .dest_mem = true, .src = agx_register(20, AGX_SIZE_16)},
|
||||
{.dest = 22, .dest_mem = true, .src = agx_register(22, AGX_SIZE_32)},
|
||||
{.dest = 0, .src = agx_memory_register(10, AGX_SIZE_16)},
|
||||
{.dest = 1, .src = agx_memory_register(11, AGX_SIZE_16)},
|
||||
{.dest = 0, .dest_mem = true, .src = agx_memory_register(12, AGX_SIZE_16)},
|
||||
{.dest = 1, .dest_mem = true, .src = agx_memory_register(13, AGX_SIZE_16)},
|
||||
{.dest = 2,
|
||||
.dest_mem = true,
|
||||
.src = agx_memory_register(804, AGX_SIZE_32)},
|
||||
{.dest = 804,
|
||||
.dest_mem = true,
|
||||
.src = agx_memory_register(2, AGX_SIZE_32)},
|
||||
{.dest = 807,
|
||||
.dest_mem = true,
|
||||
.src = agx_memory_register(808, AGX_SIZE_16)},
|
||||
{.dest = 808,
|
||||
.dest_mem = true,
|
||||
.src = agx_memory_register(807, AGX_SIZE_16)},
|
||||
};
|
||||
|
||||
CASE(test, {
|
||||
/* Vectorized fill */
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32),
|
||||
agx_memory_register(10, AGX_SIZE_32));
|
||||
|
||||
/* Regular spills */
|
||||
agx_mov_to(b, agx_memory_register(21, AGX_SIZE_16),
|
||||
agx_register(20, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_memory_register(22, AGX_SIZE_32),
|
||||
agx_register(22, AGX_SIZE_32));
|
||||
|
||||
/* Vectorized stack->stack copy */
|
||||
agx_mov_to(b, agx_register(2, AGX_SIZE_32),
|
||||
agx_memory_register(12, AGX_SIZE_32));
|
||||
|
||||
agx_mov_to(b, agx_memory_register(0, AGX_SIZE_32),
|
||||
agx_register(2, AGX_SIZE_32));
|
||||
|
||||
/* Stack swap: 32-bit */
|
||||
agx_index temp1 = agx_register(4, AGX_SIZE_32);
|
||||
agx_index temp2 = agx_register(6, AGX_SIZE_32);
|
||||
agx_index spilled_gpr_vec2 = agx_register(0, AGX_SIZE_32);
|
||||
spilled_gpr_vec2.channels_m1++;
|
||||
|
||||
agx_mov_to(b, temp1, agx_memory_register(2, AGX_SIZE_32));
|
||||
agx_mov_to(b, temp2, agx_memory_register(804, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_memory_register(804, AGX_SIZE_32), temp1);
|
||||
agx_mov_to(b, agx_memory_register(2, AGX_SIZE_32), temp2);
|
||||
|
||||
/* Stack swap: 16-bit */
|
||||
spilled_gpr_vec2.size = AGX_SIZE_16;
|
||||
temp1.size = AGX_SIZE_16;
|
||||
temp2.size = AGX_SIZE_16;
|
||||
|
||||
agx_mov_to(b, temp1, agx_memory_register(807, AGX_SIZE_16));
|
||||
agx_mov_to(b, temp2, agx_memory_register(808, AGX_SIZE_16));
|
||||
agx_mov_to(b, agx_memory_register(808, AGX_SIZE_16), temp1);
|
||||
agx_mov_to(b, agx_memory_register(807, AGX_SIZE_16), temp2);
|
||||
});
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_F(LowerParallelCopy, LooksLikeASwap) {
|
||||
struct agx_copy test[] = {
|
||||
{ .dest = 0, .src = agx_register(2, AGX_SIZE_32) },
|
||||
{ .dest = 2, .src = agx_register(0, AGX_SIZE_32) },
|
||||
{ .dest = 4, .src = agx_register(2, AGX_SIZE_32) },
|
||||
};
|
||||
|
||||
CASE(test, {
|
||||
agx_mov_to(b, agx_register(4, AGX_SIZE_32), agx_register(2, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(2, AGX_SIZE_32), agx_register(0, AGX_SIZE_32));
|
||||
agx_mov_to(b, agx_register(0, AGX_SIZE_32), agx_register(4, AGX_SIZE_32));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define CASE(instr, expected) \
|
||||
INSTRUCTION_CASE(instr, expected, agx_lower_pseudo)
|
||||
#define NEGCASE(instr) CASE(instr, instr)
|
||||
|
||||
class LowerPseudo : public testing::Test {
|
||||
protected:
|
||||
LowerPseudo()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
|
||||
wx = agx_register(0, AGX_SIZE_32);
|
||||
wy = agx_register(2, AGX_SIZE_32);
|
||||
wz = agx_register(4, AGX_SIZE_32);
|
||||
}
|
||||
|
||||
~LowerPseudo()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
agx_index wx, wy, wz;
|
||||
};
|
||||
|
||||
TEST_F(LowerPseudo, Move)
|
||||
{
|
||||
CASE(agx_mov_to(b, wx, wy), agx_bitop_to(b, wx, wy, agx_zero(), 0xA));
|
||||
}
|
||||
|
||||
TEST_F(LowerPseudo, Not)
|
||||
{
|
||||
CASE(agx_not_to(b, wx, wy), agx_bitop_to(b, wx, wy, agx_zero(), 0x5));
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2023 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compile.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_test.h"
|
||||
|
||||
#include "util/macros.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#define CASE(instr, expected) \
|
||||
do { \
|
||||
agx_builder *A = agx_test_builder(mem_ctx); \
|
||||
agx_builder *B = agx_test_builder(mem_ctx); \
|
||||
{ \
|
||||
agx_builder *b = A; \
|
||||
instr; \
|
||||
} \
|
||||
{ \
|
||||
agx_builder *b = B; \
|
||||
expected; \
|
||||
} \
|
||||
A->shader->scratch_size_B = 1024; \
|
||||
agx_lower_spill(A->shader); \
|
||||
ASSERT_SHADER_EQUAL(A->shader, B->shader); \
|
||||
} while (0)
|
||||
|
||||
class LowerSpill : public testing::Test {
|
||||
protected:
|
||||
LowerSpill()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
|
||||
wx = agx_register(0, AGX_SIZE_32);
|
||||
hy = agx_register(2, AGX_SIZE_16);
|
||||
|
||||
mw4 = agx_memory_register(0, AGX_SIZE_32);
|
||||
mh4 = agx_memory_register(0, AGX_SIZE_16);
|
||||
mw4.channels_m1 = 4 - 1;
|
||||
mh4.channels_m1 = 4 - 1;
|
||||
|
||||
wx4 = wx;
|
||||
wx4.channels_m1 = 4 - 1;
|
||||
|
||||
hy4 = hy;
|
||||
hy4.channels_m1 = 4 - 1;
|
||||
}
|
||||
|
||||
~LowerSpill()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
agx_index wx, hy, wx4, hy4;
|
||||
agx_index mw4, mh4;
|
||||
|
||||
unsigned scalar = BITFIELD_MASK(1);
|
||||
unsigned vec4 = BITFIELD_MASK(4);
|
||||
|
||||
enum agx_format i16 = AGX_FORMAT_I16;
|
||||
enum agx_format i32 = AGX_FORMAT_I32;
|
||||
};
|
||||
|
||||
TEST_F(LowerSpill, ScalarSpills)
|
||||
{
|
||||
CASE(agx_mov_to(b, agx_memory_register(11, AGX_SIZE_16), hy),
|
||||
agx_stack_store(b, hy, agx_immediate(22), i16, scalar));
|
||||
|
||||
CASE(agx_mov_to(b, agx_memory_register(18, AGX_SIZE_32), wx),
|
||||
agx_stack_store(b, wx, agx_immediate(36), i32, scalar));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, ScalarFills)
|
||||
{
|
||||
CASE(agx_mov_to(b, hy, agx_memory_register(11, AGX_SIZE_16)),
|
||||
agx_stack_load_to(b, hy, agx_immediate(22), i16, scalar));
|
||||
|
||||
CASE(agx_mov_to(b, wx, agx_memory_register(18, AGX_SIZE_32)),
|
||||
agx_stack_load_to(b, wx, agx_immediate(36), i32, scalar));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, VectorSpills)
|
||||
{
|
||||
CASE(agx_mov_to(b, mh4, hy4),
|
||||
agx_stack_store(b, hy4, agx_immediate(0), i16, vec4));
|
||||
|
||||
CASE(agx_mov_to(b, mw4, wx4),
|
||||
agx_stack_store(b, wx4, agx_immediate(0), i32, vec4));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, VectorFills)
|
||||
{
|
||||
CASE(agx_mov_to(b, hy4, mh4),
|
||||
agx_stack_load_to(b, hy4, agx_immediate(0), i16, vec4));
|
||||
|
||||
CASE(agx_mov_to(b, wx4, mw4),
|
||||
agx_stack_load_to(b, wx4, agx_immediate(0), i32, vec4));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, ScalarSpill64)
|
||||
{
|
||||
CASE(agx_mov_to(b, agx_memory_register(16, AGX_SIZE_64),
|
||||
agx_register(8, AGX_SIZE_64)),
|
||||
agx_stack_store(b, agx_register(8, AGX_SIZE_64), agx_immediate(32), i32,
|
||||
BITFIELD_MASK(2)));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, ScalarFill64)
|
||||
{
|
||||
CASE(agx_mov_to(b, agx_register(16, AGX_SIZE_64),
|
||||
agx_memory_register(8, AGX_SIZE_64)),
|
||||
agx_stack_load_to(b, agx_register(16, AGX_SIZE_64), agx_immediate(16),
|
||||
i32, BITFIELD_MASK(2)));
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, Vec6Spill)
|
||||
{
|
||||
CASE(
|
||||
{
|
||||
agx_index mvec6 = agx_memory_register(16, AGX_SIZE_32);
|
||||
agx_index vec6 = agx_register(8, AGX_SIZE_32);
|
||||
vec6.channels_m1 = 6 - 1;
|
||||
mvec6.channels_m1 = 6 - 1;
|
||||
|
||||
agx_mov_to(b, mvec6, vec6);
|
||||
},
|
||||
{
|
||||
agx_index vec4 = agx_register(8, AGX_SIZE_32);
|
||||
agx_index vec2 = agx_register(8 + (4 * 2), AGX_SIZE_32);
|
||||
vec4.channels_m1 = 4 - 1;
|
||||
vec2.channels_m1 = 2 - 1;
|
||||
|
||||
agx_stack_store(b, vec4, agx_immediate(32), i32, BITFIELD_MASK(4));
|
||||
agx_stack_store(b, vec2, agx_immediate(32 + 4 * 4), i32,
|
||||
BITFIELD_MASK(2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(LowerSpill, Vec6Fill)
|
||||
{
|
||||
CASE(
|
||||
{
|
||||
agx_index mvec6 = agx_memory_register(16, AGX_SIZE_32);
|
||||
agx_index vec6 = agx_register(8, AGX_SIZE_32);
|
||||
vec6.channels_m1 = 6 - 1;
|
||||
mvec6.channels_m1 = 6 - 1;
|
||||
|
||||
agx_mov_to(b, vec6, mvec6);
|
||||
},
|
||||
{
|
||||
agx_index vec4 = agx_register(8, AGX_SIZE_32);
|
||||
agx_index vec2 = agx_register(8 + (4 * 2), AGX_SIZE_32);
|
||||
vec4.channels_m1 = 4 - 1;
|
||||
vec2.channels_m1 = 2 - 1;
|
||||
|
||||
agx_stack_load_to(b, vec4, agx_immediate(32), i32, BITFIELD_MASK(4));
|
||||
agx_stack_load_to(b, vec2, agx_immediate(32 + 4 * 4), i32,
|
||||
BITFIELD_MASK(2));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021-2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(Minifloat, RepresentativeValues)
|
||||
{
|
||||
EXPECT_EQ(agx_minifloat_decode(0), 0.0f);
|
||||
EXPECT_EQ(agx_minifloat_decode(25), 0.390625f);
|
||||
EXPECT_EQ(agx_minifloat_decode(135), -0.109375f);
|
||||
EXPECT_EQ(agx_minifloat_decode(255), -31.0);
|
||||
}
|
||||
|
||||
TEST(Minifloat, Exactness)
|
||||
{
|
||||
EXPECT_TRUE(agx_minifloat_exact(0.0f));
|
||||
EXPECT_TRUE(agx_minifloat_exact(0.390625f));
|
||||
EXPECT_TRUE(agx_minifloat_exact(-0.109375f));
|
||||
EXPECT_TRUE(agx_minifloat_exact(-31.0));
|
||||
EXPECT_FALSE(agx_minifloat_exact(3.141f));
|
||||
EXPECT_FALSE(agx_minifloat_exact(2.718f));
|
||||
EXPECT_FALSE(agx_minifloat_exact(1.618f));
|
||||
}
|
||||
|
||||
TEST(Minifloat, AllValuesRoundtrip)
|
||||
{
|
||||
for (unsigned i = 0; i < 0x100; ++i) {
|
||||
float f = agx_minifloat_decode(i);
|
||||
EXPECT_EQ(agx_minifloat_encode(f), i);
|
||||
EXPECT_TRUE(agx_minifloat_exact(f));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* Copyright 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
static void
|
||||
agx_optimize_and_dce(agx_context *ctx)
|
||||
{
|
||||
agx_optimizer_backward(ctx);
|
||||
agx_optimizer_forward(ctx);
|
||||
agx_dce(ctx, true);
|
||||
}
|
||||
|
||||
#define CASE(instr, expected, size, returns) \
|
||||
INSTRUCTION_CASE( \
|
||||
{ \
|
||||
UNUSED agx_index out = agx_temp(b->shader, AGX_SIZE_##size); \
|
||||
instr; \
|
||||
if (returns) \
|
||||
agx_unit_test(b, out); \
|
||||
}, \
|
||||
{ \
|
||||
UNUSED agx_index out = agx_temp(b->shader, AGX_SIZE_##size); \
|
||||
expected; \
|
||||
if (returns) \
|
||||
agx_unit_test(b, out); \
|
||||
}, \
|
||||
agx_optimize_and_dce)
|
||||
|
||||
#define NEGCASE(instr, size) CASE(instr, instr, size, true)
|
||||
|
||||
#define CASE16(instr, expected) CASE(instr, expected, 16, true)
|
||||
#define CASE32(instr, expected) CASE(instr, expected, 32, true)
|
||||
#define CASE64(instr, expected) CASE(instr, expected, 64, true)
|
||||
|
||||
#define CASE_NO_RETURN(instr, expected) \
|
||||
CASE(instr, expected, 32 /* irrelevant */, false)
|
||||
|
||||
#define NEGCASE16(instr) NEGCASE(instr, 16)
|
||||
#define NEGCASE32(instr) NEGCASE(instr, 32)
|
||||
|
||||
static inline agx_index
|
||||
agx_fmov(agx_builder *b, agx_index s0)
|
||||
{
|
||||
agx_index tmp = agx_temp(b->shader, s0.size);
|
||||
agx_fmov_to(b, tmp, s0);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
class Optimizer : public testing::Test {
|
||||
protected:
|
||||
Optimizer()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
|
||||
dx = agx_register(0, AGX_SIZE_64);
|
||||
dz = agx_register(4, AGX_SIZE_64);
|
||||
|
||||
wx = agx_register(0, AGX_SIZE_32);
|
||||
wy = agx_register(2, AGX_SIZE_32);
|
||||
wz = agx_register(4, AGX_SIZE_32);
|
||||
|
||||
hx = agx_register(0, AGX_SIZE_16);
|
||||
hy = agx_register(1, AGX_SIZE_16);
|
||||
hz = agx_register(2, AGX_SIZE_16);
|
||||
}
|
||||
|
||||
~Optimizer()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
|
||||
agx_index dx, dz, wx, wy, wz, hx, hy, hz;
|
||||
};
|
||||
|
||||
TEST_F(Optimizer, FloatCopyprop)
|
||||
{
|
||||
CASE32(agx_fadd_to(b, out, agx_abs(agx_fmov(b, wx)), wy),
|
||||
agx_fadd_to(b, out, agx_abs(wx), wy));
|
||||
|
||||
CASE32(agx_fadd_to(b, out, agx_neg(agx_fmov(b, wx)), wy),
|
||||
agx_fadd_to(b, out, agx_neg(wx), wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FloatConversion)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index cvt = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_fmov_to(b, cvt, hx);
|
||||
agx_fadd_to(b, out, cvt, wy);
|
||||
},
|
||||
{ agx_fadd_to(b, out, hx, wy); });
|
||||
|
||||
CASE16(
|
||||
{
|
||||
agx_index sum = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_fadd_to(b, sum, wx, wy);
|
||||
agx_fmov_to(b, out, sum);
|
||||
},
|
||||
{ agx_fadd_to(b, out, wx, wy); });
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FusedFABSNEG)
|
||||
{
|
||||
CASE32(agx_fadd_to(b, out, agx_fmov(b, agx_abs(wx)), wy),
|
||||
agx_fadd_to(b, out, agx_abs(wx), wy));
|
||||
|
||||
CASE32(agx_fmul_to(b, out, wx, agx_fmov(b, agx_neg(agx_abs(wx)))),
|
||||
agx_fmul_to(b, out, wx, agx_neg(agx_abs(wx))));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FusedFabsAbsorb)
|
||||
{
|
||||
CASE32(agx_fadd_to(b, out, agx_abs(agx_fmov(b, agx_abs(wx))), wy),
|
||||
agx_fadd_to(b, out, agx_abs(wx), wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FusedFnegCancel)
|
||||
{
|
||||
CASE32(agx_fmul_to(b, out, wx, agx_neg(agx_fmov(b, agx_neg(wx)))),
|
||||
agx_fmul_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_fmul_to(b, out, wx, agx_neg(agx_fmov(b, agx_neg(agx_abs(wx))))),
|
||||
agx_fmul_to(b, out, wx, agx_abs(wx)));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FusedNot)
|
||||
{
|
||||
CASE32(agx_not_to(b, out, agx_and(b, wx, wx)), agx_nand_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_not_to(b, out, agx_or(b, wx, wx)), agx_nor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_not_to(b, out, agx_xor(b, wx, wx)), agx_xnor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_xor_to(b, out, agx_not(b, wx), agx_not(b, wx)),
|
||||
agx_xor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_xor_to(b, out, agx_not(b, wx), wx), agx_xnor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_xor_to(b, out, wx, agx_not(b, wx)), agx_xnor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_nand_to(b, out, agx_not(b, wx), agx_not(b, wx)),
|
||||
agx_or_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_andn1_to(b, out, agx_not(b, wx), wx), agx_and_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_andn1_to(b, out, wx, agx_not(b, wx)), agx_nor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_andn2_to(b, out, agx_not(b, wx), wx), agx_nor_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_andn2_to(b, out, wx, agx_not(b, wx)), agx_and_to(b, out, wx, wx));
|
||||
|
||||
CASE32(agx_xor_to(b, out, agx_not(b, wx), agx_uniform(8, AGX_SIZE_32)),
|
||||
agx_xnor_to(b, out, wx, agx_uniform(8, AGX_SIZE_32)));
|
||||
|
||||
CASE32(agx_or_to(b, out, agx_immediate(123), agx_not(b, wx)),
|
||||
agx_orn2_to(b, out, agx_immediate(123), wx));
|
||||
|
||||
CASE32(agx_xor_to(b, out, wx, agx_not(b, wy)), agx_xnor_to(b, out, wx, wy));
|
||||
|
||||
CASE32(agx_xor_to(b, out, wy, agx_not(b, wx)), agx_xnor_to(b, out, wy, wx));
|
||||
|
||||
CASE32(agx_and_to(b, out, agx_not(b, wx), wy), agx_andn1_to(b, out, wx, wy));
|
||||
|
||||
CASE32(agx_or_to(b, out, wx, agx_not(b, wy)), agx_orn2_to(b, out, wx, wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FmulFsatF2F16)
|
||||
{
|
||||
CASE16(
|
||||
{
|
||||
agx_index tmp = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_fmov_to(b, tmp, agx_fmul(b, wx, wy))->saturate = true;
|
||||
agx_fmov_to(b, out, tmp);
|
||||
},
|
||||
{ agx_fmul_to(b, out, wx, wy)->saturate = true; });
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, FsatWithPhi)
|
||||
{
|
||||
/*
|
||||
* Construct the loop:
|
||||
*
|
||||
* A:
|
||||
* ...
|
||||
*
|
||||
* B:
|
||||
* phi ..., u
|
||||
* u = wx * phi
|
||||
* out = fsat u
|
||||
* --> B
|
||||
*
|
||||
* This example shows that phi sources are read at the end of the
|
||||
* predecessor, not at the start of the successor. If phis are not handled
|
||||
* properly, the fsat would be fused incorrectly.
|
||||
*
|
||||
* This reproduces an issue hit in a Control shader. Astonishingly, it is not
|
||||
* hit anywhere in CTS.
|
||||
*/
|
||||
NEGCASE32({
|
||||
agx_block *A = agx_start_block(b->shader);
|
||||
agx_block *B = agx_test_block(b->shader);
|
||||
|
||||
agx_block_add_successor(A, B);
|
||||
agx_block_add_successor(B, B);
|
||||
|
||||
b->cursor = agx_after_block(B);
|
||||
agx_index u = agx_temp(b->shader, AGX_SIZE_32);
|
||||
|
||||
agx_instr *phi = agx_phi_to(b, agx_temp(b->shader, AGX_SIZE_32), 2);
|
||||
phi->src[0] = wx;
|
||||
phi->src[1] = u;
|
||||
|
||||
agx_fmul_to(b, u, wx, phi->dest[0]);
|
||||
agx_fmov_to(b, out, u)->saturate = true;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, Copyprop)
|
||||
{
|
||||
CASE32(agx_fmul_to(b, out, wx, agx_mov(b, wy)), agx_fmul_to(b, out, wx, wy));
|
||||
CASE32(agx_fmul_to(b, out, agx_mov(b, wx), agx_mov(b, wy)),
|
||||
agx_fmul_to(b, out, wx, wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, SourceZeroExtend)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index t = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_mov_to(b, t, hy);
|
||||
agx_ffs_to(b, out, t);
|
||||
},
|
||||
agx_ffs_to(b, out, hy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, AddSourceZeroExtend)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index t = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_mov_to(b, t, hy);
|
||||
agx_iadd_to(b, out, wx, t, 1);
|
||||
},
|
||||
agx_iadd_to(b, out, wx, agx_abs(hy), 1));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, AddSourceSignExtend)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index t = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_signext_to(b, t, hy);
|
||||
agx_iadd_to(b, out, wx, t, 1);
|
||||
},
|
||||
agx_iadd_to(b, out, wx, hy, 1));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, SubInlineImmediate)
|
||||
{
|
||||
CASE16(agx_iadd_to(b, out, hx, agx_mov_imm(b, 16, -2), 0),
|
||||
agx_iadd_to(b, out, hx, agx_neg(agx_immediate(2)), 0));
|
||||
|
||||
CASE32(agx_iadd_to(b, out, wx, agx_mov_imm(b, 32, -1), 0),
|
||||
agx_iadd_to(b, out, wx, agx_neg(agx_immediate(1)), 0));
|
||||
|
||||
CASE64(agx_iadd_to(b, out, dx, agx_mov_imm(b, 64, -17), 0),
|
||||
agx_iadd_to(b, out, dx, agx_neg(agx_immediate(17)), 0));
|
||||
|
||||
CASE16(agx_imad_to(b, out, hx, hy, agx_mov_imm(b, 16, -2), 0),
|
||||
agx_imad_to(b, out, hx, hy, agx_neg(agx_immediate(2)), 0));
|
||||
|
||||
CASE32(agx_imad_to(b, out, wx, wy, agx_mov_imm(b, 32, -1), 0),
|
||||
agx_imad_to(b, out, wx, wy, agx_neg(agx_immediate(1)), 0));
|
||||
|
||||
CASE64(agx_imad_to(b, out, dx, dz, agx_mov_imm(b, 64, -17), 0),
|
||||
agx_imad_to(b, out, dx, dz, agx_neg(agx_immediate(17)), 0));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, InlineHazards)
|
||||
{
|
||||
NEGCASE32({
|
||||
agx_index zero = agx_mov_imm(b, AGX_SIZE_32, 0);
|
||||
agx_instr *I = agx_collect_to(b, out, 4);
|
||||
|
||||
I->src[0] = zero;
|
||||
I->src[1] = wy;
|
||||
I->src[2] = wz;
|
||||
I->src[3] = wz;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, CopypropRespectsAbsNeg)
|
||||
{
|
||||
CASE32(agx_fadd_to(b, out, agx_abs(agx_mov(b, wx)), wy),
|
||||
agx_fadd_to(b, out, agx_abs(wx), wy));
|
||||
|
||||
CASE32(agx_fadd_to(b, out, agx_neg(agx_mov(b, wx)), wy),
|
||||
agx_fadd_to(b, out, agx_neg(wx), wy));
|
||||
|
||||
CASE32(agx_fadd_to(b, out, agx_neg(agx_abs(agx_mov(b, wx))), wy),
|
||||
agx_fadd_to(b, out, agx_neg(agx_abs(wx)), wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, IntCopyprop)
|
||||
{
|
||||
CASE32(agx_xor_to(b, out, agx_mov(b, wx), wy), agx_xor_to(b, out, wx, wy));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, CopypropSplitMovedUniform64)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
/* emit_load_preamble puts in the move, so we do too */
|
||||
agx_index mov = agx_mov(b, agx_uniform(40, AGX_SIZE_64));
|
||||
agx_instr *spl = agx_split(b, 2, mov);
|
||||
spl->dest[0] = agx_temp(b->shader, AGX_SIZE_32);
|
||||
spl->dest[1] = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_xor_to(b, out, spl->dest[0], spl->dest[1]);
|
||||
},
|
||||
{
|
||||
agx_xor_to(b, out, agx_uniform(40, AGX_SIZE_32),
|
||||
agx_uniform(42, AGX_SIZE_32));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, IntCopypropDoesntConvert)
|
||||
{
|
||||
NEGCASE32({
|
||||
agx_index cvt = agx_temp(b->shader, AGX_SIZE_32);
|
||||
agx_mov_to(b, cvt, hx);
|
||||
agx_fmul_to(b, out, cvt, wy);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, SkipPreloads)
|
||||
{
|
||||
NEGCASE32({
|
||||
agx_index preload = agx_preload(b, agx_register(0, AGX_SIZE_32));
|
||||
agx_xor_to(b, out, preload, wy);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, NoConversionsOn16BitALU)
|
||||
{
|
||||
NEGCASE16({
|
||||
agx_index cvt = agx_temp(b->shader, AGX_SIZE_16);
|
||||
agx_fmov_to(b, cvt, wx);
|
||||
agx_fadd_to(b, out, cvt, hy);
|
||||
});
|
||||
|
||||
NEGCASE32(agx_fmov_to(b, out, agx_fadd(b, hx, hy)));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, BallotCondition)
|
||||
{
|
||||
CASE32(agx_ballot_to(b, out, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, true)),
|
||||
agx_icmp_ballot_to(b, out, wx, wy, AGX_ICOND_UEQ, true));
|
||||
|
||||
CASE32(agx_ballot_to(b, out, agx_fcmp(b, wx, wy, AGX_FCOND_GE, false)),
|
||||
agx_fcmp_ballot_to(b, out, wx, wy, AGX_FCOND_GE, false));
|
||||
|
||||
CASE32(agx_quad_ballot_to(b, out, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, true)),
|
||||
agx_icmp_quad_ballot_to(b, out, wx, wy, AGX_ICOND_UEQ, true));
|
||||
|
||||
CASE32(agx_quad_ballot_to(b, out, agx_fcmp(b, wx, wy, AGX_FCOND_GT, false)),
|
||||
agx_fcmp_quad_ballot_to(b, out, wx, wy, AGX_FCOND_GT, false));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, BallotMultipleUses)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index cmp = agx_fcmp(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_index ballot = agx_quad_ballot(b, cmp);
|
||||
agx_fadd_to(b, out, cmp, ballot);
|
||||
},
|
||||
{
|
||||
agx_index cmp = agx_fcmp(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_index ballot =
|
||||
agx_fcmp_quad_ballot(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_fadd_to(b, out, cmp, ballot);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* We had a bug where the ballot optimization didn't check the agx_index's type
|
||||
* so would fuse constants with overlapping values. An unrelated common code
|
||||
* change surfaced this in CTS case:
|
||||
*
|
||||
* dEQP-VK.subgroups.vote.frag_helper.subgroupallequal_bool_fragment
|
||||
*
|
||||
* We passed Vulkan CTS without hitting it though, hence the targeted test.
|
||||
*/
|
||||
TEST_F(Optimizer, BallotConstant)
|
||||
{
|
||||
CASE32(
|
||||
{
|
||||
agx_index cmp = agx_fcmp(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_index ballot = agx_quad_ballot(b, agx_immediate(cmp.value));
|
||||
agx_index ballot2 = agx_quad_ballot(b, cmp);
|
||||
agx_fadd_to(b, out, ballot, agx_fadd(b, ballot2, cmp));
|
||||
},
|
||||
{
|
||||
agx_index cmp = agx_fcmp(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_index ballot = agx_quad_ballot(b, agx_immediate(cmp.value));
|
||||
agx_index ballot2 =
|
||||
agx_fcmp_quad_ballot(b, wx, wy, AGX_FCOND_GT, false);
|
||||
agx_fadd_to(b, out, ballot, agx_fadd(b, ballot2, cmp));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, IfCondition)
|
||||
{
|
||||
CASE_NO_RETURN(agx_if_icmp(b, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, true),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_icmp(b, wx, wy, 1, AGX_ICOND_UEQ, true, NULL));
|
||||
|
||||
CASE_NO_RETURN(agx_if_icmp(b, agx_fcmp(b, wx, wy, AGX_FCOND_EQ, true),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_fcmp(b, wx, wy, 1, AGX_FCOND_EQ, true, NULL));
|
||||
|
||||
CASE_NO_RETURN(agx_if_icmp(b, agx_fcmp(b, hx, hy, AGX_FCOND_LT, false),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_fcmp(b, hx, hy, 1, AGX_FCOND_LT, false, NULL));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, SelectCondition)
|
||||
{
|
||||
CASE32(agx_icmpsel_to(b, out, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, false),
|
||||
agx_zero(), wz, wx, AGX_ICOND_UEQ),
|
||||
agx_icmpsel_to(b, out, wx, wy, wx, wz, AGX_ICOND_UEQ));
|
||||
|
||||
CASE32(agx_icmpsel_to(b, out, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, true),
|
||||
agx_zero(), wz, wx, AGX_ICOND_UEQ),
|
||||
agx_icmpsel_to(b, out, wx, wy, wz, wx, AGX_ICOND_UEQ));
|
||||
|
||||
CASE32(agx_icmpsel_to(b, out, agx_fcmp(b, wx, wy, AGX_FCOND_EQ, false),
|
||||
agx_zero(), wz, wx, AGX_ICOND_UEQ),
|
||||
agx_fcmpsel_to(b, out, wx, wy, wx, wz, AGX_FCOND_EQ));
|
||||
|
||||
CASE32(agx_icmpsel_to(b, out, agx_fcmp(b, wx, wy, AGX_FCOND_LT, true),
|
||||
agx_zero(), wz, wx, AGX_ICOND_UEQ),
|
||||
agx_fcmpsel_to(b, out, wx, wy, wz, wx, AGX_FCOND_LT));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, IfInverted)
|
||||
{
|
||||
CASE_NO_RETURN(
|
||||
agx_if_icmp(b, agx_xor(b, hx, agx_immediate(1)), agx_zero(), 1,
|
||||
AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_icmp(b, hx, agx_zero(), 1, AGX_ICOND_UEQ, false, NULL));
|
||||
|
||||
CASE_NO_RETURN(agx_if_icmp(b, agx_xor(b, hx, agx_immediate(1)), agx_zero(),
|
||||
1, AGX_ICOND_UEQ, false, NULL),
|
||||
agx_if_icmp(b, hx, agx_zero(), 1, AGX_ICOND_UEQ, true, NULL));
|
||||
}
|
||||
|
||||
TEST_F(Optimizer, IfInvertedCondition)
|
||||
{
|
||||
CASE_NO_RETURN(
|
||||
agx_if_icmp(
|
||||
b,
|
||||
agx_xor(b, agx_icmp(b, wx, wy, AGX_ICOND_UEQ, true), agx_immediate(1)),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_icmp(b, wx, wy, 1, AGX_ICOND_UEQ, false, NULL));
|
||||
|
||||
CASE_NO_RETURN(
|
||||
agx_if_icmp(
|
||||
b,
|
||||
agx_xor(b, agx_fcmp(b, wx, wy, AGX_FCOND_EQ, true), agx_immediate(1)),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_fcmp(b, wx, wy, 1, AGX_FCOND_EQ, false, NULL));
|
||||
|
||||
CASE_NO_RETURN(
|
||||
agx_if_icmp(
|
||||
b,
|
||||
agx_xor(b, agx_fcmp(b, hx, hy, AGX_FCOND_LT, false), agx_immediate(1)),
|
||||
agx_zero(), 1, AGX_ICOND_UEQ, true, NULL),
|
||||
agx_if_fcmp(b, hx, hy, 1, AGX_FCOND_LT, true, NULL));
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* Copyright 2022 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_builder.h"
|
||||
#include "agx_compiler.h"
|
||||
#include "agx_test.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
static void
|
||||
pass(agx_context *ctx)
|
||||
{
|
||||
agx_repair_ssa(ctx);
|
||||
agx_reindex_ssa(ctx);
|
||||
}
|
||||
|
||||
#define CASE(instr) \
|
||||
INSTRUCTION_CASE( \
|
||||
{ \
|
||||
bool repaired = false; \
|
||||
instr \
|
||||
}, \
|
||||
{ \
|
||||
bool repaired = true; \
|
||||
instr \
|
||||
}, \
|
||||
pass)
|
||||
|
||||
class RepairSSA : public testing::Test {
|
||||
protected:
|
||||
RepairSSA()
|
||||
{
|
||||
mem_ctx = ralloc_context(NULL);
|
||||
}
|
||||
|
||||
~RepairSSA()
|
||||
{
|
||||
ralloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
void *mem_ctx;
|
||||
};
|
||||
|
||||
static agx_index
|
||||
agx_phi_2(agx_builder *b, agx_index x, agx_index y)
|
||||
{
|
||||
agx_index idx = agx_temp(b->shader, x.size);
|
||||
agx_instr *phi = agx_phi_to(b, idx, 2);
|
||||
phi->src[0] = x;
|
||||
phi->src[1] = y;
|
||||
return idx;
|
||||
}
|
||||
|
||||
TEST_F(RepairSSA, Local)
|
||||
{
|
||||
CASE({
|
||||
agx_index x = agx_mov_imm(b, AGX_SIZE_32, 0xcafe);
|
||||
agx_index y = agx_mov_imm(b, AGX_SIZE_32, 0xefac);
|
||||
|
||||
if (repaired) {
|
||||
agx_unit_test(b, agx_fadd(b, y, x));
|
||||
} else {
|
||||
agx_fadd_to(b, x, y, x);
|
||||
agx_unit_test(b, x);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* A
|
||||
* / \
|
||||
* B C
|
||||
* \ /
|
||||
* D
|
||||
*/
|
||||
TEST_F(RepairSSA, IfElse)
|
||||
{
|
||||
CASE({
|
||||
agx_block *A = agx_start_block(b->shader);
|
||||
agx_block *B = agx_test_block(b->shader);
|
||||
agx_block *C = agx_test_block(b->shader);
|
||||
agx_block *D = agx_test_block(b->shader);
|
||||
|
||||
agx_block_add_successor(A, B);
|
||||
agx_block_add_successor(A, C);
|
||||
|
||||
agx_block_add_successor(B, D);
|
||||
agx_block_add_successor(C, D);
|
||||
|
||||
b->cursor = agx_after_block(B);
|
||||
agx_index x = agx_mov_imm(b, 32, 0xcafe);
|
||||
agx_index y = agx_mov_imm(b, 32, 0xbade);
|
||||
|
||||
b->cursor = agx_after_block(C);
|
||||
agx_index x2 = repaired ? agx_temp(b->shader, AGX_SIZE_32) : x;
|
||||
agx_mov_imm_to(b, x2, 0xefac);
|
||||
agx_index y2 = agx_mov_imm(b, 32, 0xbade);
|
||||
|
||||
b->cursor = agx_after_block(D);
|
||||
if (repaired)
|
||||
x = agx_phi_2(b, x, x2);
|
||||
|
||||
agx_index y3 = agx_phi_2(b, y, y2);
|
||||
agx_unit_test(b, agx_fadd(b, x, y3));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2018 Broadcom
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "drm-shim/drm_shim.h"
|
||||
#include "drm-uapi/asahi_drm.h"
|
||||
|
||||
bool drm_shim_driver_prefers_first_render_node = true;
|
||||
|
||||
static const struct drm_asahi_params_global params = {
|
||||
.gpu_generation = 13,
|
||||
.gpu_variant = 'G',
|
||||
.gpu_revision = 0,
|
||||
.vm_start = 0x4000,
|
||||
.vm_end = 0x7fffff8000ull,
|
||||
.command_timestamp_frequency_hz = 1000000000,
|
||||
};
|
||||
|
||||
struct asahi_bo {
|
||||
struct shim_bo base;
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
static struct asahi_bo *
|
||||
asahi_bo(struct shim_bo *bo)
|
||||
{
|
||||
return (struct asahi_bo *)bo;
|
||||
}
|
||||
|
||||
struct asahi_device {
|
||||
uint64_t next_offset;
|
||||
};
|
||||
|
||||
static struct asahi_device asahi = {
|
||||
.next_offset = 0x1000,
|
||||
};
|
||||
|
||||
static int
|
||||
asahi_ioctl_noop(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
asahi_ioctl_gem_create(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
struct drm_asahi_gem_create *create = arg;
|
||||
struct asahi_bo *bo = calloc(1, sizeof(*bo));
|
||||
|
||||
drm_shim_bo_init(&bo->base, create->size);
|
||||
|
||||
assert(UINT64_MAX - asahi.next_offset > create->size);
|
||||
bo->offset = asahi.next_offset;
|
||||
asahi.next_offset += create->size;
|
||||
|
||||
create->handle = drm_shim_bo_get_handle(shim_fd, &bo->base);
|
||||
|
||||
drm_shim_bo_put(&bo->base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
asahi_ioctl_gem_mmap_offset(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct shim_fd *shim_fd = drm_shim_fd_lookup(fd);
|
||||
struct drm_asahi_gem_mmap_offset *map = arg;
|
||||
struct shim_bo *bo = drm_shim_bo_lookup(shim_fd, map->handle);
|
||||
|
||||
map->offset = drm_shim_bo_get_mmap_offset(shim_fd, bo);
|
||||
|
||||
drm_shim_bo_put(bo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
asahi_ioctl_get_param(int fd, unsigned long request, void *arg)
|
||||
{
|
||||
struct drm_asahi_get_params *gp = arg;
|
||||
|
||||
switch (gp->param_group) {
|
||||
case 0:
|
||||
assert(gp->size == sizeof(struct drm_asahi_params_global));
|
||||
memcpy((void *)gp->pointer, ¶ms, gp->size);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown DRM_IOCTL_ASAHI_GET_PARAMS %d\n",
|
||||
gp->param_group);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static ioctl_fn_t driver_ioctls[] = {
|
||||
[DRM_ASAHI_GET_PARAMS] = asahi_ioctl_get_param,
|
||||
[DRM_ASAHI_VM_CREATE] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_VM_DESTROY] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_VM_BIND] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_GEM_CREATE] = asahi_ioctl_gem_create,
|
||||
[DRM_ASAHI_GEM_MMAP_OFFSET] = asahi_ioctl_gem_mmap_offset,
|
||||
[DRM_ASAHI_QUEUE_CREATE] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_QUEUE_DESTROY] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_GEM_BIND_OBJECT] = asahi_ioctl_noop,
|
||||
[DRM_ASAHI_SUBMIT] = asahi_ioctl_noop,
|
||||
};
|
||||
|
||||
void
|
||||
drm_shim_driver_init(void)
|
||||
{
|
||||
shim_device.bus_type = DRM_BUS_PLATFORM;
|
||||
shim_device.driver_name = "asahi";
|
||||
shim_device.driver_ioctls = driver_ioctls;
|
||||
shim_device.driver_ioctl_count = ARRAY_SIZE(driver_ioctls);
|
||||
|
||||
drm_shim_override_file("DRIVER=asahi\n"
|
||||
"OF_FULLNAME=/soc/agx\n"
|
||||
"OF_COMPATIBLE_0=apple,gpu-g13g\n"
|
||||
"OF_COMPATIBLE_N=1\n",
|
||||
"/sys/dev/char/%d:%d/device/uevent", DRM_MAJOR,
|
||||
render_node_minor);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright 2019 Broadcom
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
libasahi_noop_drm_shim = shared_library(
|
||||
'asahi_noop_drm_shim',
|
||||
'asahi_noop.c',
|
||||
include_directories: [inc_include, inc_src],
|
||||
dependencies: [dep_drm_shim, dep_valgrind],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
install : true,
|
||||
)
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2016 Intel Corporation
|
||||
* Copyright 2016 Broadcom
|
||||
* Copyright 2020 Collabora, Ltd.
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "compiler/libcl/libcl.h"
|
||||
#include "util/bitpack_helpers.h"
|
||||
|
||||
#ifndef __OPENCL_VERSION__
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "util/half_float.h"
|
||||
#endif
|
||||
|
||||
#define __gen_unpack_float(x, y, z) uif(__gen_unpack_uint(x, y, z))
|
||||
#define __gen_unpack_half(x, y, z) \
|
||||
_mesa_half_to_float(__gen_unpack_uint(x, y, z))
|
||||
|
||||
static inline uint64_t
|
||||
__gen_unpack_uint(CONST uint32_t *restrict cl, uint32_t start, uint32_t end)
|
||||
{
|
||||
uint64_t val = 0;
|
||||
const int width = end - start + 1;
|
||||
const uint64_t mask =
|
||||
(width == 64) ? ~((uint64_t)0) : ((uint64_t)1 << width) - 1;
|
||||
|
||||
for (unsigned word = start / 32; word < (end / 32) + 1; word++) {
|
||||
val |= ((uint64_t)cl[word]) << ((word - start / 32) * 32);
|
||||
}
|
||||
|
||||
return (val >> (start % 32)) & mask;
|
||||
}
|
||||
|
||||
/*
|
||||
* LODs are 4:6 fixed point. We must clamp before converting to integers to
|
||||
* avoid undefined behaviour for out-of-bounds inputs like +/- infinity.
|
||||
*/
|
||||
static inline uint32_t
|
||||
__gen_pack_lod(float f, uint32_t start, uint32_t end)
|
||||
{
|
||||
uint32_t fixed = CLAMP(f * (1 << 6), 0 /* 0.0 */, 0x380 /* 14.0 */);
|
||||
return util_bitpack_uint(fixed, start, end);
|
||||
}
|
||||
|
||||
static inline float
|
||||
__gen_unpack_lod(CONST uint32_t *restrict cl, uint32_t start, uint32_t end)
|
||||
{
|
||||
return ((float)__gen_unpack_uint(cl, start, end)) / (1 << 6);
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
__gen_unpack_sint(CONST uint32_t *restrict cl, uint32_t start, uint32_t end)
|
||||
{
|
||||
int size = end - start + 1;
|
||||
int64_t val = __gen_unpack_uint(cl, start, end);
|
||||
|
||||
return util_sign_extend(val, size);
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
__gen_to_groups(uint32_t value, uint32_t group_size, uint32_t length)
|
||||
{
|
||||
/* Zero is not representable, clamp to minimum */
|
||||
if (value == 0)
|
||||
return 1;
|
||||
|
||||
/* Round up to the nearest number of groups */
|
||||
uint32_t groups = DIV_ROUND_UP(value, group_size);
|
||||
|
||||
/* The 0 encoding means "all" */
|
||||
if (groups == ((uint64_t)1) << length)
|
||||
return 0;
|
||||
|
||||
/* Otherwise it's encoded as the identity */
|
||||
assert(groups < (1u << length) && "out of bounds");
|
||||
assert(groups >= 1 && "exhaustive");
|
||||
return groups;
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
__gen_from_groups(uint32_t value, uint32_t group_size, uint32_t length)
|
||||
{
|
||||
return group_size * (value ? value : (1 << length));
|
||||
}
|
||||
|
||||
#define agx_pack(dst, T, name) \
|
||||
for (struct AGX_##T name = {AGX_##T##_header}, \
|
||||
*_loop_count = (GLOBAL void *)((uintptr_t)0); \
|
||||
(uintptr_t)_loop_count < 1; ( \
|
||||
{ \
|
||||
AGX_##T##_pack((GLOBAL uint32_t *)(dst), &name); \
|
||||
_loop_count = (GLOBAL void *)(((uintptr_t)_loop_count) + 1); \
|
||||
}))
|
||||
|
||||
#define agx_unpack(fp, src, T, name) \
|
||||
struct AGX_##T name; \
|
||||
AGX_##T##_unpack(fp, (CONST uint8_t *)(src), &name)
|
||||
|
||||
#define agx_print(fp, T, var, indent) AGX_##T##_print(fp, &(var), indent)
|
||||
|
||||
static inline void
|
||||
agx_merge_helper(uint32_t *dst, const uint32_t *src, size_t bytes)
|
||||
{
|
||||
assert((bytes & 3) == 0);
|
||||
|
||||
for (unsigned i = 0; i < (bytes / 4); ++i)
|
||||
dst[i] |= src[i];
|
||||
}
|
||||
|
||||
#define agx_merge(packed1, packed2, type) \
|
||||
agx_merge_helper((packed1).opaque, (packed2).opaque, AGX_##type##_LENGTH)
|
||||
|
||||
#if defined(NDEBUG) || defined(__OPENCL_VERSION__)
|
||||
#define agx_genxml_validate_bounds(a, b, c)
|
||||
#define agx_genxml_validate_mask(a, b, c, d, e) true
|
||||
#define agx_genxml_validate_exact(a, b, c, d) true
|
||||
#else
|
||||
static inline void
|
||||
agx_genxml_validate_bounds(const char *name, uint64_t value, uint64_t bound)
|
||||
{
|
||||
if (unlikely(value >= bound)) {
|
||||
fprintf(stderr, "%s out-of-bounds, got 0x%" PRIx64 ", max %" PRIx64 "\n",
|
||||
name, value, bound);
|
||||
|
||||
UNREACHABLE("Out-of-bounds pack");
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
agx_genxml_validate_mask(FILE *fp, const char *name, const void *cl_,
|
||||
uint32_t index, uint32_t bad_mask)
|
||||
{
|
||||
const uint32_t *cl = (const uint32_t *)cl_;
|
||||
uint32_t bad = cl[index] & bad_mask;
|
||||
|
||||
if (bad && fp != NULL) {
|
||||
fprintf(
|
||||
fp,
|
||||
"XXX: Unknown field of %s unpacked at word %u got %X, bad mask %X\n",
|
||||
name, index, cl[index], bad);
|
||||
}
|
||||
|
||||
return bad == 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
agx_genxml_validate_exact(FILE *fp, const char *name, uint64_t value,
|
||||
uint64_t exact)
|
||||
{
|
||||
if (value != exact && fp != NULL) {
|
||||
fprintf(fp, "XXX: Expected %s to equal %" PRIx64 " but got %" PRIx64 "\n",
|
||||
name, value, exact);
|
||||
}
|
||||
|
||||
return value == exact;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Everything after this is autogenerated from XML. Do not hand edit. */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,655 @@
|
||||
#encoding=utf-8
|
||||
|
||||
# Copyright 2016 Intel Corporation
|
||||
# Copyright 2016 Broadcom
|
||||
# Copyright 2020 Collabora, Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import xml.parsers.expat
|
||||
import sys
|
||||
import operator
|
||||
import math
|
||||
import platform
|
||||
from functools import reduce
|
||||
|
||||
global_prefix = "agx"
|
||||
|
||||
def to_alphanum(name):
|
||||
substitutions = {
|
||||
' ': '_',
|
||||
'/': '_',
|
||||
'[': '',
|
||||
']': '',
|
||||
'(': '',
|
||||
')': '',
|
||||
'-': '_',
|
||||
':': '',
|
||||
'.': '',
|
||||
',': '',
|
||||
'=': '',
|
||||
'>': '',
|
||||
'#': '',
|
||||
'&': '',
|
||||
'*': '',
|
||||
'"': '',
|
||||
'+': '',
|
||||
'\'': '',
|
||||
'?': '',
|
||||
}
|
||||
|
||||
for i, j in substitutions.items():
|
||||
name = name.replace(i, j)
|
||||
|
||||
return name
|
||||
|
||||
def safe_name(name):
|
||||
name = to_alphanum(name)
|
||||
if not name[0].isalpha():
|
||||
name = '_' + name
|
||||
|
||||
return name
|
||||
|
||||
def prefixed_upper_name(prefix, name):
|
||||
if prefix:
|
||||
name = prefix + "_" + name
|
||||
return safe_name(name).upper()
|
||||
|
||||
def enum_name(name):
|
||||
return f"{global_prefix}_{safe_name(name)}".lower()
|
||||
|
||||
MODIFIERS = ["shr", "minus", "align", "log2", "groups"]
|
||||
|
||||
def parse_modifier(modifier):
|
||||
if modifier is None:
|
||||
return None
|
||||
|
||||
for mod in MODIFIERS:
|
||||
if modifier[0:len(mod)] == mod:
|
||||
if mod == "log2":
|
||||
assert(len(mod) == len(modifier))
|
||||
return [mod]
|
||||
|
||||
if modifier[len(mod)] == '(' and modifier[-1] == ')':
|
||||
ret = [mod, int(modifier[(len(mod) + 1):-1])]
|
||||
if ret[0] == 'align':
|
||||
align = ret[1]
|
||||
# Make sure the alignment is a power of 2
|
||||
assert(align > 0 and not(align & (align - 1)));
|
||||
|
||||
return ret
|
||||
|
||||
print("Invalid modifier")
|
||||
assert(False)
|
||||
|
||||
class Field(object):
|
||||
def __init__(self, parser, attrs):
|
||||
self.parser = parser
|
||||
if "name" in attrs:
|
||||
self.name = safe_name(attrs["name"]).lower()
|
||||
self.human_name = attrs["name"]
|
||||
|
||||
if ":" in str(attrs["start"]):
|
||||
(word, bit) = attrs["start"].split(":")
|
||||
self.start = (int(word) * 32) + int(bit)
|
||||
else:
|
||||
self.start = int(attrs["start"])
|
||||
|
||||
self.end = self.start + int(attrs["size"]) - 1
|
||||
self.type = attrs["type"]
|
||||
|
||||
if self.type == 'bool' and self.start != self.end:
|
||||
print(f"#error Field {self.name} has bool type but more than one bit of size");
|
||||
|
||||
if "prefix" in attrs:
|
||||
self.prefix = safe_name(attrs["prefix"]).upper()
|
||||
else:
|
||||
self.prefix = None
|
||||
|
||||
self.modifier = parse_modifier(attrs.get("modifier"))
|
||||
self.exact = attrs.get("exact")
|
||||
self.default = None
|
||||
|
||||
if self.exact is not None:
|
||||
self.default = self.exact
|
||||
elif self.modifier is not None:
|
||||
# Set the default value to encode to zero
|
||||
mod = self.modifier
|
||||
if mod[0] == 'log2':
|
||||
self.default = 1
|
||||
elif mod[0] == 'minus':
|
||||
self.default = mod[1]
|
||||
elif mod[0] == 'groups':
|
||||
# The zero encoding means "all"
|
||||
self.default = (1 << int(attrs["size"])) * mod[1]
|
||||
elif mod[0] in ['shr', 'align']:
|
||||
# Zero encodes to zero
|
||||
pass
|
||||
else:
|
||||
assert(0)
|
||||
|
||||
# Map enum values
|
||||
if self.type in self.parser.enums and self.default is not None:
|
||||
self.default = safe_name(f'{global_prefix}_{self.type}_{self.default}').upper()
|
||||
|
||||
|
||||
def emit_template_struct(self, dim):
|
||||
if self.type == 'address':
|
||||
type = 'uint64_t'
|
||||
elif self.type == 'bool':
|
||||
type = 'bool'
|
||||
elif self.type in ['float', 'half', 'lod']:
|
||||
type = 'float'
|
||||
elif self.type in ['uint', 'hex'] and self.end - self.start > 32:
|
||||
type = 'uint64_t'
|
||||
elif self.type == 'int':
|
||||
type = 'int32_t'
|
||||
elif self.type in ['uint', 'hex']:
|
||||
type = 'uint32_t'
|
||||
elif self.type in self.parser.structs:
|
||||
type = 'struct ' + self.parser.gen_prefix(safe_name(self.type.upper()))
|
||||
elif self.type in self.parser.enums:
|
||||
type = 'enum ' + enum_name(self.type)
|
||||
else:
|
||||
print(f"#error unhandled type: {self.type}")
|
||||
type = "uint32_t"
|
||||
|
||||
print(" %-36s %s%s;" % (type, self.name, dim))
|
||||
|
||||
for value in self.values:
|
||||
name = prefixed_upper_name(self.prefix, value.name)
|
||||
print("#define %-40s %d" % (name, value.value))
|
||||
|
||||
def overlaps(self, field):
|
||||
return self != field and max(self.start, field.start) <= min(self.end, field.end)
|
||||
|
||||
class Group(object):
|
||||
def __init__(self, parser, parent, start, count, label):
|
||||
self.parser = parser
|
||||
self.parent = parent
|
||||
self.start = start
|
||||
self.count = count
|
||||
self.label = label
|
||||
self.size = 0
|
||||
self.length = 0
|
||||
self.fields = []
|
||||
|
||||
def get_length(self):
|
||||
# Determine number of bytes in this group.
|
||||
calculated = max(field.end // 8 for field in self.fields) + 1 if len(self.fields) > 0 else 0
|
||||
if self.length > 0:
|
||||
assert(self.length >= calculated)
|
||||
else:
|
||||
self.length = calculated
|
||||
return self.length
|
||||
|
||||
|
||||
def emit_template_struct(self, dim):
|
||||
if self.count == 0:
|
||||
print(" /* variable length fields follow */")
|
||||
else:
|
||||
if self.count > 1:
|
||||
dim = "%s[%d]" % (dim, self.count)
|
||||
|
||||
any_fields = False
|
||||
for field in self.fields:
|
||||
if not field.exact:
|
||||
field.emit_template_struct(dim)
|
||||
any_fields = True
|
||||
|
||||
if not any_fields:
|
||||
print(" int dummy;")
|
||||
|
||||
class Word:
|
||||
def __init__(self):
|
||||
self.size = 32
|
||||
self.contributors = []
|
||||
|
||||
class FieldRef:
|
||||
def __init__(self, field, path, start, end):
|
||||
self.field = field
|
||||
self.path = path
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def collect_fields(self, fields, offset, path, all_fields):
|
||||
for field in fields:
|
||||
field_path = f'{path}{field.name}'
|
||||
field_offset = offset + field.start
|
||||
|
||||
if field.type in self.parser.structs:
|
||||
sub_struct = self.parser.structs[field.type]
|
||||
self.collect_fields(sub_struct.fields, field_offset, field_path + '.', all_fields)
|
||||
continue
|
||||
|
||||
start = field_offset
|
||||
end = offset + field.end
|
||||
all_fields.append(self.FieldRef(field, field_path, start, end))
|
||||
|
||||
def collect_words(self, fields, offset, path, words):
|
||||
for field in fields:
|
||||
field_path = f'{path}{field.name}'
|
||||
start = offset + field.start
|
||||
|
||||
if field.type in self.parser.structs:
|
||||
sub_fields = self.parser.structs[field.type].fields
|
||||
self.collect_words(sub_fields, start, field_path + '.', words)
|
||||
continue
|
||||
|
||||
end = offset + field.end
|
||||
contributor = self.FieldRef(field, field_path, start, end)
|
||||
first_word = contributor.start // 32
|
||||
last_word = contributor.end // 32
|
||||
for b in range(first_word, last_word + 1):
|
||||
if not b in words:
|
||||
words[b] = self.Word()
|
||||
words[b].contributors.append(contributor)
|
||||
|
||||
def emit_pack_function(self):
|
||||
self.get_length()
|
||||
|
||||
words = {}
|
||||
self.collect_words(self.fields, 0, '', words)
|
||||
|
||||
# Validate the modifier is lossless
|
||||
for field in self.fields:
|
||||
if field.modifier is None:
|
||||
continue
|
||||
|
||||
if field.modifier[0] == "shr":
|
||||
shift = field.modifier[1]
|
||||
mask = hex((1 << shift) - 1)
|
||||
print(f" assert((values->{field.name} & {mask}) == 0);")
|
||||
elif field.modifier[0] == "minus":
|
||||
print(f" assert(values->{field.name} >= {field.modifier[1]});")
|
||||
elif field.modifier[0] == "log2":
|
||||
print(f" assert(IS_POT_NONZERO(values->{field.name}));")
|
||||
|
||||
for index in range(math.ceil(self.length / 4)):
|
||||
# Handle MBZ words
|
||||
if not index in words:
|
||||
print(" cl[%2d] = 0;" % index)
|
||||
continue
|
||||
|
||||
word = words[index]
|
||||
|
||||
word_start = index * 32
|
||||
|
||||
v = None
|
||||
prefix = " cl[%2d] =" % index
|
||||
|
||||
lines = []
|
||||
|
||||
for contributor in word.contributors:
|
||||
field = contributor.field
|
||||
name = field.name
|
||||
start = contributor.start
|
||||
end = contributor.end
|
||||
contrib_word_start = (start // 32) * 32
|
||||
start -= contrib_word_start
|
||||
end -= contrib_word_start
|
||||
|
||||
value = f"values->{contributor.path}"
|
||||
if field.exact:
|
||||
value = field.default
|
||||
|
||||
# These types all use util_bitpack_uint
|
||||
pack_as_uint = field.type in ["uint", "hex", "address", "bool"]
|
||||
pack_as_uint |= field.type in self.parser.enums
|
||||
start_adjusted = start
|
||||
value_unshifted = None
|
||||
|
||||
if field.modifier is not None:
|
||||
if field.modifier[0] == "shr":
|
||||
if pack_as_uint and start >= field.modifier[1]:
|
||||
# For uint, we fast path. If we do `(a >> 2) << 2`,
|
||||
# clang will generate a mask in release builds, even
|
||||
# though we know we're aligned. So don't generate
|
||||
# that to avoid the masking.
|
||||
start_adjusted = start - field.modifier[1]
|
||||
else:
|
||||
value = f"{value} >> {field.modifier[1]}"
|
||||
elif field.modifier[0] == "minus":
|
||||
value = f"{value} - {field.modifier[1]}"
|
||||
elif field.modifier[0] == "align":
|
||||
value = f"ALIGN_POT({value}, {field.modifier[1]})"
|
||||
elif field.modifier[0] == "log2":
|
||||
value = f"util_logbase2({value})"
|
||||
elif field.modifier[0] == "groups":
|
||||
value = "__gen_to_groups({}, {}, {})".format(value,
|
||||
field.modifier[1], end - start + 1)
|
||||
|
||||
if pack_as_uint:
|
||||
bits = (end - start_adjusted + 1)
|
||||
if bits < 64 and not field.exact:
|
||||
# Add some nicer error checking
|
||||
label = f"{self.label}::{name}"
|
||||
bound = hex(1 << bits)
|
||||
print(f" agx_genxml_validate_bounds(\"{label}\", {value}, {bound}ull);")
|
||||
|
||||
s = f"util_bitpack_uint({value}, {start_adjusted}, {end})"
|
||||
elif field.type == "int":
|
||||
s = "util_bitpack_sint(%s, %d, %d)" % \
|
||||
(value, start, end)
|
||||
elif field.type == "float":
|
||||
assert(start == 0 and end == 31)
|
||||
s = f"util_bitpack_float({value})"
|
||||
elif field.type == "half":
|
||||
assert(start == 0 and end == 15)
|
||||
s = f"_mesa_float_to_half({value})"
|
||||
elif field.type == "lod":
|
||||
assert(end - start + 1 == 10)
|
||||
s = "__gen_pack_lod(%s, %d, %d)" % (value, start, end)
|
||||
else:
|
||||
s = f"#error unhandled field {contributor.path}, type {field.type}"
|
||||
|
||||
if not s == None:
|
||||
shift = word_start - contrib_word_start
|
||||
if shift:
|
||||
s = "%s >> %d" % (s, shift)
|
||||
|
||||
if contributor == word.contributors[-1]:
|
||||
lines.append(f"{prefix} {s};")
|
||||
else:
|
||||
lines.append(f"{prefix} {s} |")
|
||||
prefix = " "
|
||||
|
||||
for ln in lines:
|
||||
print(ln)
|
||||
|
||||
continue
|
||||
|
||||
# Given a field (start, end) contained in word `index`, generate the 32-bit
|
||||
# mask of present bits relative to the word
|
||||
def mask_for_word(self, index, start, end):
|
||||
field_word_start = index * 32
|
||||
start -= field_word_start
|
||||
end -= field_word_start
|
||||
# Cap multiword at one word
|
||||
start = max(start, 0)
|
||||
end = min(end, 32 - 1)
|
||||
count = (end - start + 1)
|
||||
return (((1 << count) - 1) << start)
|
||||
|
||||
def emit_unpack_function(self):
|
||||
# First, verify there is no garbage in unused bits
|
||||
words = {}
|
||||
self.collect_words(self.fields, 0, '', words)
|
||||
validation = []
|
||||
|
||||
for index in range(self.length // 4):
|
||||
base = index * 32
|
||||
word = words.get(index, self.Word())
|
||||
masks = [self.mask_for_word(index, c.start, c.end) for c in word.contributors]
|
||||
mask = reduce(lambda x,y: x | y, masks, 0)
|
||||
|
||||
ALL_ONES = 0xffffffff
|
||||
|
||||
if mask != ALL_ONES:
|
||||
bad_mask = hex(mask ^ ALL_ONES)
|
||||
validation.append(f'agx_genxml_validate_mask(fp, \"{self.label}\", cl, {index}, {bad_mask})')
|
||||
|
||||
fieldrefs = []
|
||||
self.collect_fields(self.fields, 0, '', fieldrefs)
|
||||
for fieldref in fieldrefs:
|
||||
field = fieldref.field
|
||||
convert = None
|
||||
|
||||
args = []
|
||||
args.append('(CONST uint32_t *) cl')
|
||||
args.append(str(fieldref.start))
|
||||
args.append(str(fieldref.end))
|
||||
|
||||
if field.type in set(["uint", "address", "hex"]) | self.parser.enums:
|
||||
convert = "__gen_unpack_uint"
|
||||
elif field.type == "int":
|
||||
convert = "__gen_unpack_sint"
|
||||
elif field.type == "bool":
|
||||
convert = "__gen_unpack_uint"
|
||||
elif field.type == "float":
|
||||
convert = "__gen_unpack_float"
|
||||
elif field.type == "half":
|
||||
convert = "__gen_unpack_half"
|
||||
elif field.type == "lod":
|
||||
convert = "__gen_unpack_lod"
|
||||
else:
|
||||
s = f"/* unhandled field {field.name}, type {field.type} */\n"
|
||||
|
||||
suffix = ""
|
||||
prefix = ""
|
||||
if field.modifier:
|
||||
if field.modifier[0] == "minus":
|
||||
suffix = f" + {field.modifier[1]}"
|
||||
elif field.modifier[0] == "shr":
|
||||
suffix = f" << {field.modifier[1]}"
|
||||
if field.modifier[0] == "log2":
|
||||
prefix = "1 << "
|
||||
elif field.modifier[0] == "groups":
|
||||
prefix = "__gen_from_groups("
|
||||
suffix = ", {}, {})".format(field.modifier[1],
|
||||
fieldref.end - fieldref.start + 1)
|
||||
|
||||
if field.type in self.parser.enums and not field.exact:
|
||||
prefix = f"(enum {enum_name(field.type)}) {prefix}"
|
||||
|
||||
decoded = f"{prefix}{convert}({', '.join(args)}){suffix}"
|
||||
|
||||
if field.exact:
|
||||
name = self.label
|
||||
validation.append(f'agx_genxml_validate_exact(fp, \"{name}\", {decoded}, {field.default})')
|
||||
else:
|
||||
print(f' values->{fieldref.path} = {decoded};')
|
||||
|
||||
if field.modifier and field.modifier[0] == "align":
|
||||
assert(not field.exact)
|
||||
mask = hex(field.modifier[1] - 1)
|
||||
print(f' assert(!(values->{fieldref.path} & {mask}));')
|
||||
|
||||
if len(validation) > 1:
|
||||
print(' bool valid = true;')
|
||||
for v in validation:
|
||||
print(f' valid &= {v};')
|
||||
print(" return valid;")
|
||||
elif len(validation) == 1:
|
||||
print(f" return {validation[0]};")
|
||||
else:
|
||||
print(" return true;")
|
||||
|
||||
def emit_print_function(self):
|
||||
for field in self.fields:
|
||||
convert = None
|
||||
name, val = field.human_name, f'values->{field.name}'
|
||||
|
||||
if field.exact:
|
||||
continue
|
||||
|
||||
if field.type in self.parser.structs:
|
||||
pack_name = self.parser.gen_prefix(safe_name(field.type)).upper()
|
||||
print(f' fprintf(fp, "%*s{field.human_name}:\\n", indent, "");')
|
||||
print(f" {pack_name}_print(fp, &values->{field.name}, indent + 2);")
|
||||
elif field.type == "address":
|
||||
# TODO resolve to name
|
||||
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx64 "\\n", indent, "", {val});')
|
||||
elif field.type in self.parser.enums:
|
||||
print(f' if ({enum_name(field.type)}_as_str({val}))')
|
||||
print(f' fprintf(fp, "%*s{name}: %s\\n", indent, "", {enum_name(field.type)}_as_str({val}));')
|
||||
print(f' else')
|
||||
print(f' fprintf(fp, "%*s{name}: unknown %X (XXX)\\n", indent, "", {val});')
|
||||
elif field.type == "int":
|
||||
print(f' fprintf(fp, "%*s{name}: %d\\n", indent, "", {val});')
|
||||
elif field.type == "bool":
|
||||
print(f' fprintf(fp, "%*s{name}: %s\\n", indent, "", {val} ? "true" : "false");')
|
||||
elif field.type in ["float", "lod", "half"]:
|
||||
print(f' fprintf(fp, "%*s{name}: %f\\n", indent, "", {val});')
|
||||
elif field.type in ["uint", "hex"] and (field.end - field.start) >= 32:
|
||||
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx64 "\\n", indent, "", {val});')
|
||||
elif field.type == "hex":
|
||||
print(f' fprintf(fp, "%*s{name}: 0x%" PRIx32 "\\n", indent, "", {val});')
|
||||
else:
|
||||
print(f' fprintf(fp, "%*s{name}: %u\\n", indent, "", {val});')
|
||||
|
||||
class Value(object):
|
||||
def __init__(self, attrs):
|
||||
self.name = attrs["name"]
|
||||
self.value = int(attrs["value"], 0)
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self):
|
||||
self.parser = xml.parsers.expat.ParserCreate()
|
||||
self.parser.StartElementHandler = self.start_element
|
||||
self.parser.EndElementHandler = self.end_element
|
||||
|
||||
self.struct = None
|
||||
self.structs = {}
|
||||
# Set of enum names we've seen.
|
||||
self.enums = set()
|
||||
|
||||
def gen_prefix(self, name):
|
||||
return f'{global_prefix.upper()}_{name}'
|
||||
|
||||
def start_element(self, name, attrs):
|
||||
if name == "genxml":
|
||||
print(pack_header)
|
||||
elif name == "struct":
|
||||
name = attrs["name"]
|
||||
object_name = self.gen_prefix(safe_name(name.upper()))
|
||||
self.struct = object_name
|
||||
|
||||
self.group = Group(self, None, 0, 1, name)
|
||||
if "size" in attrs:
|
||||
self.group.length = int(attrs["size"])
|
||||
self.group.align = int(attrs["align"]) if "align" in attrs else None
|
||||
self.structs[attrs["name"]] = self.group
|
||||
elif name == "field" and self.group is not None:
|
||||
self.group.fields.append(Field(self, attrs))
|
||||
self.values = []
|
||||
elif name == "enum":
|
||||
self.values = []
|
||||
self.enum = safe_name(attrs["name"])
|
||||
self.enums.add(attrs["name"])
|
||||
if "prefix" in attrs:
|
||||
self.prefix = attrs["prefix"]
|
||||
else:
|
||||
self.prefix= None
|
||||
elif name == "value":
|
||||
self.values.append(Value(attrs))
|
||||
|
||||
def end_element(self, name):
|
||||
if name == "struct":
|
||||
if self.struct is not None:
|
||||
self.emit_struct()
|
||||
self.struct = None
|
||||
|
||||
self.group = None
|
||||
elif name == "field" and self.group is not None:
|
||||
self.group.fields[-1].values = self.values
|
||||
elif name == "enum":
|
||||
self.emit_enum()
|
||||
self.enum = None
|
||||
|
||||
def emit_header(self, name):
|
||||
default_fields = []
|
||||
for field in self.group.fields:
|
||||
if not type(field) is Field or field.exact:
|
||||
continue
|
||||
if field.default is not None:
|
||||
default_fields.append(f" .{field.name} = {field.default}")
|
||||
elif field.type in self.structs:
|
||||
default_fields.append(f" .{field.name} = {{ {self.gen_prefix(safe_name(field.type.upper()))}_header }}")
|
||||
|
||||
if default_fields:
|
||||
print('#define %-40s\\' % (name + '_header'))
|
||||
print(", \\\n".join(default_fields))
|
||||
else:
|
||||
print(f'#define {name}_header 0')
|
||||
print('')
|
||||
|
||||
def emit_template_struct(self, name, group):
|
||||
print("struct %s {" % name)
|
||||
group.emit_template_struct("")
|
||||
print("};\n")
|
||||
|
||||
def emit_pack_function(self, name, group):
|
||||
print("static inline void\n%s_pack(GLOBAL uint32_t * restrict cl,\n%sconst struct %s * restrict values)\n{" %
|
||||
(name, ' ' * (len(name) + 6), name))
|
||||
|
||||
group.emit_pack_function()
|
||||
|
||||
print("}\n")
|
||||
|
||||
print(f"#define {name + '_LENGTH'} {self.group.length}")
|
||||
if self.group.align != None:
|
||||
print(f"#define {name + '_ALIGN'} {self.group.align}")
|
||||
|
||||
# round up to handle 6 half-word USC structures
|
||||
words = (self.group.length + 4 - 1) // 4
|
||||
print(f'struct {name.lower()}_packed {{ uint32_t opaque[{words}];}};')
|
||||
|
||||
def emit_unpack_function(self, name, group):
|
||||
print("static inline bool")
|
||||
print("%s_unpack(FILE *fp, CONST uint8_t * restrict cl,\n%sstruct %s * restrict values)\n{" %
|
||||
(name.upper(), ' ' * (len(name) + 8), name))
|
||||
|
||||
group.emit_unpack_function()
|
||||
|
||||
print("}\n")
|
||||
|
||||
def emit_print_function(self, name, group):
|
||||
print("#ifndef __OPENCL_VERSION__")
|
||||
print("static inline void")
|
||||
print(f"{name.upper()}_print(FILE *fp, const struct {name} * values, unsigned indent)\n{{")
|
||||
|
||||
group.emit_print_function()
|
||||
|
||||
print("}")
|
||||
print("#endif\n")
|
||||
|
||||
def emit_struct(self):
|
||||
name = self.struct
|
||||
|
||||
self.emit_template_struct(self.struct, self.group)
|
||||
self.emit_header(name)
|
||||
self.emit_pack_function(self.struct, self.group)
|
||||
self.emit_unpack_function(self.struct, self.group)
|
||||
self.emit_print_function(self.struct, self.group)
|
||||
|
||||
def enum_prefix(self, name):
|
||||
return
|
||||
|
||||
def emit_enum(self):
|
||||
e_name = enum_name(self.enum)
|
||||
prefix = e_name if self.enum != 'Format' else global_prefix
|
||||
print(f'enum {e_name} {{')
|
||||
|
||||
for value in self.values:
|
||||
name = f'{prefix}_{value.name}'
|
||||
name = safe_name(name).upper()
|
||||
print(f' {name} = {value.value},')
|
||||
print('};\n')
|
||||
|
||||
print("#ifndef __OPENCL_VERSION__")
|
||||
print("static inline const char *")
|
||||
print(f"{e_name.lower()}_as_str(enum {e_name} imm)\n{{")
|
||||
print(" switch (imm) {")
|
||||
for value in self.values:
|
||||
name = f'{prefix}_{value.name}'
|
||||
name = safe_name(name).upper()
|
||||
print(f' case {name}: return "{value.name}";')
|
||||
print(' default: return NULL;')
|
||||
print(" }")
|
||||
print("}")
|
||||
print("#endif\n")
|
||||
|
||||
def parse(self, filename):
|
||||
file = open(filename, "rb")
|
||||
self.parser.ParseFile(file)
|
||||
file.close()
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Missing input files file specified")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = sys.argv[1]
|
||||
pack_header = open(sys.argv[2]).read()
|
||||
|
||||
p = Parser()
|
||||
p.parse(input_file)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright 2017 Intel Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
agx_pack = custom_target(
|
||||
input : ['gen_pack.py', 'cmdbuf.xml', 'agx_pack_header.h'],
|
||||
output : 'agx_pack.h',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
)
|
||||
|
||||
idep_agx_pack = declare_dependency(
|
||||
sources : [agx_pack],
|
||||
dependencies : dep_valgrind,
|
||||
include_directories : include_directories('.'),
|
||||
)
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Whitespace-only changes.
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "util/macros.h"
|
||||
|
||||
/* AGX includes an 8-bit floating-point format for small dyadic immediates,
|
||||
* consisting of 3 bits for the exponent, 4 bits for the mantissa, and 1-bit
|
||||
* for sign, in the usual order. Zero exponent has special handling. */
|
||||
|
||||
static inline float
|
||||
agx_minifloat_decode(uint8_t imm)
|
||||
{
|
||||
float sign = (imm & 0x80) ? -1.0 : 1.0;
|
||||
signed exp = (imm & 0x70) >> 4;
|
||||
unsigned mantissa = (imm & 0xF);
|
||||
|
||||
if (exp)
|
||||
return ldexpf(sign * (float)(mantissa | 0x10), exp - 7);
|
||||
else
|
||||
return ldexpf(sign * ((float)mantissa), -6);
|
||||
}
|
||||
|
||||
/* Encodes a float. Results are only valid if the float can be represented
|
||||
* exactly, if not the result of this function is UNDEFINED. However, it is
|
||||
* guaranteed that this function will not crash on out-of-spec inputs, so it is
|
||||
* safe to call on any input. signbit() is used to ensure -0.0 is handled
|
||||
* correctly.
|
||||
*/
|
||||
static inline uint8_t
|
||||
agx_minifloat_encode(float f)
|
||||
{
|
||||
unsigned sign = signbit(f) ? 0x80 : 0;
|
||||
f = fabsf(f);
|
||||
|
||||
/* frac is in [0.5, 1) and f = frac * 2^exp */
|
||||
int exp = 0;
|
||||
float frac = frexpf(f, &exp);
|
||||
|
||||
if (f >= 0.25) {
|
||||
unsigned mantissa = (frac * 32.0);
|
||||
exp -= 5; /* 2^5 = 32 */
|
||||
exp = CLAMP(exp + 7, 0, 7);
|
||||
|
||||
return sign | (exp << 4) | (mantissa & 0xF);
|
||||
} else {
|
||||
unsigned mantissa = (f * 64.0f);
|
||||
|
||||
return sign | mantissa;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
agx_minifloat_exact(float f)
|
||||
{
|
||||
float f_ = agx_minifloat_decode(agx_minifloat_encode(f));
|
||||
return memcmp(&f, &f_, sizeof(float)) == 0;
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright 2025 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "asahi/isa/agx_minifloat.h"
|
||||
#include "util/bitset.h"
|
||||
#include "util/u_math.h"
|
||||
#include "disasm.h"
|
||||
|
||||
struct disasm_ctx {
|
||||
FILE *fp;
|
||||
bool any_operands;
|
||||
bool error;
|
||||
};
|
||||
|
||||
static inline uint64_t
|
||||
bits(BITSET_WORD *word, unsigned start, unsigned size, unsigned shift)
|
||||
{
|
||||
return ((uint64_t)__bitset_extract(word, start, size)) << shift;
|
||||
}
|
||||
|
||||
typedef void (*disasm_instr)(struct disasm_ctx *ctx, BITSET_WORD *code);
|
||||
|
||||
struct disasm_spec {
|
||||
const char *display;
|
||||
disasm_instr disassemble;
|
||||
unsigned length_bit;
|
||||
unsigned length_short;
|
||||
unsigned length_long;
|
||||
BITSET_WORD mask[4];
|
||||
BITSET_WORD exact[4];
|
||||
BITSET_WORD known[4];
|
||||
};
|
||||
|
||||
static void
|
||||
introduce_operand(struct disasm_ctx *ctx)
|
||||
{
|
||||
if (ctx->any_operands) {
|
||||
fprintf(ctx->fp, ", ");
|
||||
} else {
|
||||
fprintf(ctx->fp, " ");
|
||||
}
|
||||
|
||||
ctx->any_operands = true;
|
||||
}
|
||||
|
||||
enum operand_kind {
|
||||
KIND_NONE = 0,
|
||||
KIND_REG,
|
||||
KIND_UNIFORM,
|
||||
KIND_CF,
|
||||
KIND_TS,
|
||||
KIND_SS,
|
||||
KIND_IMM,
|
||||
KIND_FIMM,
|
||||
};
|
||||
|
||||
struct operand_desc {
|
||||
enum operand_kind kind;
|
||||
int32_t value;
|
||||
unsigned hint, count;
|
||||
bool optional, size16, size32, size64, abs, neg, sx, cache, lu;
|
||||
};
|
||||
|
||||
static void
|
||||
_print_operand(struct disasm_ctx *ctx, struct operand_desc d)
|
||||
{
|
||||
if (d.kind == KIND_NONE) {
|
||||
if (!d.optional) {
|
||||
introduce_operand(ctx);
|
||||
fprintf(ctx->fp, "_");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned size = d.size64 ? 64 : d.size32 ? 32 : 16;
|
||||
bool cache = d.cache || d.hint == 2;
|
||||
bool lu = d.lu || d.hint == 3;
|
||||
unsigned count = MAX2(d.count, 1);
|
||||
|
||||
introduce_operand(ctx);
|
||||
|
||||
if (lu && cache) {
|
||||
fprintf(ctx->fp, "XXX invalid cache+lu set\n");
|
||||
ctx->error = true;
|
||||
}
|
||||
|
||||
if (lu)
|
||||
fprintf(ctx->fp, "^");
|
||||
if (cache)
|
||||
fprintf(ctx->fp, "$");
|
||||
|
||||
const char *prefixes[][2] = {
|
||||
[KIND_REG] = {"r", "dr"}, [KIND_UNIFORM] = {"u", "du"},
|
||||
[KIND_CF] = {"cf"}, [KIND_TS] = {"ts"},
|
||||
[KIND_SS] = {"ss"},
|
||||
};
|
||||
|
||||
if (d.kind == KIND_IMM) {
|
||||
fprintf(ctx->fp, "%d", d.value);
|
||||
} else if (d.kind == KIND_FIMM) {
|
||||
float f = agx_minifloat_decode(d.value);
|
||||
|
||||
/* Match python's float printing */
|
||||
if (f == (int)f)
|
||||
fprintf(ctx->fp, "%g.0", agx_minifloat_decode(d.value));
|
||||
else
|
||||
fprintf(ctx->fp, "%g", agx_minifloat_decode(d.value));
|
||||
} else if (d.kind == KIND_CF || d.kind == KIND_TS || d.kind == KIND_SS) {
|
||||
fprintf(ctx->fp, "%s%u", prefixes[d.kind][0], d.value);
|
||||
} else {
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
if (i)
|
||||
fprintf(ctx->fp, "_");
|
||||
|
||||
unsigned reg = d.value + (i * (size / 16));
|
||||
unsigned whole = reg >> 1;
|
||||
unsigned part = reg & 1;
|
||||
const char *prefix = prefixes[d.kind][size == 64];
|
||||
|
||||
if (size == 16) {
|
||||
fprintf(ctx->fp, "%s%u%c", prefix, whole, "lh"[part]);
|
||||
} else {
|
||||
if (part != 0) {
|
||||
fprintf(ctx->fp, "# 32-bit must be expected, but got raw %u\n",
|
||||
reg);
|
||||
ctx->error = true;
|
||||
}
|
||||
|
||||
fprintf(ctx->fp, "%s%u", prefix, whole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (d.abs)
|
||||
fprintf(ctx->fp, ".abs");
|
||||
if (d.neg)
|
||||
fprintf(ctx->fp, ".neg");
|
||||
if (d.sx)
|
||||
fprintf(ctx->fp, ".sx");
|
||||
}
|
||||
|
||||
static void
|
||||
print_immediate(struct disasm_ctx *ctx, bool is_signed, uint32_t value)
|
||||
{
|
||||
introduce_operand(ctx);
|
||||
fprintf(ctx->fp, is_signed ? "%d" : "%u", value);
|
||||
}
|
||||
|
||||
static void
|
||||
_print_enum(struct disasm_ctx *ctx, const char **arr, unsigned n,
|
||||
unsigned value)
|
||||
{
|
||||
if (value >= n || arr[value] == NULL) {
|
||||
introduce_operand(ctx);
|
||||
fprintf(ctx->fp, "XXX: Unknown enum value %u", value);
|
||||
ctx->error = true;
|
||||
} else if (arr[value][0] != 0) {
|
||||
introduce_operand(ctx);
|
||||
fprintf(ctx->fp, "%s", arr[value]);
|
||||
}
|
||||
}
|
||||
|
||||
#define print_enum(ctx, arr, value) \
|
||||
_print_enum(ctx, arr, ARRAY_SIZE(arr), value)
|
||||
|
||||
static void
|
||||
print_modifier(struct disasm_ctx *ctx, const char *display, unsigned value)
|
||||
{
|
||||
if (value) {
|
||||
introduce_operand(ctx);
|
||||
fprintf(ctx->fp, "%s", display);
|
||||
}
|
||||
}
|
||||
|
||||
static signed
|
||||
_disassemble_instr(BITSET_WORD *code, FILE *fp, struct disasm_spec *specs,
|
||||
unsigned nr_specs, unsigned offset, bool verbose)
|
||||
{
|
||||
BITSET_WORD tmp[4];
|
||||
memcpy(tmp, code, sizeof(tmp));
|
||||
struct disasm_spec *spec = NULL;
|
||||
unsigned n = 0;
|
||||
bool match = false;
|
||||
BITSET_WORD masked[4] = {0};
|
||||
|
||||
for (unsigned i = 0; i < nr_specs; ++i) {
|
||||
spec = &specs[i];
|
||||
|
||||
n = spec->length_short;
|
||||
if (BITSET_TEST(tmp, spec->length_bit)) {
|
||||
n = spec->length_long;
|
||||
}
|
||||
|
||||
match = true;
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(tmp); ++i) {
|
||||
masked[i] = tmp[i];
|
||||
|
||||
uint32_t bytes_left = (n - (i * 4));
|
||||
if (bytes_left < 4) {
|
||||
masked[i] &= ((1 << (bytes_left * 8)) - 1);
|
||||
}
|
||||
|
||||
if ((masked[i] & spec->mask[i]) != spec->exact[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
break;
|
||||
}
|
||||
|
||||
struct disasm_ctx ctx = {.fp = fp};
|
||||
|
||||
if (match) {
|
||||
BITSET_WORD unknown[4];
|
||||
__bitset_andnot(unknown, masked, spec->known, ARRAY_SIZE(masked));
|
||||
int i;
|
||||
BITSET_FOREACH_SET(i, unknown, n * 8) {
|
||||
fprintf(fp, "# XXX: Unknown bit set %u\n", i);
|
||||
ctx.error = true;
|
||||
}
|
||||
} else {
|
||||
n = 2;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
fprintf(fp, "%4x: ", offset);
|
||||
for (unsigned i = 0; i < n; ++i) {
|
||||
fprintf(fp, "%02x", __bitset_extract(masked, i * 8, 8));
|
||||
}
|
||||
for (unsigned i = n; i < 11; ++i) {
|
||||
fprintf(fp, " ");
|
||||
}
|
||||
fprintf(fp, " ");
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
fprintf(fp, "<unknown instr>\n");
|
||||
return -n;
|
||||
}
|
||||
|
||||
fprintf(fp, "%s", spec->display);
|
||||
|
||||
if (spec->disassemble)
|
||||
spec->disassemble(&ctx, masked);
|
||||
|
||||
fprintf(fp, "\n");
|
||||
|
||||
return ctx.error ? -n : n;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 Valve Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "util/bitset.h"
|
||||
|
||||
signed agx2_disassemble_instr(BITSET_WORD *code, FILE *fp, unsigned offset,
|
||||
bool verbose);
|
||||
|
||||
static inline bool
|
||||
agx2_disassemble(uint8_t *code, size_t max_len, FILE *fp)
|
||||
{
|
||||
int i = 0;
|
||||
bool errors = false;
|
||||
|
||||
while (i < max_len) {
|
||||
/* Break on trap */
|
||||
if (code[i + 0] == 0x08 && code[i + 1] == 0x00)
|
||||
break;
|
||||
|
||||
signed ret =
|
||||
agx2_disassemble_instr((BITSET_WORD *)(code + i), fp, i, true);
|
||||
if (ret < 0)
|
||||
fprintf(fp, "XXX error here\n");
|
||||
|
||||
errors |= ret < 0;
|
||||
i += abs(ret);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
# Copyright 2016 Intel Corporation
|
||||
# Copyright 2016 Broadcom
|
||||
# Copyright 2020 Collabora, Ltd.
|
||||
# Copyright 2025 Valve Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from mako.template import Template
|
||||
from mako import exceptions
|
||||
from isa import isa
|
||||
|
||||
# XXX: deduplicate
|
||||
def to_alphanum(name):
|
||||
substitutions = {
|
||||
' ': '_',
|
||||
'/': '_',
|
||||
'[': '',
|
||||
']': '',
|
||||
'(': '',
|
||||
')': '',
|
||||
'-': '_',
|
||||
':': '',
|
||||
'.': '',
|
||||
',': '',
|
||||
'=': '',
|
||||
'>': '',
|
||||
'#': '',
|
||||
'&': '',
|
||||
'*': '',
|
||||
'"': '',
|
||||
'+': '',
|
||||
'\'': '',
|
||||
}
|
||||
|
||||
for i, j in substitutions.items():
|
||||
name = name.replace(i, j)
|
||||
|
||||
return name
|
||||
|
||||
def safe_name(name):
|
||||
name = to_alphanum(name)
|
||||
if not name[0].isalpha():
|
||||
name = '_' + name
|
||||
|
||||
return name.lower()
|
||||
|
||||
def chunk(x):
|
||||
chunks = [hex((x >> (32 * i)) & ((1 << 32) - 1)) for i in range(4)]
|
||||
while len(chunks) > 1 and chunks[-1] == '0x0':
|
||||
chunks.pop()
|
||||
|
||||
return f'{{{", ".join(chunks)}}}'
|
||||
|
||||
def instr_is_trivial(I):
|
||||
return len(I.dests + I.sources + I.immediates + I.modifiers) == 0
|
||||
|
||||
def extract_bits(x, bitset="r"):
|
||||
parts = []
|
||||
n = 0
|
||||
|
||||
for r in x.bits.ranges:
|
||||
parts.append(f'bits({bitset}, {r.start}, {r.size}, {n})')
|
||||
n += r.size
|
||||
|
||||
return ' | '.join(parts)
|
||||
|
||||
template = """
|
||||
#include "disasm-internal.h"
|
||||
|
||||
% for en in isa.enums:
|
||||
UNUSED static const char *enum_${safe_name(isa.enums[en].kind)}[] = {
|
||||
% for n, v in isa.enums[en].values.items():
|
||||
[${n}] = "${v.display or ''}",
|
||||
% endfor
|
||||
};
|
||||
% endfor
|
||||
|
||||
% for k, enc in isa.encodings.items():
|
||||
static void
|
||||
print_${k}(struct disasm_ctx *ctx, BITSET_WORD *orig, uint64_t v, bool source)
|
||||
{
|
||||
BITSET_WORD r[] = { v, v >> 32 };
|
||||
% for i, case in enumerate(enc.cases):
|
||||
${"} else " if i > 0 else ""}if ((v & ${hex(case.exact.mask)}ull) == ${hex(case.exact.value)}ull) {
|
||||
% for mod in case.modifiers:
|
||||
% if mod.name == 'hint':
|
||||
if (${extract_bits(mod)} == 0) {
|
||||
fprintf(ctx->fp, "# missing hint");
|
||||
}
|
||||
|
||||
% endif
|
||||
% endfor
|
||||
_print_operand(ctx, (struct operand_desc){
|
||||
% if case.kind is not None:
|
||||
.kind = KIND_${case.kind.name.upper()},
|
||||
% if case.kind.kind == 'signed':
|
||||
.value = util_sign_extend(${extract_bits(case.kind)} << ${case.kind.shift}, ${case.kind.bits.size()}),
|
||||
% else:
|
||||
.value = ${extract_bits(case.kind)} << ${case.kind.shift},
|
||||
% endif
|
||||
% endif
|
||||
% for mod in case.modifiers:
|
||||
% if mod.name == 'mask':
|
||||
.count = util_bitcount(${extract_bits(mod)}),
|
||||
% elif mod.name == 'count':
|
||||
.count = (${extract_bits(mod)}) ?: 4,
|
||||
% elif mod.name == 'zx':
|
||||
.sx = ${extract_bits(mod)} ^ 1,
|
||||
% else:
|
||||
.${mod.name} = ${extract_bits(mod)},
|
||||
% endif
|
||||
% endfor
|
||||
% for prop, v in case.properties.items():
|
||||
.${prop} = ${v},
|
||||
% endfor
|
||||
.optional = source,
|
||||
});
|
||||
% endfor
|
||||
} else {
|
||||
fprintf(ctx->fp, "# XXX: Invalid value 0x%"PRIx64" for ${k}", v);
|
||||
ctx->error = true;
|
||||
}
|
||||
}
|
||||
|
||||
% endfor
|
||||
|
||||
% for k, instr in isa.instrs.items():
|
||||
% if not instr_is_trivial(instr):
|
||||
static void
|
||||
print_${instr.name}(struct disasm_ctx *ctx, BITSET_WORD *r)
|
||||
{
|
||||
% for i, op in enumerate(instr.dests):
|
||||
print_${op.kind}(ctx, r, ${extract_bits(op)}, false);
|
||||
% endfor
|
||||
% for i, op in enumerate(instr.sources):
|
||||
print_${op.kind}(ctx, r, ${extract_bits(op)}, true);
|
||||
% endfor
|
||||
% for imm in instr.immediates:
|
||||
print_immediate(ctx, ${'true' if imm.kind == 'signed' else 'false'}, ${extract_bits(imm)});
|
||||
% endfor
|
||||
% for mod in instr.modifiers:
|
||||
% if mod.name != 'length':
|
||||
% if mod.kind in isa.enums:
|
||||
print_enum(ctx, enum_${mod.kind}, ${extract_bits(mod)});
|
||||
% else:
|
||||
print_modifier(ctx, "${mod.display}", ${extract_bits(mod)});
|
||||
% endif
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
|
||||
% endif
|
||||
% endfor
|
||||
struct disasm_spec decodings[] = {
|
||||
% for k, instr in isa.instrs.items():
|
||||
{ "${instr.display if instr_is_trivial(instr) else f'{instr.display:<8}'}", ${'NULL' if instr_is_trivial(instr) else f'print_{instr.name}'}, ${instr.length_bit}, ${instr.length[0]}, ${instr.length[-1]}, ${chunk(instr.exact.mask)}, ${chunk(instr.exact.value)}, ${chunk(instr.mask())} },
|
||||
% endfor
|
||||
};
|
||||
|
||||
signed
|
||||
agx2_disassemble_instr(BITSET_WORD *code, FILE *fp, unsigned offset, bool verbose)
|
||||
{
|
||||
return _disassemble_instr(code, fp, decodings, ARRAY_SIZE(decodings), offset, verbose);
|
||||
}"""
|
||||
|
||||
try:
|
||||
print(Template(template).render(isa = isa, safe_name = safe_name, chunk = chunk, instr_is_trivial = instr_is_trivial, extract_bits = extract_bits))
|
||||
except:
|
||||
print(exceptions.text_error_template().render())
|
||||
@@ -0,0 +1,321 @@
|
||||
# Copyright 2025 Valve Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from dataclasses import dataclass
|
||||
from functools import reduce
|
||||
import xml.etree.ElementTree as ET
|
||||
import sys
|
||||
import copy
|
||||
import os
|
||||
|
||||
def ensure(cond, msg):
|
||||
if not cond:
|
||||
print(msg)
|
||||
sys.exit(1)
|
||||
|
||||
class BitRange:
|
||||
def __init__(self, start, end):
|
||||
assert(start <= end)
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
if self.start >= 0:
|
||||
self.size = end - start + 1
|
||||
self.size_mask = (1 << self.size) - 1
|
||||
self.mask = self.size_mask << start
|
||||
|
||||
def parse(spec: str):
|
||||
if ':' in spec:
|
||||
start, end = [int(x) for x in spec.split(':')]
|
||||
return BitRange(start, end)
|
||||
else:
|
||||
return BitRange(int(spec), int(spec))
|
||||
|
||||
def specialize(self, length):
|
||||
return BitRange(self.start % length, self.end % length)
|
||||
|
||||
def __repr__(self):
|
||||
if self.start == self.end:
|
||||
return f'{self.start}'
|
||||
else:
|
||||
return f'{self.start}:{self.end}'
|
||||
|
||||
class Bits:
|
||||
def __init__(self, spec = None):
|
||||
if spec is not None:
|
||||
self.ranges = [BitRange.parse(r) for r in spec.split(' ')]
|
||||
else:
|
||||
self.ranges = []
|
||||
|
||||
def size(self) -> int:
|
||||
return sum([x.size for x in self.ranges])
|
||||
|
||||
def mask(self) -> int:
|
||||
masks = [r.mask for r in self.ranges]
|
||||
union = 0
|
||||
for mask in masks:
|
||||
union |= mask
|
||||
|
||||
# No overlap
|
||||
assert(sum(masks) == union)
|
||||
return union
|
||||
|
||||
def extract(self, x):
|
||||
accum = 0
|
||||
n = 0
|
||||
|
||||
for r in self.ranges:
|
||||
accum |= ((x >> r.start) & r.size_mask) << n
|
||||
n += r.size
|
||||
|
||||
return accum
|
||||
|
||||
def specialize(self, length):
|
||||
obj = copy.copy(self)
|
||||
obj.ranges = [r.specialize(length) for r in obj.ranges]
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return ' '.join([str(r) for r in self.ranges])
|
||||
|
||||
def parse_bitstring(bits):
|
||||
value = 0
|
||||
|
||||
# Reverse to deal with the convention
|
||||
bits = bits[::-1]
|
||||
|
||||
for i, bit in enumerate(bits):
|
||||
assert(bit in ['0', '1'])
|
||||
if bit == '1':
|
||||
value |= (1 << i)
|
||||
|
||||
return value
|
||||
|
||||
# Spread the bits of value out according to the 1s in mask.
|
||||
# Similar to pdep in x86.
|
||||
def deposit_bits(mask, value):
|
||||
accum = 0
|
||||
value_i = 0
|
||||
|
||||
assert(mask < (1 << 128))
|
||||
for m in range(128):
|
||||
if mask & (1 << m):
|
||||
if (value & (1 << value_i)) != 0:
|
||||
accum |= (1 << m)
|
||||
|
||||
value_i += 1
|
||||
|
||||
assert((accum & ~mask) == 0)
|
||||
return accum
|
||||
|
||||
def substitute(text, substitutions):
|
||||
if text in substitutions:
|
||||
return substitutions[text]
|
||||
else:
|
||||
return text
|
||||
|
||||
class Exact:
|
||||
def __init__(self, el = None, subst = None):
|
||||
bits = 0
|
||||
if el is not None:
|
||||
self.mask = Bits(el.attrib['bit']).mask()
|
||||
if el.tag != 'zero':
|
||||
text = substitute(el.text, subst)
|
||||
bits = parse_bitstring(text)
|
||||
assert(len(text) == self.mask.bit_count())
|
||||
else:
|
||||
self.mask = 0
|
||||
|
||||
self.value = deposit_bits(self.mask, bits)
|
||||
|
||||
def union(self, other):
|
||||
self.value |= other.value
|
||||
self.mask |= other.mask
|
||||
|
||||
class EnumValue:
|
||||
def __init__(self, el):
|
||||
self.name = el.text
|
||||
self.label = el.attrib.get('label')
|
||||
self.default = bool(el.attrib.get('default', False))
|
||||
self.display = None if self.default else self.name
|
||||
|
||||
class Enum:
|
||||
def __init__(self, el):
|
||||
self.name = el.attrib['name']
|
||||
self.kind = el.attrib['kind']
|
||||
self.values = {}
|
||||
|
||||
for child in el:
|
||||
self.values[int(child.attrib['value'])] = EnumValue(child)
|
||||
|
||||
class Immediate:
|
||||
def __init__(self, el, name):
|
||||
self.name = name
|
||||
self.kind = el.attrib.get('kind', 'int')
|
||||
self.bits = Bits(el.attrib['bit'])
|
||||
self.shift = int(el.attrib.get('shift', 0))
|
||||
|
||||
class Operand:
|
||||
def __init__(self, el, is_dest):
|
||||
self.kind = el.attrib['kind']
|
||||
self.bits = Bits(el.attrib['bit'])
|
||||
self.is_dest = is_dest
|
||||
|
||||
def specialize(self, length):
|
||||
obj = copy.copy(self)
|
||||
obj.bits = obj.bits.specialize(length)
|
||||
return obj
|
||||
|
||||
class EncodingCase:
|
||||
def __init__(self, el):
|
||||
self.exact = Exact()
|
||||
self.modifiers = []
|
||||
self.kind = None
|
||||
self.properties = {}
|
||||
|
||||
for ch in el:
|
||||
if ch.tag in ["exact", "zero"]:
|
||||
self.exact.union(Exact(ch, {}))
|
||||
elif ch.tag == "immediate":
|
||||
assert(self.kind is None)
|
||||
self.kind = Immediate(ch, ch.attrib['name'])
|
||||
elif ch.tag == "modifier":
|
||||
self.modifiers.append(Modifier(ch, ch.attrib['name']))
|
||||
elif ch.tag == "property":
|
||||
self.properties[ch.attrib['name']] = ch.text or '1'
|
||||
|
||||
def union(self, other):
|
||||
self.exact.union(other.exact)
|
||||
self.modifiers = other.modifiers + self.modifiers
|
||||
self.properties.update(other.properties)
|
||||
|
||||
assert(self.kind is None or other.kind is None)
|
||||
self.kind = self.kind or other.kind
|
||||
|
||||
class Encoding:
|
||||
def __init__(self, el):
|
||||
base = EncodingCase(el)
|
||||
cases = [EncodingCase(ch) for ch in el if ch.tag == 'case']
|
||||
|
||||
if len(cases) == 0:
|
||||
self.cases = [base]
|
||||
else:
|
||||
self.cases = cases
|
||||
for c in self.cases:
|
||||
c.union(base)
|
||||
|
||||
class Modifier:
|
||||
def __init__(self, el, name):
|
||||
self.name = name
|
||||
self.display = el.get('display', name)
|
||||
self.kind = el.get('kind', 'int')
|
||||
self.bits = Bits(el.attrib['bit'])
|
||||
|
||||
class Instruction:
|
||||
def __init__(self, xml, name, display, substitutions, defs):
|
||||
length = [int(x) for x in xml.attrib['length'].split('/')]
|
||||
exact = Exact()
|
||||
mods = []
|
||||
dests = []
|
||||
srcs = []
|
||||
imms = []
|
||||
|
||||
for el in xml:
|
||||
if el.tag in ["exact", "zero"]:
|
||||
exact.union(Exact(el, substitutions))
|
||||
elif el.tag == "immediate":
|
||||
imms.append(Immediate(el, el.attrib['name']))
|
||||
elif el.tag == "src":
|
||||
srcs.append(Operand(el, False))
|
||||
elif el.tag == "dest":
|
||||
dests.append(Operand(el, True))
|
||||
elif el.tag == "modifier":
|
||||
mods.append(Modifier(el, el.attrib['name']))
|
||||
elif el.tag in defs:
|
||||
d = defs[el.tag]
|
||||
if isinstance(d, Operand):
|
||||
spec = d.specialize(length[-1] * 8)
|
||||
if spec.is_dest:
|
||||
dests.append(spec)
|
||||
else:
|
||||
srcs.append(spec)
|
||||
elif isinstance(d, Modifier):
|
||||
mods.append(d)
|
||||
elif isinstance(d, Immediate):
|
||||
imms.append(d)
|
||||
else:
|
||||
assert(0)
|
||||
elif el.tag == 'ins':
|
||||
assert(xml.tag == 'group')
|
||||
else:
|
||||
print(name)
|
||||
assert(0)
|
||||
|
||||
# Infer length bit if needed
|
||||
if len(length) > 1 and not any([x.name == 'length' for x in mods]):
|
||||
mods.append(defs['length'])
|
||||
|
||||
self.length_spec = None
|
||||
self.length_bit = 0
|
||||
for mod in mods:
|
||||
if mod.name == 'length':
|
||||
self.length_spec = mod
|
||||
self.length_bit = mod.bits.ranges[0].start
|
||||
self.name = name
|
||||
self.display = display or name
|
||||
self.length = length
|
||||
self.exact = exact
|
||||
self.modifiers = mods
|
||||
self.dests = dests
|
||||
self.sources = srcs
|
||||
self.immediates = imms
|
||||
|
||||
def mask(self):
|
||||
mask = self.exact.mask
|
||||
parts = self.modifiers + self.immediates + self.sources + self.dests
|
||||
bits = [x.bits.mask() for x in parts]
|
||||
return self.exact.mask | reduce(lambda x, y: x | y, bits, 0)
|
||||
|
||||
class ISA:
|
||||
def __init__(self, el):
|
||||
self.enums = {}
|
||||
self.instrs = {}
|
||||
self.encodings = {}
|
||||
defs = {}
|
||||
|
||||
for child in el:
|
||||
name = child.get('name')
|
||||
|
||||
if child.tag == "define":
|
||||
if 'src' in child.attrib:
|
||||
defs[child.attrib['src']] = Operand(child, False)
|
||||
elif 'dest' in child.attrib:
|
||||
defs[child.attrib['dest']] = Operand(child, True)
|
||||
elif 'modifier' in child.attrib:
|
||||
name = child.attrib['modifier']
|
||||
defs[child.attrib['modifier']] = Modifier(child, name)
|
||||
elif 'immediate' in child.attrib:
|
||||
name = child.attrib['immediate']
|
||||
defs[child.attrib['immediate']] = Immediate(child, name)
|
||||
else:
|
||||
assert(0)
|
||||
elif child.tag == "encoding":
|
||||
self.encodings[child.attrib['name']] = Encoding(child)
|
||||
elif child.tag == "enum":
|
||||
enum = Enum(child)
|
||||
self.enums[enum.kind] = enum
|
||||
elif child.tag == "ins":
|
||||
ins = Instruction(child, name, child.get('display'), {}, defs)
|
||||
ensure(ins.name not in self.instrs, f'Multiple instructions named {ins.name}')
|
||||
self.instrs[ins.name] = ins
|
||||
elif child.tag == "group":
|
||||
ensure(any([c.tag == "ins" for c in child]), f'No instructions in group {name}')
|
||||
|
||||
for c in child:
|
||||
if c.tag == "ins":
|
||||
ins = Instruction(child, c.attrib['name'], c.get('display'), c.attrib, defs)
|
||||
ensure(ins.name not in self.instrs, f'Multiple instructions named {ins.name}')
|
||||
self.instrs[ins.name] = ins
|
||||
|
||||
xmlfile = os.path.join(os.path.dirname(__file__), 'AGX2.xml')
|
||||
isa = ISA(ET.parse(xmlfile).getroot())
|
||||
@@ -0,0 +1,43 @@
|
||||
# Copyright 2025 Valve Corporation
|
||||
# Copyright 2020-2021 Collabora
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
agx2_disasm_c = custom_target(
|
||||
input : ['gen-disasm.py', 'AGX2.xml'],
|
||||
output : 'agx2_disasm.c',
|
||||
command : [prog_python, '@INPUT@'],
|
||||
capture : true,
|
||||
depend_files : files('isa.py'),
|
||||
)
|
||||
|
||||
libagx2_disasm = static_library(
|
||||
'agx2_disasm',
|
||||
agx2_disasm_c,
|
||||
include_directories : [inc_include, inc_src],
|
||||
c_args : [no_override_init_args],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
build_by_default : false,
|
||||
)
|
||||
|
||||
if with_tests
|
||||
test(
|
||||
'agx2_disasm',
|
||||
executable(
|
||||
'agx2_disasm_test',
|
||||
files('test/test-disassembler.c'),
|
||||
c_args : [c_msvc_compat_args, no_override_init_args],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
include_directories : [inc_include, inc_src],
|
||||
link_with : [libagx2_disasm],
|
||||
),
|
||||
suite : ['asahi'],
|
||||
args : files('test/cases.txt'),
|
||||
)
|
||||
|
||||
test(
|
||||
'agx2_python_disasm',
|
||||
prog_python,
|
||||
args : files('test-disasm.py', 'test/cases.txt'),
|
||||
suite : ['asahi']
|
||||
)
|
||||
endif
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright 2025 Valve Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import sys
|
||||
import os
|
||||
import textwrap
|
||||
import binascii
|
||||
from test.disasm import disasm
|
||||
|
||||
errors = False
|
||||
new_cases = []
|
||||
|
||||
for case in open(sys.argv[1]).read().strip().split('\n'):
|
||||
first = case.split(' ')[0]
|
||||
ref = case[len(first):].strip()
|
||||
|
||||
# Extract bytes
|
||||
lst = [int(x, 16) for x in textwrap.wrap(first, 2)]
|
||||
|
||||
# Make sure we don't depend on zeroes in upper bits
|
||||
padded = lst + [0xCA, 0xFE, 0xBA, 0xBE] * 16
|
||||
raw = sum([x << (8 * i) for i, x in enumerate(padded)])
|
||||
|
||||
error = False
|
||||
length, asm = disasm(raw)
|
||||
error |= (length != len(lst))
|
||||
|
||||
actual = f'{str(binascii.hexlify(bytes(lst)).decode()):<20} {asm}\n'
|
||||
new_cases.append(actual)
|
||||
error |= (actual[:-1] != case)
|
||||
|
||||
if error:
|
||||
print(case)
|
||||
print(actual)
|
||||
errors |= error
|
||||
|
||||
# If there were errors, optionally update expectations.
|
||||
if len(sys.argv) >= 3:
|
||||
assert(sys.argv[2] in ['-u', '--update'])
|
||||
open(sys.argv[1], 'w').write(''.join(new_cases))
|
||||
|
||||
if errors:
|
||||
print('Fail.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('Pass.')
|
||||
Whitespace-only changes.
@@ -0,0 +1,555 @@
|
||||
00c086fdffff jmp_any -634
|
||||
02000000040001e4 csel r0l, 0.0, r0l, 0, r0l, fgtn
|
||||
0200400000421024 csel r0l, r0l, 0.0, r1l, r0h, flt
|
||||
0200404004404064 csel r0l, r0l, r2l, r0l, r2l, fltn
|
||||
02020010040011e4 csel r0h, 0.0, r0h, 0, r0h, fgtn
|
||||
02020010044300b0 csel r0h, 0.0, r0h, r1h, 0, fgte
|
||||
02040010044200a4 csel r1l, 0.0, r0h, r1l, r0l, fgte
|
||||
0204410444432004 csel r1l, r0h.abs, r0l.abs, r1h, r1l, feq
|
||||
02084654044430a4 csel r2l, r3l.abs, r2h, r2l, r1h, fgte
|
||||
020a3020044250a4 csel r2h, 1.0, r1l, r1l, r2h, fgte
|
||||
020c0010044020a4 csel r3l, 0.0, r0h, r0l, r1l, fgte
|
||||
020e3020040071a4 csel r3h, 1.0, r1l, 0, r3h, fgte
|
||||
021900c0249001b0 csel r6, 0.0, r6, u8, 0, fgte
|
||||
02194ce2244ce064 csel r6, r6, r7, r6, r7, fltn
|
||||
02214c02004ea024 csel r8, r6, 0.0, r7, r5, flt
|
||||
0501000d10c0f200 load r0_r1_r2_r3, du8, 0, i32, xyzw, a
|
||||
0501000d80c01200 load r0, du64, 0, i32, x, a
|
||||
0501480d60c07200 load r0_r1_r2, du52, 4, i32, xyz, a
|
||||
0501a80d69c03200 load r0_r1, du52, 154, i32, xy, a
|
||||
0509480e00c01200 load r1, du4, r2, i32, x, a
|
||||
0511440500c03200 load r2_r3, dr2, 4, i32, xy, a
|
||||
05116e0621c01200 load r2, dr23, r11, i32, x, a
|
||||
0521004500c01200 load r4, dr0, 0, i32, x, b
|
||||
0521040500c01200 load r4, dr2, 0, i32, x, a
|
||||
0521100500c01200 load r4, dr0, 1, i32, x, a
|
||||
0521340510c01200 load r4, dr10, 3, i32, x, a
|
||||
05310c0500c03200 load r6_r7, dr6, 0, i32, xy, a
|
||||
0531200510c01200 load r6, dr8, 2, i32, x, a
|
||||
0531400510c01200 load r6, dr8, 4, i32, x, a
|
||||
0531cc0e60c03200 load r6_r7, du54, r6, i32, xy, a
|
||||
0541000510c03200 load r8_r9, dr8, 0, i32, xy, a
|
||||
0541000e00c07200 load r8_r9_r10, du0, r0, i32, xyz, a
|
||||
05410c0500c03200 load r8_r9, dr6, 0, i32, xy, a
|
||||
05410c0500c0f200 load r8_r9_r10_r11, dr6, 0, i32, xyzw, a
|
||||
05410c4500c03200 load r8_r9, dr6, 0, i32, xy, b
|
||||
05412c0500c01200 load r8, dr6, 2, i32, x, a
|
||||
0541f00f0fc8f20f load r8_r9_r10_r11, du0, 4095, lsl 2, i32, xyzw, a
|
||||
0551300510c01200 load r10, dr8, 3, i32, x, a
|
||||
05610c0e60c0f200 load r12_r13_r14_r15, du54, r0, i32, xyzw, a
|
||||
0561e44610c01200 load r12, dr10, r7, i32, x, b
|
||||
09010004f0fc0003 st_tile r0_r1_r2_r3, 0, 255, 0, u8norm, xyzw
|
||||
0901000e70fc0003 st_tile r0_r1_r2, 0, 255, 0, rg11b10f_rtz, xyz
|
||||
0901000f70fc0003 st_tile r0_r1_r2, 0, 255, 0, rgb9e5_rtz, xyz
|
||||
09100004f0fc0003 st_tile r4l_r4h_r5l_r5h, 0, 255, 0, u8norm, xyzw
|
||||
09111204f8048000 st_tile r4_r5_r6_r7, r9l_r9h, 1, 0, explicit_coords, u8norm, xyzw
|
||||
0a0040c0 log2 r0l, r0l
|
||||
0a0040d0 exp2 r0l, r0l
|
||||
0a064380 rcp r1h, r1h
|
||||
0a084580 rcp r2l, r2h
|
||||
0a0a4280 rcp r2h, r1l
|
||||
0a0a4590 rsqrt r2h, r2h
|
||||
0a0d4692 rsqrt r3, r3
|
||||
0a0e4790 rsqrt r3h, r3h
|
||||
0e0100c065002000 iadd r0, 0, r14.sx, lsl 4
|
||||
0e01401600000000 iadd r0, r0.sx, 1
|
||||
0e01402664000000 iadd r0, r0.sx, r1.sx
|
||||
0e01408664000000 iadd r0, r0.sx, r4.sx
|
||||
0e014aa6e4000000 iadd r0, r5.sx, r5.sx, lsl 1
|
||||
0e03408334000000 iadd dr0, dr0, dr4
|
||||
0e05420658020000 iadd r1, r1.sx, u64.sx
|
||||
0e05428658000000 iadd r1, r1.sx, u4.sx
|
||||
0e05428664000000 iadd r1, r1.sx, r4.sx
|
||||
0e09424658000000 iadd r2, r1.sx, u2.sx
|
||||
0e09442658020000 iadd r2, r2.sx, u65.sx
|
||||
0e0944a658000000 iadd r2, r2.sx, u5.sx
|
||||
0e0b44c334000000 iadd dr2, dr2, dr6
|
||||
0e0d408664000000 iadd r3, r0.sx, r4.sx
|
||||
0e0d446658000000 iadd r3, r2.sx, u3.sx
|
||||
0e0d464658020000 iadd r3, r3.sx, u66.sx
|
||||
0e14192500000000 iadd r5l, u12h.sx, 2
|
||||
0e14193500000000 iadd r5l, u12h.sx, 3
|
||||
0e194c1600000000 iadd r6, r6.sx, 1
|
||||
0e194ec664000000 iadd r6, r7.sx, r6.sx
|
||||
0e1b4c4335000000 iadd dr6, dr6, dr10
|
||||
0e1b4cc300000000 iadd dr6, dr6, 12
|
||||
0e1b504335000000 iadd dr6, dr8, dr10
|
||||
0e1b50c334000000 iadd dr6, dr8, dr6
|
||||
0e1d01e064000000 iadd r7, 1, r7.sx
|
||||
0e201a1500000000 iadd r8l, u13l.sx, 1
|
||||
0e23448300000000 iadd dr8, dr2, 8
|
||||
0e23504300000000 iadd dr8, dr8, 4
|
||||
0e23504335000000 iadd dr8, dr8, dr10
|
||||
0e23508300000000 iadd dr8, dr8, 8
|
||||
0e23540335000000 iadd dr8, dr10, dr8
|
||||
0e25502665000000 iadd r9, r8.sx, r9.sx
|
||||
0e2900c064002000 iadd r10, 0, r6.sx, lsl 4
|
||||
0e29540665002000 iadd r10, r10.sx, r8.sx, lsl 4
|
||||
0e2b444335000000 iadd dr10, dr2, dr10
|
||||
0e2b448300000000 iadd dr10, dr2, 8
|
||||
0e2b508301000000 iadd dr10, dr8, 24
|
||||
0e314c1600000000 iadd r12, r6.sx, 1
|
||||
0e31581600000000 iadd r12, r12.sx, 1
|
||||
0e690160a9000000 iadd r26, 1, $r11, lsl 1
|
||||
10c060ffffff call -160
|
||||
10c08affffff call -118
|
||||
11000880 st_var r0, r4l
|
||||
11008280 st_var r0, 2
|
||||
11040680 st_var r1, r3l
|
||||
11080880 st_var r2, r4l
|
||||
110c0880 st_var r3, r4l
|
||||
11100a80 st_var r4, r5l
|
||||
11140c80 st_var r5, r6l
|
||||
11141080 st_var r5, r8l
|
||||
11208080 st_var r8, 0
|
||||
11248180 st_var r9, 1
|
||||
112c8380 st_var r11, 3
|
||||
11308480 st_var r12, 4
|
||||
11348580 st_var r13, 5
|
||||
11388680 st_var r14, 6
|
||||
113c8780 st_var r15, 7
|
||||
12004c5200404010 csel r0l, r6, 5, r0l, 4, ieq
|
||||
1201401200401050 csel r0, r0, 1, r0, 1, ugt
|
||||
1202506004506044 csel r0h, r8l, r3l, r8l, r3l, ugt
|
||||
12114802004c0010 csel r4, r4, 0, r6, 0, ieq
|
||||
12194802004c0010 csel r6, r4, 0, r6, 0, ieq
|
||||
12194c12004c1050 csel r6, r6, 1, r6, 1, ugt
|
||||
12194e12004e1050 csel r6, r7, 1, r7, 1, ugt
|
||||
1235548225548045 csel r13, r10, r12, r10, r12, ugt
|
||||
12824262180101300002 csel r0h, r1, u67, 1, 0, ult
|
||||
12824482184100300002 csel r0h, r2, u68, r0h, 0, ult
|
||||
1402 ret r1
|
||||
1500048d20501400 atomic _, r10, du18, 0, add, coherent1, a
|
||||
1500248c10500600 atomic _, r3, du10, r1.sx, add, coherent1, a
|
||||
1500248c90500600 atomic _, r3, du74, r1.sx, add, coherent1, a
|
||||
1502248c10500600 atomic _, r3, du10, r1.sx, and, coherent1, a
|
||||
1508a48c10d00800 atomic r1, r4, du10, r5.sx, add, coherent1, a
|
||||
150aa48c10d00800 atomic r1, r4, du10, r5.sx, and, coherent1, a
|
||||
1520008d20d00200 atomic r4, r1, du16, 0, add, coherent1, a
|
||||
1520048d20d00200 atomic r4, r1, du18, 0, add, coherent1, a
|
||||
15302c8d20d00c00 atomic r6, r6, du22, 2, add, coherent1, a
|
||||
160081f0 hmul r0l, $r0h, 0.234375
|
||||
168040101100 hmul r0l, r0l, u8h
|
||||
168040700400 hmul r0l, r0l, r3h
|
||||
168241700400 hmul r0h, r0h, r3h
|
||||
168442400400 hmul r1l, r1l, r2l
|
||||
168442422400 hmul r1l, r1l.abs, r2l.abs
|
||||
168442700400 hmul r1l, r1l, r3h
|
||||
168640300400 hmul r1h, r0l, r1h
|
||||
168643500400 hmul r1h, r1h, r2h
|
||||
168842300400 hmul r2l, r1l, r1h
|
||||
168844500400 hmul r2l, r2l, r2h
|
||||
168a44400400 hmul r2h, r2l, r2l
|
||||
168a46400400 hmul r2h, r3l, r2l
|
||||
168a48500400 hmul r2h, r4l, r2h
|
||||
168a4aa00400 hmul r2h, r5l, r5l
|
||||
168c46500400 hmul r3l, r3l, r2h
|
||||
168e42200400 hmul r3h, r1l, r1l
|
||||
168e45500400 hmul r3h, r2h, r2h
|
||||
168e4a500400 hmul r3h, r5l, r2h
|
||||
169044700400 hmul r4l, r2l, r3h
|
||||
169249500400 hmul r4h, r4h, r2h
|
||||
16944a000001 hmul r5l, r5l, 2.0
|
||||
19918000548000000800 latomic r4, r4, 0, add
|
||||
19918002548000000800 latomic r4, r4, 0, xchg
|
||||
19918004548000000800 latomic r4, r4, 0, umin
|
||||
19918006548000000800 latomic r4, r4, 0, umax
|
||||
19918008548000000800 latomic r4, r4, 0, and
|
||||
19918009548000000800 latomic r4, r4, 0, or
|
||||
1991800a548000000800 latomic r4, r4, 0, xor
|
||||
19998003548000000c00 latomic r6, r6_r7, 0, cmpxchg
|
||||
1a0182f2 fmul r0, $r1, 0.234375
|
||||
1a9156222500 fmul r4, r11, r9
|
||||
1aa556222500 fmul r9, r11, r9
|
||||
1aad52222500 fmul r11, r9, r9
|
||||
1e0188a558000000 imul r0, u4.sx, u5.sx
|
||||
1e0542a658440602 imadd r1, r1.sx, u69.sx, r2.sx
|
||||
1e09480659440602 imadd r2, r4.sx, u72.sx, r2.sx
|
||||
1e0d422664460600 imadd r3, r1.sx, r1.sx, r3.sx
|
||||
1e0d444664460600 imadd r3, r2.sx, r2.sx, r3.sx
|
||||
1e0d466664000000 imul r3, r3.sx, r3.sx
|
||||
1e1b428201000001 imul dr6, r1, 88
|
||||
1e23428201000001 imul dr8, r1, 88
|
||||
20000600 jmp_if_skipping_doorbell 6
|
||||
20c01c010000 jmp_none 284
|
||||
20c060010000 jmp_none 352
|
||||
20c09e000000 jmp_none 158
|
||||
20c0dc000000 jmp_none 220
|
||||
20c0fa000000 jmp_none 250
|
||||
2199014000000000 iter r6, cf1
|
||||
219d004000000000 iter r7, cf0
|
||||
2602c1f0 hadd r0h, ^r0h, 0.234375
|
||||
268440000300 hadd r1l, r0l, 1.0
|
||||
269642100400 hadd r5h, r1l, r0h
|
||||
2820 doorbell 32
|
||||
2830 doorbell 48
|
||||
2831 doorbell 49
|
||||
2981800214000000 lstore r0, 0, i32, x
|
||||
2989800234000000 lstore r2_r3, 0, i32, xy
|
||||
298d040214000000 lstore r3, r2l, 0, i32, x
|
||||
29914b02f4000000 lstore r4_r5_r6_r7, u5h, 0, i32, xyzw
|
||||
29914c02f4000000 lstore r4_r5_r6_r7, u6l, 0, i32, xyzw
|
||||
2991800274000000 lstore r4_r5_r6, 0, i32, xyz
|
||||
29918002f4000000 lstore r4_r5_r6_r7, 0, i32, xyzw
|
||||
2a05c2f2 fadd r1, ^r1, 0.234375
|
||||
2a81546a2500 fadd r0, r10.neg, r11
|
||||
2a8441040002 fadd r1l, r0h.abs, -0.0
|
||||
2a8441080002 fadd r1l, r0h.neg, -0.0
|
||||
2a9d4a0a0002 fadd r7, r5.neg, -0.0
|
||||
2e01001000400200 shl r0, 1, r0
|
||||
2e014466241e0000 extr r0, r2, r3, 30, 0
|
||||
2e0158a625180000 extr r0, r12, r13, 24, 0
|
||||
2e05001000420200 shl r1, 1, r1
|
||||
2e0500c024080000 shl r1, r6, 8
|
||||
2e055c06241c0000 extr r1, r14, r0, 28, 0
|
||||
2e11004024070000 shl r4, r2, 7
|
||||
2e194ce6241c0000 extr r6, r6, r7, 28, 0
|
||||
2e81000024420200 shr r0, r0, r1
|
||||
2e81008024020000 shr r0, r4, 2
|
||||
2e850040e41b0000 bfeil r1, 0, r2, 27, 3
|
||||
2e8500e025040400 bfeil r1, 0, r15, 4, 4
|
||||
2e91008064090000 bfeil r4, 0, r4, 9, 1
|
||||
2e99008024020000 shr r6, r4, 2
|
||||
2e9900c024500200 shr r6, r6, r8
|
||||
2e9d00e024500200 shr r7, r7, r8
|
||||
2e9d00e0a40a0c00 bfeil r7, 0, r7, 10, 14
|
||||
2ea100e024180400 bfeil r8, 0, r7, 24, 4
|
||||
2ead0020251c0000 shr r11, r9, 28
|
||||
2ead00c0241c0000 shr r11, r6, 28
|
||||
3111081400622f01 smpf r4_r5_r6_r7, ts0, ss1, r4, u10l, 2d, xyzw, lod_min, a
|
||||
31818000ca6321481b201e00 smpf r0, du54, r5, r4l, r0, u64l, r15, shadow, 2d_array, x, lod_min, a
|
||||
31910400c2620f411b008001 smpf r4_r5_r6_r7, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_g, a
|
||||
31910400c2620f411b008002 smpf r4_r5_r6_r7, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_b, a
|
||||
31910400c2620f411b008003 smpf r4_r5_r6_r7, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_a, a
|
||||
31910406c26293411b200000 smpf r4_r5, du54, r1, r0h, r2, u67l_u67h, 2d, xy, auto_lod_bias_min, a
|
||||
31a10400c2620f411b008000 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_r, a
|
||||
31a10400c2620f411b008001 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_g, a
|
||||
31a10400c2620f411b008002 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_b, a
|
||||
31a10400c2620f411b008003 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, 2d, xyzw, auto_lod, gather_a, a
|
||||
31a10400c2620f415b008000 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_r, a
|
||||
31a10400c2620f415b008001 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_g, a
|
||||
31a10400c2620f415b008002 smpf r8_r9_r10_r11, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_b, a
|
||||
31a10402c862df415b000000 smpf r8_r9_r10_r11, du54, r4, r0h, r2, r1l_r1h, sparse, 2d, xyzw, auto_lod_bias_min, a
|
||||
31a10406c26291411b200000 smpf r8, du54, r1, r0h, r2, u67l_u67h, 2d, x, auto_lod_bias_min, a
|
||||
31a10804c665df411b000208 smpf r8_r9_r10_r11, du54, r3, r0h, r4, r2l_r2h, r1l, 3d, xyzw, auto_lod_bias_min, a
|
||||
31a10808c2679f411b200000 smpf r8_r9_r10_r11, du54, r1, r0h, r4, u68l_u68h, cube_array, xyzw, auto_lod_bias_min, a
|
||||
31b10806c2629f411b200000 smpf r12_r13_r14_r15, du54, r1, r0h, r4, u67l_u67h, 2d, xyzw, auto_lod_bias_min, a
|
||||
31c10400c2620f415b008000 smpf r16_r17_r18_r19, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_r, a
|
||||
31c10400c2620f415b008001 smpf r16_r17_r18_r19, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_g, a
|
||||
31c10400c2620f415b008002 smpf r16_r17_r18_r19, du54, r1, r0h, r2, 0, sparse, 2d, xyzw, auto_lod, gather_b, a
|
||||
31c11800c2622f415b200000 smpf r16_r17_r18_r19, du54, r1, r0h, r12, u64l, sparse, 2d, xyzw, lod_min, a
|
||||
3219010000800100 ballot r6
|
||||
3219420000000100 ballot r6, r1l, 0, ieq
|
||||
3229010000800100 ballot r10
|
||||
325962a226100100 ballot r54, r17, r21, ieq
|
||||
3509000501c03000 stack_load r1l_r1h, 16, i16, xy, a
|
||||
3522000500c01200 stack_load r4, 0, i32, x, a
|
||||
3532400500c01200 stack_load r6, 4, i32, x, a
|
||||
3532800500c01200 stack_load r6, 8, i32, x, a
|
||||
3532800502c01200 stack_load r6, 40, i32, x, a
|
||||
353ac00503c01200 stack_load r7, 60, i32, x, a
|
||||
35b9c00500c03000 stack_load r23l_r23h, 12, i16, xy, a
|
||||
35c1800500c03000 stack_load r24l_r24h, 8, i16, xy, a
|
||||
35c9400500c03000 stack_load r25l_r25h, 4, i16, xy, a
|
||||
35d1000500c03000 stack_load r26l_r26h, 0, i16, xy, a
|
||||
360040440441 hfma r0l, r0l.neg, r2l, r0h
|
||||
360241300442 hfma r0h, r0h, r1h, r1l
|
||||
360245340442 hfma r0h, r2h.neg, r1h, r1l
|
||||
36084a540443 hfma r2l, r5l.neg, r2h, r1h
|
||||
36084a740444 hfma r2l, r5l.neg, r3h, r2l
|
||||
360a43300445 hfma r2h, r1h, r1h, r2h
|
||||
360a45600447 hfma r2h, r2h, r3l, r3h
|
||||
360a46600445 hfma r2h, r3l, r3l, r2h
|
||||
360a48800445 hfma r2h, r4l, r4l, r2h
|
||||
360a49900445 hfma r2h, r4h, r4h, r2h
|
||||
360c41340430 hfma r3l, r0h.neg, r1h, 1.0
|
||||
360c46700430 hfma r3l, r3l, r3h, 1.0
|
||||
360c46700448 hfma r3l, r3l, r3h, r4l
|
||||
360c48700446 hfma r3l, r4l, r3h, r3l
|
||||
360e40000447 hfma r3h, r0l, r0l, r3h
|
||||
360e41100447 hfma r3h, r0h, r0h, r3h
|
||||
361410110449 hfma r5l, u8l, r0h, r4h
|
||||
36144530044a hfma r5l, r2h, r1h, r5l
|
||||
361446940448 hfma r5l, r3l.neg, r4h, r4l
|
||||
3682414404420400 hfma r0h, r0h.neg, r2l, r1l.neg
|
||||
3684469004480400 hfma r1l, r3l, r4h, r4l.neg
|
||||
36884a9404460400 hfma r2l, r5l.neg, r4h, r3l.neg
|
||||
3692100104120100 hfma r4h, u8l, r0l, u9l
|
||||
3800 wait a
|
||||
3801 wait b
|
||||
3a81130124540200 ffma r0, u9h, r0, r10
|
||||
3a914602198c0100 ffma r4, r3, u8, u6
|
||||
3a954622198e0100 ffma r5, r3, u9, u7
|
||||
3a954c22194a0200 ffma r5, r6, u9, r5
|
||||
3e0944500400 intl r2, r2l, r2h
|
||||
3e29540e0000 ffs r10, r10
|
||||
3e800a042400 u32_to_f r0l, r0, rte
|
||||
3e800b042400 s32_to_f r0l, r0, rte
|
||||
3e8108000400 f_to_u32 r0, r0l, rtz
|
||||
3e8108802500 f_to_u32 r0, r12, rtz
|
||||
3e8109000400 f_to_s32 r0, r0l, rtz
|
||||
3e8109802500 f_to_s32 r0, r12, rtz
|
||||
3e810a042400 u32_to_f r0, r0, rte
|
||||
3e810b042400 s32_to_f r0, r0, rte
|
||||
3e8508202400 f_to_u32 r1, r1, rtz
|
||||
3e8509202400 f_to_s32 r1, r1, rtz
|
||||
3e850a242400 u32_to_f r1, r1, rte
|
||||
3e850b242400 s32_to_f r1, r1, rte
|
||||
3e8908402400 f_to_u32 r2, r2, rtz
|
||||
3e8909402400 f_to_s32 r2, r2, rtz
|
||||
3e8d0a242400 u32_to_f r3, r1, rte
|
||||
3e8d0a442400 u32_to_f r3, r2, rte
|
||||
417f0423 zs_emit 255, r2
|
||||
420000000000 update_exec
|
||||
42096026e600 if r16.abs, r17.abs.neg, 1, fneq
|
||||
420a00000000 else 0.0, 0.0, 1, feq
|
||||
421000000000 if 0.0, 0.0, 2, feq
|
||||
422844801100 if r2l, u12l, 1, flt
|
||||
4501040d80c01200 store r0, du66, 0, i32, x, a
|
||||
4501400e00c43200 store r0_r1, du0, r2, lsl 1, i32, xy, a
|
||||
4501404d80c03200 store r0_r1, du64, 4, i32, xy, b
|
||||
4509080500c01200 store r1, dr4, 0, i32, x, a
|
||||
4521000d80c0f200 store r4_r5_r6_r7, du64, 0, i32, xyzw, a
|
||||
4531400510c01200 store r6, dr8, 4, i32, x, a
|
||||
45410c0500c01200 store r8, dr6, 0, i32, x, a
|
||||
4541800e00c8f200 store r8_r9_r10_r11, du0, r4, lsl 2, i32, xyzw, a
|
||||
45492c0500c01200 store r9, dr6, 2, i32, x, a
|
||||
4561000510c01200 store r12, dr8, 0, i32, x, a
|
||||
4561004510c01200 store r12, dr8, 0, i32, x, b
|
||||
4561200510c03200 store r12_r13, dr8, 2, i32, xy, a
|
||||
4561e44610c01200 store r12, dr10, r7, i32, x, b
|
||||
4800c200 pixwait 512, 3
|
||||
48040000 pixwait 4, 0
|
||||
48080000 pixwait 8, 0
|
||||
480c0000 pixwait 12, 0
|
||||
49000004f8fc0003 ld_tile r0l_r0h_r1l_r1h, 0, 255, 0, u8norm, xyzw
|
||||
520841000000 if r0h, 0, 1, ieq
|
||||
52084c020000 if r6, 0, 1, ieq
|
||||
52084c120000 if r6, 1, 1, ieq
|
||||
52084c420000 if r6, 4, 1, ieq
|
||||
52084cf20000 if r6, 15, 1, ieq
|
||||
520850020000 if r8, 0, 1, ieq
|
||||
520941000000 if r0h, 0, 1, ine
|
||||
520e00000000 pop_exec 1
|
||||
52284c822400 if r6, r4, 1, ult
|
||||
5501248c10500600 atomic _, r3, du10, r1.sx, imin, coherent1, a
|
||||
5502248c10500600 atomic _, r3, du10, r1.sx, or, coherent1, a
|
||||
5509a48c10d00800 atomic r1, r4, du10, r5.sx, imin, coherent1, a
|
||||
550aa48c10d00800 atomic r1, r4, du10, r5.sx, or, coherent1, a
|
||||
6180010000000000 iter r0l_r0h_r1l_r1h, cf1, cf0
|
||||
6181018000000100 iter r0_r1, cf1, cf0, 0, sample
|
||||
618101c000000200 iter r0_r1_r2, cf1, cf0, centroid
|
||||
618101c000401000 iter r0_r1_r2, cf1, cf0, elide, kill
|
||||
619005c000000000 iter r4l_r4h_r5l, cf5, cf0
|
||||
6191010006000300 iter r4_r5_r6_r7, cf1, cf0, r3l, sample
|
||||
6191018002000300 iter r4_r5, cf1, cf0, r1l, sample
|
||||
6191018006000300 iter r4_r5, cf1, cf0, r3l, sample
|
||||
619101c002000300 iter r4_r5_r6, cf1, cf0, r1l, sample
|
||||
62000000 ldimm r0l, 0
|
||||
620100000040 ldimm r0, 1073741824
|
||||
62020000 ldimm r0h, 0
|
||||
6206003c ldimm r1h, 15360
|
||||
6206483e ldimm r1h, 15944
|
||||
62080034 ldimm r2l, 13312
|
||||
62090000adde ldimm r2, 3735879680
|
||||
620a0074 ldimm r2h, 29696
|
||||
620c0080 ldimm r3l, 32768
|
||||
620cdf2a ldimm r3l, 10975
|
||||
620cff7f ldimm r3l, 32767
|
||||
620dffffff07 ldimm r3, 134217727
|
||||
620e48be ldimm r3h, 48712
|
||||
62100000 ldimm r4l, 0
|
||||
62103432 ldimm r4l, 12852
|
||||
621036a2 ldimm r4l, 41526
|
||||
621053b5 ldimm r4l, 46419
|
||||
621083af ldimm r4l, 44931
|
||||
621100000000 ldimm r4, 0
|
||||
6211ffffffff ldimm r4, 4294967295
|
||||
62120000 ldimm r4h, 0
|
||||
621500000000 ldimm r5, 0
|
||||
6216003c ldimm r5h, 15360
|
||||
6225ff3f0000 ldimm r9, 16383
|
||||
6225ffffffff ldimm r9, 4294967295
|
||||
6229f0ffffff ldimm r10, 4294967280
|
||||
6800 barrier
|
||||
6985800214000000 lload r1, 0, i32, x
|
||||
6989000214000000 lload r2, r0l, 0, i32, x
|
||||
6991800234000000 lload r4_r5, 0, i32, xy
|
||||
6991800274000000 lload r4_r5_r6, 0, i32, xyz
|
||||
69918002f4000000 lload r4_r5_r6_r7, 0, i32, xyzw
|
||||
6aad50020002 fadd r11, r8, -0.0, sat
|
||||
6f0241104000 shuffle r0h, r0h, 1, quad, xor
|
||||
6f0241204000 shuffle r0h, r0h, 2, quad, xor
|
||||
6f0241304000 shuffle r0h, r0h, 3, quad, xor
|
||||
6f104c100400 shuffle r4l, r6l, r0h, quad
|
||||
6f124c100000 shuffle r4h, r6l, 1, quad
|
||||
6f124c100400 shuffle r4h, r6l, r0h, quad
|
||||
6f124c200000 shuffle r4h, r6l, 2, quad
|
||||
6f194c160400 shuffle r6, r6, r0h
|
||||
6f1a4c000000 shuffle r6h, r6l, 0, quad
|
||||
6f1a4c300000 shuffle r6h, r6l, 3, quad
|
||||
6f8444600000 simd r1l, r2l, prefix, and
|
||||
6f8444604000 simd r1l, r2l, prefix, or
|
||||
6f8444608000 simd r1l, r2l, prefix, xor
|
||||
6f8444608080 simd r1l, r2l, prefix, smin
|
||||
6f8846604000 simd r2l, r3l, prefix, or
|
||||
6f8846608000 simd r2l, r3l, prefix, xor
|
||||
6f8a44600080 simd r2h, r2l, prefix, iadd
|
||||
6f8a44604000 simd r2h, r2l, prefix, or
|
||||
6f8a44608000 simd r2h, r2l, prefix, xor
|
||||
6f8a4460c080 simd r2h, r2l, prefix, smax
|
||||
6f8e46604000 simd r3h, r3l, prefix, or
|
||||
6f8e46608000 simd r3h, r3l, prefix, xor
|
||||
6f9a4c600080 simd r6h, r6l, prefix, iadd
|
||||
6f9d4c620080 simd r7, r6, prefix, iadd
|
||||
7101040000620f00 smpi r0_r1_r2_r3, ts0, ss0, r2, 0, 2d, xyzw, auto_lod, a
|
||||
72001004 mov r0l, thread_in_grid_x
|
||||
72021104 mov r0h, thread_in_grid_y
|
||||
72050000 mov r1, threadgroup_in_grid_x
|
||||
72051004 mov r1, thread_in_grid_x
|
||||
72051400 mov r1, core_index
|
||||
72090100 mov r2, threadgroup_in_grid_y
|
||||
72091004 mov r2, thread_in_grid_x
|
||||
72091104 mov r2, thread_in_grid_y
|
||||
72091208 mov r2, helper_arg_l
|
||||
720d0200 mov r3, threadgroup_in_grid_z
|
||||
720d1308 mov r3, helper_arg_h
|
||||
720d3000 mov r3, thread_in_threadgroup_x
|
||||
72191008 mov r6, helper_op
|
||||
721d1208 mov r7, helper_arg_l
|
||||
72213100 mov r8, thread_in_threadgroup_y
|
||||
72283a00 mov r10l, active_thread_index_in_simdgroup
|
||||
7540010500c01000 map r8, 0, 0
|
||||
7540300500c01000 unmap r8, 0, 3
|
||||
7548110500c01000 map r9, 0, 1
|
||||
7550210500c01000 map r10, 0, 2
|
||||
7558310500c01000 map r11, 0, 3
|
||||
7560000500c01000 unmap r12, 0, 0
|
||||
7560100500c01000 unmap r12, 0, 1
|
||||
7560200500c01000 unmap r12, 0, 2
|
||||
7e0140088000 mov r0, r0l
|
||||
7e014022a500 and r0, r0, r9
|
||||
7e01402a2400 andn2 r0, r0, r1
|
||||
7e0140728000 and r0, r0, 7
|
||||
7e0140f28100 and r0, r0, 31
|
||||
7e015ef28000 and r0, r15, 15
|
||||
7e0190098000 mov r0, u8
|
||||
7e02540a8000 mov r0h, r10
|
||||
7e0414098000 mov r1l, u10l
|
||||
7e0442608400 and r1l, r1l, r3l
|
||||
7e044528c400 or r1l, r2h, r1l
|
||||
7e0500088000 mov r1, 0
|
||||
7e05400a8000 mov r1, r0
|
||||
7e0542064000 not r1, r1
|
||||
7e0542e29802 and r1, r1, u71
|
||||
7e0542f28100 and r1, r1, 31
|
||||
7e0542f28300 and r1, r1, 63
|
||||
7e0543088000 mov r1, r1h
|
||||
7e0644088000 mov r1h, r2l
|
||||
7e0646088000 mov r1h, r3l
|
||||
7e0940088000 mov r2, r0l
|
||||
7e0942c29802 and r2, r1, u70
|
||||
7e094462a400 and r2, r2, r3
|
||||
7e09ac098004 mov r2, u54
|
||||
7e0a45608400 and r2h, r2h, r3l
|
||||
7e0d41088000 mov r3, r0h
|
||||
7e0dae098004 mov r3, u55
|
||||
7e1049088000 mov r4l, r4h
|
||||
7e11400a8000 mov r4, r0
|
||||
7e1142088000 mov r4, r1l
|
||||
7e11442ae400 or r4, r2, r1
|
||||
7e15400a8000 mov r5, r0
|
||||
7e19400a8000 mov r6, r0
|
||||
7e19480a8000 mov r6, r4
|
||||
7e194c22a500 and r6, r6, r9
|
||||
7e1d400a8000 mov r7, r0
|
||||
7e1d4a0a8000 mov r7, r5
|
||||
7e2150f28100 and r8, r8, 31
|
||||
7e2446088000 mov r9l, r3l
|
||||
7e2650088000 mov r9h, r8l
|
||||
7e29480a8000 mov r10, r4
|
||||
7e2d440a8000 mov r11, r2
|
||||
7e2d480a8000 mov r11, r4
|
||||
7e2d4a0a8000 mov r11, r5
|
||||
7e2d740a8000 mov r11, r26
|
||||
7e31ae0a8000 mov r12, $r23
|
||||
7e35700a8000 mov r13, r24
|
||||
7e5d980a8000 mov r23, $r12
|
||||
7e615a0a8000 mov r24, r13
|
||||
7e65960a8000 mov r25, $r11
|
||||
85044c0e00c01000 load r0h, du6, r2, i16, x, a
|
||||
8800 stop
|
||||
8e090060a9000000 iadd $r2, 0, $r11, lsl 1
|
||||
8e09562227000000 iadd $r2, r11, r25
|
||||
91008880 st_var r0, 8, last
|
||||
9500248c10500600 atomic _, r3, du10, r1.sx, xchg, coherent1, a
|
||||
9502248c10500600 atomic _, r3, du10, r1.sx, xor, coherent1, a
|
||||
9508a48c10d00800 atomic r1, r4, du10, r5.sx, xchg, coherent1, a
|
||||
950aa48c10d00800 atomic r1, r4, du10, r5.sx, xor, coherent1, a
|
||||
a181004000000000 ldcf r0_r1_r2, cf0
|
||||
a191014000000000 ldcf r4_r5_r6, cf1
|
||||
a191024000000000 ldcf r4_r5_r6, cf2
|
||||
a191034000000000 ldcf r4_r5_r6, cf3
|
||||
ad01100800000010 load ss1, du4, r1, a
|
||||
ad01116001000024 load ss1, du8, 88, b
|
||||
b1800080004a00000a00 imgwblk r0l, ts0, r0, 0, 2d, u8norm
|
||||
b1848480024a00000a00 imgwblk r1l, ts2, r2, 0, explicit_coords, 2d, u8norm
|
||||
b1880680004880000a00 imgwblk r2l, ts0, r3, 0, 2d_ms_array, u8norm
|
||||
b50081052000 stack_adjust 8
|
||||
b500810523800000 stack_adjust 56
|
||||
b50081052f8000ff stack_adjust -8
|
||||
b509000501c03000 stack_store r1l_r1h, 16, i16, xy, a
|
||||
b521800500c01000 stack_store r4l, 8, i16, x, a
|
||||
b522000500c01200 stack_store r4, 0, i32, x, a
|
||||
b522400501c01200 stack_store r4, 20, i32, x, a
|
||||
b522404500c01200 stack_store r4, 4, i32, x, b
|
||||
b522c00500c01200 stack_store r4, 12, i32, x, a
|
||||
b53a800501c01200 stack_store r7, 24, i32, x, a
|
||||
b552400501c01200 stack_store r10, 20, i32, x, a
|
||||
b55a000501c01200 stack_store r11, 16, i32, x, a
|
||||
b5b9c00500c03000 stack_store r23l_r23h, 12, i16, xy, a
|
||||
b5c1800500c03000 stack_store r24l_r24h, 8, i16, xy, a
|
||||
b5c9400500c03000 stack_store r25l_r25h, 4, i16, xy, a
|
||||
b5d1000500c03000 stack_store r26l_r26h, 0, i16, xy, a
|
||||
c1000100 sample_mask r0l, r0h
|
||||
c1008000 sample_mask r0l, 0
|
||||
c1020000 sample_mask r0h, r0l
|
||||
c1028000 sample_mask r0h, 0
|
||||
c102bf0c sample_mask r0h, 255
|
||||
c17f8003 sample_mask 255, 0
|
||||
c500003d0880f000 mov du64, dr0
|
||||
c500003d09803000 mov u72, r0
|
||||
c500403d01803000 mov u10, r0
|
||||
c500403d0980f000 mov du74, dr0
|
||||
c500440e00c43000 store r0l_r0h, du2, r2, lsl 1, i16, xy, a
|
||||
c500a03d08803000 mov u69, r0
|
||||
c508c03d08803000 mov u70, r1
|
||||
c508e03d08803000 mov u71, r1
|
||||
c510403d08803000 mov u66, r2
|
||||
c520803d08803000 mov u68, r4
|
||||
c520a03d02807000 mov u21l_u21h_u22l, r4l_r4h_r5l
|
||||
c528903d02803000 mov u20h_u21l, r5
|
||||
c530603d08803000 mov u67, r6
|
||||
ce05864200000000 iadd $r1, $r3, 4, sat
|
||||
d21600000000 pop_exec 2, cache
|
||||
d28884020000 if $r2, 0, 1, cache, seq
|
||||
d500688c10500c00 atomic _, r6_r7, du12, r3.sx, cmpxchg, coherent1, a
|
||||
d501248c10500600 atomic _, r3, du10, r1.sx, imax, coherent1, a
|
||||
d508288c10d01000 atomic r1, r8_r9, du12, r1.sx, cmpxchg, coherent1, a
|
||||
d509a48c10d00800 atomic r1, r4, du10, r5.sx, imax, coherent1, a
|
||||
ed00100001000024 load ts0, du8, 64, a
|
||||
ed01100800000000 load ts1, du0, r1, a
|
||||
ed02100800000008 load ts2, du2, r1, a
|
||||
ed02100800000018 load ts2, du6, r1, a
|
||||
f151a080054a imgw r20_r21_r22_r23, ts5, r16, 0, 2d
|
||||
f1a18c80c24a1b00 imgw r8_r9_r10_r11, du54, r1, r6, 0, 2d
|
||||
f503 unknown_barrier_2
|
||||
f533 unknown_barrier_1
|
||||
f540 flush_memory_to_texture
|
||||
f596 memory_barrier
|
||||
f599 memory_barrier_3
|
||||
f59a memory_barrier_2
|
||||
f5a9 image_barrier_3
|
||||
f5aa image_barrier_1
|
||||
f5ad image_barrier_4
|
||||
f5ae image_barrier_2
|
||||
@@ -0,0 +1,158 @@
|
||||
# Copyright 2025 Valve Corporation
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from isa import isa
|
||||
import math
|
||||
import sys
|
||||
import binascii
|
||||
|
||||
def match_instr(raw):
|
||||
for key in isa.instrs:
|
||||
instr = isa.instrs[key]
|
||||
length = instr.length[instr.length_spec.bits.extract(raw) if instr.length_spec is not None else 0]
|
||||
r = raw & ((1 << (length * 8)) - 1)
|
||||
if (r & instr.exact.mask) == instr.exact.value:
|
||||
return length, instr, r
|
||||
|
||||
return 2, None, raw
|
||||
|
||||
def map_value(v, kind, length):
|
||||
if kind in isa.enums:
|
||||
val = isa.enums[kind].values.get(v)
|
||||
return f'unk{v}' if val is None else val.display
|
||||
elif kind == 'signed':
|
||||
return str(-((1 << length) - v) if (v & (1 << (length - 1))) else v)
|
||||
else:
|
||||
return str(v)
|
||||
|
||||
def map_mod(v, mod):
|
||||
if mod.kind in isa.enums:
|
||||
return map_value(v, mod.kind, 0)
|
||||
else:
|
||||
assert(mod.bits.size() == 1)
|
||||
return mod.display if v != 0 else None
|
||||
|
||||
def decode_float_immediate(n):
|
||||
sign = -1.0 if n & 0x80 else 1.0
|
||||
e = (n & 0x70) >> 4
|
||||
f = n & 0xF
|
||||
if e == 0:
|
||||
return sign * f / 64.0
|
||||
else:
|
||||
return sign * float(0x10 | f) * (2.0 ** (e - 7))
|
||||
|
||||
def operand(desc, length):
|
||||
count = desc.get('count', 1)
|
||||
if count == 0:
|
||||
count = 4
|
||||
if 'mask' in desc:
|
||||
count = desc['mask'].bit_count()
|
||||
if 'zx' in desc:
|
||||
desc['sx'] = desc['zx'] ^ 1
|
||||
|
||||
size = 64 if desc.get('size64', 0) != 0 else 32 if desc.get('size32', 0) != 0 else 16
|
||||
base = desc.get('base')
|
||||
kind = desc.get('kind')
|
||||
cache = desc.get('cache', False)
|
||||
discard = desc.get('discard', False)
|
||||
|
||||
hint = desc.get('hint')
|
||||
if hint is not None:
|
||||
if hint == 3:
|
||||
discard = True
|
||||
elif hint == 2:
|
||||
cache = True
|
||||
else:
|
||||
assert(hint == 1)
|
||||
|
||||
assert(not (cache and discard))
|
||||
hint = '^' if discard else '$' if cache else ''
|
||||
suffixes = ''.join(['.' + x for x in ['abs', 'neg', 'sx'] if desc.get(x, 0) != 0])
|
||||
|
||||
if kind == None:
|
||||
return None
|
||||
elif kind in ['cf', 'ts', 'ss']:
|
||||
assert(count == 1)
|
||||
return f'{kind}{base}' + suffixes
|
||||
elif kind == 'fimm':
|
||||
return str(decode_float_immediate(base)) + suffixes
|
||||
elif kind in ['imm', 'signed']:
|
||||
return map_value(base, kind, length) + suffixes
|
||||
|
||||
def m(offs):
|
||||
assert(size == 16 or size == 32 or size == 64)
|
||||
reg = base + (offs * (size // 16))
|
||||
whole = reg >> 1
|
||||
part = reg & 1
|
||||
prefix = kind[0]
|
||||
|
||||
if size == 16:
|
||||
part_str = 'h' if part != 0 else 'l'
|
||||
return f'{prefix}{whole}{part_str}'
|
||||
else:
|
||||
assert(part == 0)
|
||||
return f'{"d" if size == 64 else ""}{prefix}{whole}'
|
||||
|
||||
return hint + '_'.join([m(x) for x in range(count)]) + suffixes
|
||||
|
||||
def map_op(v, kind, raw, ins, length):
|
||||
for c in isa.encodings[kind].cases:
|
||||
if (v & c.exact.mask) == c.exact.value:
|
||||
desc = {}
|
||||
if c.kind is not None:
|
||||
length = c.kind.bits.size()
|
||||
desc['kind'] = c.kind.name
|
||||
desc['base'] = c.kind.bits.extract(v) << c.kind.shift
|
||||
if c.kind.kind == 'signed':
|
||||
desc['kind'] = 'signed'
|
||||
|
||||
for mod in c.modifiers:
|
||||
desc[mod.name] = mod.bits.extract(v)
|
||||
|
||||
for prop in c.properties:
|
||||
desc[prop] = int(c.properties[prop])
|
||||
|
||||
return operand(desc, length)
|
||||
|
||||
def disasm(raw_in):
|
||||
length, instr, raw = match_instr(raw_in)
|
||||
if instr is None:
|
||||
return length, "<unknown instr>"
|
||||
|
||||
imms = [map_value(imm.bits.extract(raw), imm.kind, imm.bits.size()) for imm in instr.immediates]
|
||||
mods = [map_mod(mod.bits.extract(raw), mod) for mod in instr.modifiers if mod.name != 'length']
|
||||
dests = [map_op(d.bits.extract(raw), d.kind, raw, instr, d.bits.size()) for d in instr.dests]
|
||||
srcs = [map_op(s.bits.extract(raw), s.kind, raw, instr, s.bits.size()) for s in instr.sources]
|
||||
|
||||
dests = ['_' if x is None else str(x) for x in dests]
|
||||
srcs = [str(x) for x in srcs if x is not None]
|
||||
imms = [str(x) for x in imms if x is not None]
|
||||
mods = [str(x) for x in mods if x is not None]
|
||||
end = ', '.join(dests + srcs + imms + mods)
|
||||
if end != "":
|
||||
end = " " + end
|
||||
|
||||
asm = f'{instr.display:<8}{end}'.strip()
|
||||
|
||||
unexpected = raw & ~instr.mask()
|
||||
unexpected = [i for i in range(length * 8) if unexpected & (1 << i)]
|
||||
if len(unexpected) != 0:
|
||||
print(f"Unexpected set bits: {unexpected}")
|
||||
length = -length
|
||||
|
||||
return length, asm
|
||||
|
||||
if __name__ == '__main__':
|
||||
prog = open(sys.argv[1], 'rb').read()
|
||||
print("")
|
||||
i = 0
|
||||
while i < len(prog):
|
||||
lst = prog[i:i + 16]
|
||||
raw = sum([int(x) << (8 * i) for i, x in enumerate(lst)])
|
||||
length, asm = disasm(raw)
|
||||
lst = lst[0:length]
|
||||
print(f'{hex(i)[2:]:>4}: {str(binascii.hexlify(lst).decode()):<20} {asm}')
|
||||
if asm.strip() == 'trap':
|
||||
break
|
||||
i += length
|
||||
print("")
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "asahi/isa/disasm.h"
|
||||
#include "util/macros.h"
|
||||
|
||||
static inline uint8_t
|
||||
parse_nibble(const char c)
|
||||
{
|
||||
return (c >= 'a') ? 10 + (c - 'a') : (c - '0');
|
||||
}
|
||||
|
||||
/* Given a little endian 8 byte hexdump, parse out the 64-bit value */
|
||||
static uint64_t
|
||||
parse_hex(const char *in)
|
||||
{
|
||||
uint64_t v = 0;
|
||||
|
||||
for (unsigned i = 0; i < 8; ++i) {
|
||||
uint8_t byte = (parse_nibble(in[0]) << 4) | parse_nibble(in[1]);
|
||||
v |= ((uint64_t)byte) << (8 * i);
|
||||
|
||||
/* Skip the space after the byte */
|
||||
in += 3;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, const char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Expected case list\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *fp = fopen(argv[1], "r");
|
||||
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Could not open the case list");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char line[128];
|
||||
unsigned nr_fail = 0, nr_pass = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), fp) != NULL) {
|
||||
char *output = NULL;
|
||||
size_t sz = 0;
|
||||
size_t len = strlen(line);
|
||||
|
||||
/* Skip empty lines */
|
||||
if (len <= 1)
|
||||
continue;
|
||||
|
||||
/* Parse the hex */
|
||||
uint8_t code[16];
|
||||
const char *cursor = line;
|
||||
const char *end = line + len;
|
||||
uint32_t i = 0;
|
||||
while ((cursor + 2) <= end && cursor[0] != ' ') {
|
||||
assert(i < ARRAY_SIZE(code));
|
||||
code[i] = (parse_nibble(cursor[0]) << 4) | parse_nibble(cursor[1]);
|
||||
cursor += 2;
|
||||
++i;
|
||||
}
|
||||
|
||||
/* Skip spacing */
|
||||
while (cursor < end && (*cursor) == ' ')
|
||||
++cursor;
|
||||
|
||||
FILE *outputp = open_memstream(&output, &sz);
|
||||
signed ret = agx2_disassemble_instr((void *)code, outputp, 0, false);
|
||||
unsigned instr_len = abs(ret);
|
||||
fclose(outputp);
|
||||
|
||||
/* Rest of the line is the reference assembly */
|
||||
bool fail = strcmp(cursor, output);
|
||||
fail |= (instr_len != i);
|
||||
fail |= (ret < 0);
|
||||
|
||||
if (fail) {
|
||||
/* Extra spaces after Got to align with Expected */
|
||||
printf("Got %sExpected %s\n", output, cursor);
|
||||
|
||||
if (instr_len != i) {
|
||||
printf("Got length %d, expected length %u\n", instr_len, i);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
printf("Got an error.\n");
|
||||
}
|
||||
|
||||
nr_fail++;
|
||||
} else {
|
||||
nr_pass++;
|
||||
}
|
||||
|
||||
free(output);
|
||||
}
|
||||
|
||||
printf("Passed %u/%u tests.\n", nr_pass, nr_pass + nr_fail);
|
||||
fclose(fp);
|
||||
|
||||
return nr_fail ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# ail
|
||||
|
||||
ail is a small library for working with the image (and buffer) layouts
|
||||
encountered with AGX hardware. Its design is inspired by isl. In
|
||||
particular, ail strives to use isl unit suffixes and to represent
|
||||
quantities in a canonical, API-agnostic fashion.
|
||||
|
||||
ail conventions differ slightly from isl. See the isl documentation in
|
||||
mesa/docs/isl for the conventions that inspired ail, in particular
|
||||
mesa/docs/isl/units.rst.
|
||||
|
||||
In ail, we have the following units:
|
||||
|
||||
**Bytes (B)**: 8-bits.
|
||||
|
||||
**Samples (sa)**: An individual sample. The number of bytes per sample depends
|
||||
on the format.
|
||||
|
||||
**Pixels (px)**: The unit everything starts as from the API. A pixel contains a
|
||||
fixed number of samples, given as the layout's `sample_count_sa`. For twiddled
|
||||
layouts, these samples are stored together in emmory.
|
||||
|
||||
**Elements (el)**: For a block-compressed format, a single compression block
|
||||
containing multiple pixels. Otherwise, equal to a single pixel.
|
||||
|
||||
**Tiles (tiles)**: Defined only for tiled/twiddled layouts. A group of elements
|
||||
forming the layout-specific tile.
|
||||
|
||||
We do not support any multisampled block-compressed layouts, so either pixels
|
||||
equals either samples or elements.
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright 2021 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "agx_pack.h"
|
||||
#include "layout.h"
|
||||
|
||||
#define AIL_ISA_FORMAT__ PIPE_FORMAT_NONE
|
||||
|
||||
#define AIL_FMT(pipe, channels_, type_, renderable_) \
|
||||
[PIPE_FORMAT_##pipe] = { \
|
||||
.channels = AGX_CHANNELS_##channels_, \
|
||||
.type = AGX_TEXTURE_TYPE_##type_, \
|
||||
.texturable = true, \
|
||||
.renderable = (enum pipe_format)AIL_ISA_FORMAT_##renderable_, \
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
const struct ail_pixel_format_entry ail_pixel_format[PIPE_FORMAT_COUNT] = {
|
||||
AIL_FMT(R5G6B5_UNORM, R5G6B5, UNORM, F16),
|
||||
AIL_FMT(B5G6R5_UNORM, R5G6B5, UNORM, F16),
|
||||
|
||||
AIL_FMT(R5G5B5A1_UNORM, R5G5B5A1, UNORM, F16),
|
||||
AIL_FMT(B5G5R5A1_UNORM, R5G5B5A1, UNORM, F16),
|
||||
AIL_FMT(R5G5B5X1_UNORM, R5G5B5A1, UNORM, F16),
|
||||
AIL_FMT(B5G5R5X1_UNORM, R5G5B5A1, UNORM, F16),
|
||||
|
||||
AIL_FMT(R4G4B4A4_UNORM, R4G4B4A4, UNORM, F16),
|
||||
AIL_FMT(B4G4R4A4_UNORM, R4G4B4A4, UNORM, F16),
|
||||
AIL_FMT(A4B4G4R4_UNORM, R4G4B4A4, UNORM, F16),
|
||||
AIL_FMT(A4R4G4B4_UNORM, R4G4B4A4, UNORM, F16),
|
||||
|
||||
AIL_FMT(R8_UNORM, R8, UNORM, U8NORM),
|
||||
AIL_FMT(R8G8_UNORM, R8G8, UNORM, U8NORM),
|
||||
AIL_FMT(R8G8B8A8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(A8R8G8B8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(A8B8G8R8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(B8G8R8A8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(R8G8B8X8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(X8R8G8B8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(X8B8G8R8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
AIL_FMT(B8G8R8X8_UNORM, R8G8B8A8, UNORM, U8NORM),
|
||||
|
||||
AIL_FMT(R16_UNORM, R16, UNORM, U16NORM),
|
||||
AIL_FMT(R16G16_UNORM, R16G16, UNORM, U16NORM),
|
||||
AIL_FMT(R16G16B16A16_UNORM, R16G16B16A16, UNORM, U16NORM),
|
||||
AIL_FMT(R16_SNORM, R16, SNORM, S16NORM),
|
||||
AIL_FMT(R16G16_SNORM, R16G16, SNORM, S16NORM),
|
||||
AIL_FMT(R16G16B16A16_SNORM, R16G16B16A16, SNORM, S16NORM),
|
||||
|
||||
AIL_FMT(R8_SRGB, R8, UNORM, SRGBA8),
|
||||
AIL_FMT(R8G8_SRGB, R8G8, UNORM, SRGBA8),
|
||||
AIL_FMT(R8G8B8A8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(A8R8G8B8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(A8B8G8R8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(B8G8R8A8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(R8G8B8X8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(X8R8G8B8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(X8B8G8R8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
AIL_FMT(B8G8R8X8_SRGB, R8G8B8A8, UNORM, SRGBA8),
|
||||
|
||||
AIL_FMT(R8_SNORM, R8, SNORM, S8NORM),
|
||||
AIL_FMT(R8G8_SNORM, R8G8, SNORM, S8NORM),
|
||||
AIL_FMT(R8G8B8A8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(A8R8G8B8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(A8B8G8R8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(B8G8R8A8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(R8G8B8X8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(X8R8G8B8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(X8B8G8R8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
AIL_FMT(B8G8R8X8_SNORM, R8G8B8A8, SNORM, S8NORM),
|
||||
|
||||
AIL_FMT(R16_FLOAT, R16, FLOAT, F16),
|
||||
AIL_FMT(R16G16_FLOAT, R16G16, FLOAT, F16),
|
||||
AIL_FMT(R16G16B16X16_FLOAT, R16G16B16A16, FLOAT, F16),
|
||||
AIL_FMT(R16G16B16A16_FLOAT, R16G16B16A16, FLOAT, F16),
|
||||
|
||||
AIL_FMT(R32_FLOAT, R32, FLOAT, I32),
|
||||
AIL_FMT(R32G32_FLOAT, R32G32, FLOAT, I32),
|
||||
AIL_FMT(R32G32B32X32_FLOAT, R32G32B32A32, FLOAT, I32),
|
||||
AIL_FMT(R32G32B32A32_FLOAT, R32G32B32A32, FLOAT, I32),
|
||||
|
||||
AIL_FMT(R8_UINT, R8, UINT, I8),
|
||||
AIL_FMT(R8G8_UINT, R8G8, UINT, I8),
|
||||
AIL_FMT(R8G8B8X8_UINT, R8G8B8A8, UINT, I8),
|
||||
AIL_FMT(R8G8B8A8_UINT, R8G8B8A8, UINT, I8),
|
||||
AIL_FMT(B8G8R8X8_UINT, R8G8B8A8, UINT, I8),
|
||||
AIL_FMT(B8G8R8A8_UINT, R8G8B8A8, UINT, I8),
|
||||
|
||||
AIL_FMT(R16_UINT, R16, UINT, I16),
|
||||
AIL_FMT(R16G16_UINT, R16G16, UINT, I16),
|
||||
AIL_FMT(R16G16B16X16_UINT, R16G16B16A16, UINT, I16),
|
||||
AIL_FMT(R16G16B16A16_UINT, R16G16B16A16, UINT, I16),
|
||||
|
||||
AIL_FMT(R32_UINT, R32, UINT, I32),
|
||||
AIL_FMT(R32G32_UINT, R32G32, UINT, I32),
|
||||
AIL_FMT(R32G32B32X32_UINT, R32G32B32A32, UINT, I32),
|
||||
AIL_FMT(R32G32B32A32_UINT, R32G32B32A32, UINT, I32),
|
||||
|
||||
AIL_FMT(R8_SINT, R8, SINT, I8),
|
||||
AIL_FMT(R8G8_SINT, R8G8, SINT, I8),
|
||||
AIL_FMT(R8G8B8X8_SINT, R8G8B8A8, SINT, I8),
|
||||
AIL_FMT(R8G8B8A8_SINT, R8G8B8A8, SINT, I8),
|
||||
AIL_FMT(B8G8R8X8_SINT, R8G8B8A8, SINT, I8),
|
||||
AIL_FMT(B8G8R8A8_SINT, R8G8B8A8, SINT, I8),
|
||||
|
||||
AIL_FMT(R16_SINT, R16, SINT, I16),
|
||||
AIL_FMT(R16G16_SINT, R16G16, SINT, I16),
|
||||
AIL_FMT(R16G16B16X16_SINT, R16G16B16A16, SINT, I16),
|
||||
AIL_FMT(R16G16B16A16_SINT, R16G16B16A16, SINT, I16),
|
||||
|
||||
AIL_FMT(R32_SINT, R32, SINT, I32),
|
||||
AIL_FMT(R32G32_SINT, R32G32, SINT, I32),
|
||||
AIL_FMT(R32G32B32X32_SINT, R32G32B32A32, SINT, I32),
|
||||
AIL_FMT(R32G32B32A32_SINT, R32G32B32A32, SINT, I32),
|
||||
|
||||
AIL_FMT(Z16_UNORM, R16, UNORM, _),
|
||||
AIL_FMT(Z32_FLOAT, R32, FLOAT, _),
|
||||
AIL_FMT(Z32_FLOAT_S8X24_UINT, R32, FLOAT, _),
|
||||
AIL_FMT(S8_UINT, R8, UINT, _),
|
||||
|
||||
/* The stencil part of Z32F + S8 is just S8 */
|
||||
AIL_FMT(X32_S8X24_UINT, R8, UINT, _),
|
||||
|
||||
/* These must be lowered by u_transfer_helper to Z32F + S8 */
|
||||
AIL_FMT(Z24X8_UNORM, R32, FLOAT, _),
|
||||
AIL_FMT(Z24_UNORM_S8_UINT, R32, FLOAT, _),
|
||||
|
||||
AIL_FMT(R10G10B10A2_UNORM, R10G10B10A2, UNORM, RGB10A2),
|
||||
AIL_FMT(R10G10B10X2_UNORM, R10G10B10A2, UNORM, RGB10A2),
|
||||
AIL_FMT(B10G10R10A2_UNORM, R10G10B10A2, UNORM, RGB10A2),
|
||||
AIL_FMT(B10G10R10X2_UNORM, R10G10B10A2, UNORM, RGB10A2),
|
||||
|
||||
AIL_FMT(R10G10B10A2_UINT, R10G10B10A2, UINT, I16),
|
||||
AIL_FMT(B10G10R10A2_UINT, R10G10B10A2, UINT, I16),
|
||||
|
||||
/* I don't see why this wouldn't be renderable, but it doesn't seem to work
|
||||
* properly and it's not in Metal.
|
||||
*/
|
||||
AIL_FMT(R10G10B10A2_SINT, R10G10B10A2, SINT, _),
|
||||
AIL_FMT(B10G10R10A2_SINT, R10G10B10A2, SINT, _),
|
||||
|
||||
AIL_FMT(R11G11B10_FLOAT, R11G11B10, FLOAT, RG11B10F),
|
||||
AIL_FMT(R9G9B9E5_FLOAT, R9G9B9E5, FLOAT, RGB9E5),
|
||||
|
||||
/* These formats are emulated for texture buffers only */
|
||||
AIL_FMT(R32G32B32_FLOAT, R32G32B32_EMULATED, FLOAT, _),
|
||||
AIL_FMT(R32G32B32_UINT, R32G32B32_EMULATED, UINT, _),
|
||||
AIL_FMT(R32G32B32_SINT, R32G32B32_EMULATED, SINT, _),
|
||||
|
||||
/* Likewise, luminance/alpha/intensity formats are supported for texturing,
|
||||
* because they are required for texture buffers in the compat profile and
|
||||
* mesa/st is unable to emulate them for texture buffers. Our Gallium driver
|
||||
* handles the swizzles appropriately, so we just need to plumb through the
|
||||
* enums.
|
||||
*
|
||||
* If mesa/st grows emulation for these formats later, we can drop this.
|
||||
*/
|
||||
AIL_FMT(A8_UNORM, R8, UNORM, _),
|
||||
AIL_FMT(A16_UNORM, R16, UNORM, _),
|
||||
AIL_FMT(A8_SINT, R8, SINT, _),
|
||||
AIL_FMT(A16_SINT, R16, SINT, _),
|
||||
AIL_FMT(A32_SINT, R32, SINT, _),
|
||||
AIL_FMT(A8_UINT, R8, UINT, _),
|
||||
AIL_FMT(A16_UINT, R16, UINT, _),
|
||||
AIL_FMT(A32_UINT, R32, UINT, _),
|
||||
AIL_FMT(A16_FLOAT, R16, FLOAT, _),
|
||||
AIL_FMT(A32_FLOAT, R32, FLOAT, _),
|
||||
|
||||
AIL_FMT(L8_UNORM, R8, UNORM, _),
|
||||
AIL_FMT(L16_UNORM, R16, UNORM, _),
|
||||
AIL_FMT(L8_SINT, R8, SINT, _),
|
||||
AIL_FMT(L16_SINT, R16, SINT, _),
|
||||
AIL_FMT(L32_SINT, R32, SINT, _),
|
||||
AIL_FMT(L8_UINT, R8, UINT, _),
|
||||
AIL_FMT(L16_UINT, R16, UINT, _),
|
||||
AIL_FMT(L32_UINT, R32, UINT, _),
|
||||
AIL_FMT(L16_FLOAT, R16, FLOAT, _),
|
||||
AIL_FMT(L32_FLOAT, R32, FLOAT, _),
|
||||
|
||||
AIL_FMT(L8A8_UNORM, R8G8, UNORM, _),
|
||||
AIL_FMT(L16A16_UNORM, R16G16, UNORM, _),
|
||||
AIL_FMT(L8A8_SINT, R8G8, SINT, _),
|
||||
AIL_FMT(L16A16_SINT, R16G16, SINT, _),
|
||||
AIL_FMT(L32A32_SINT, R32G32, SINT, _),
|
||||
AIL_FMT(L8A8_UINT, R8G8, UINT, _),
|
||||
AIL_FMT(L16A16_UINT, R16G16, UINT, _),
|
||||
AIL_FMT(L32A32_UINT, R32G32, UINT, _),
|
||||
AIL_FMT(L16A16_FLOAT, R16G16, FLOAT, _),
|
||||
AIL_FMT(L32A32_FLOAT, R32G32, FLOAT, _),
|
||||
|
||||
AIL_FMT(I8_UNORM, R8, UNORM, _),
|
||||
AIL_FMT(I16_UNORM, R16, UNORM, _),
|
||||
AIL_FMT(I8_SINT, R8, SINT, _),
|
||||
AIL_FMT(I16_SINT, R16, SINT, _),
|
||||
AIL_FMT(I32_SINT, R32, SINT, _),
|
||||
AIL_FMT(I8_UINT, R8, UINT, _),
|
||||
AIL_FMT(I16_UINT, R16, UINT, _),
|
||||
AIL_FMT(I32_UINT, R32, UINT, _),
|
||||
AIL_FMT(I16_FLOAT, R16, FLOAT, _),
|
||||
AIL_FMT(I32_FLOAT, R32, FLOAT, _),
|
||||
|
||||
AIL_FMT(ETC1_RGB8, ETC2_RGB8, UNORM, _),
|
||||
AIL_FMT(ETC2_RGB8, ETC2_RGB8, UNORM, _),
|
||||
AIL_FMT(ETC2_SRGB8, ETC2_RGB8, UNORM, _),
|
||||
AIL_FMT(ETC2_RGB8A1, ETC2_RGB8A1, UNORM, _),
|
||||
AIL_FMT(ETC2_SRGB8A1, ETC2_RGB8A1, UNORM, _),
|
||||
AIL_FMT(ETC2_RGBA8, ETC2_RGBA8, UNORM, _),
|
||||
AIL_FMT(ETC2_SRGBA8, ETC2_RGBA8, UNORM, _),
|
||||
AIL_FMT(ETC2_R11_UNORM, EAC_R11, UNORM, _),
|
||||
AIL_FMT(ETC2_R11_SNORM, EAC_R11, SNORM, _),
|
||||
AIL_FMT(ETC2_RG11_UNORM, EAC_RG11, UNORM, _),
|
||||
AIL_FMT(ETC2_RG11_SNORM, EAC_RG11, SNORM, _),
|
||||
|
||||
AIL_FMT(ASTC_4x4, ASTC_4X4, UNORM, _),
|
||||
AIL_FMT(ASTC_5x4, ASTC_5X4, UNORM, _),
|
||||
AIL_FMT(ASTC_5x5, ASTC_5X5, UNORM, _),
|
||||
AIL_FMT(ASTC_6x5, ASTC_6X5, UNORM, _),
|
||||
AIL_FMT(ASTC_6x6, ASTC_6X6, UNORM, _),
|
||||
AIL_FMT(ASTC_8x5, ASTC_8X5, UNORM, _),
|
||||
AIL_FMT(ASTC_8x6, ASTC_8X6, UNORM, _),
|
||||
AIL_FMT(ASTC_8x8, ASTC_8X8, UNORM, _),
|
||||
AIL_FMT(ASTC_10x5, ASTC_10X5, UNORM, _),
|
||||
AIL_FMT(ASTC_10x6, ASTC_10X6, UNORM, _),
|
||||
AIL_FMT(ASTC_10x8, ASTC_10X8, UNORM, _),
|
||||
AIL_FMT(ASTC_10x10, ASTC_10X10, UNORM, _),
|
||||
AIL_FMT(ASTC_12x10, ASTC_12X10, UNORM, _),
|
||||
AIL_FMT(ASTC_12x12, ASTC_12X12, UNORM, _),
|
||||
|
||||
AIL_FMT(ASTC_4x4_SRGB, ASTC_4X4, UNORM, _),
|
||||
AIL_FMT(ASTC_5x4_SRGB, ASTC_5X4, UNORM, _),
|
||||
AIL_FMT(ASTC_5x5_SRGB, ASTC_5X5, UNORM, _),
|
||||
AIL_FMT(ASTC_6x5_SRGB, ASTC_6X5, UNORM, _),
|
||||
AIL_FMT(ASTC_6x6_SRGB, ASTC_6X6, UNORM, _),
|
||||
AIL_FMT(ASTC_8x5_SRGB, ASTC_8X5, UNORM, _),
|
||||
AIL_FMT(ASTC_8x6_SRGB, ASTC_8X6, UNORM, _),
|
||||
AIL_FMT(ASTC_8x8_SRGB, ASTC_8X8, UNORM, _),
|
||||
AIL_FMT(ASTC_10x5_SRGB, ASTC_10X5, UNORM, _),
|
||||
AIL_FMT(ASTC_10x6_SRGB, ASTC_10X6, UNORM, _),
|
||||
AIL_FMT(ASTC_10x8_SRGB, ASTC_10X8, UNORM, _),
|
||||
AIL_FMT(ASTC_10x10_SRGB, ASTC_10X10, UNORM, _),
|
||||
AIL_FMT(ASTC_12x10_SRGB, ASTC_12X10, UNORM, _),
|
||||
AIL_FMT(ASTC_12x12_SRGB, ASTC_12X12, UNORM, _),
|
||||
|
||||
AIL_FMT(ASTC_4x4_FLOAT, ASTC_4X4, FLOAT, _),
|
||||
AIL_FMT(ASTC_5x4_FLOAT, ASTC_5X4, FLOAT, _),
|
||||
AIL_FMT(ASTC_5x5_FLOAT, ASTC_5X5, FLOAT, _),
|
||||
AIL_FMT(ASTC_6x5_FLOAT, ASTC_6X5, FLOAT, _),
|
||||
AIL_FMT(ASTC_6x6_FLOAT, ASTC_6X6, FLOAT, _),
|
||||
AIL_FMT(ASTC_8x5_FLOAT, ASTC_8X5, FLOAT, _),
|
||||
AIL_FMT(ASTC_8x6_FLOAT, ASTC_8X6, FLOAT, _),
|
||||
AIL_FMT(ASTC_8x8_FLOAT, ASTC_8X8, FLOAT, _),
|
||||
AIL_FMT(ASTC_10x5_FLOAT, ASTC_10X5, FLOAT, _),
|
||||
AIL_FMT(ASTC_10x6_FLOAT, ASTC_10X6, FLOAT, _),
|
||||
AIL_FMT(ASTC_10x8_FLOAT, ASTC_10X8, FLOAT, _),
|
||||
AIL_FMT(ASTC_10x10_FLOAT, ASTC_10X10, FLOAT, _),
|
||||
AIL_FMT(ASTC_12x10_FLOAT, ASTC_12X10, FLOAT, _),
|
||||
AIL_FMT(ASTC_12x12_FLOAT, ASTC_12X12, FLOAT, _),
|
||||
|
||||
AIL_FMT(DXT1_RGB, BC1, UNORM, _),
|
||||
AIL_FMT(DXT1_RGBA, BC1, UNORM, _),
|
||||
AIL_FMT(DXT1_SRGB, BC1, UNORM, _),
|
||||
AIL_FMT(DXT1_SRGBA, BC1, UNORM, _),
|
||||
AIL_FMT(DXT3_RGBA, BC2, UNORM, _),
|
||||
AIL_FMT(DXT3_SRGBA, BC2, UNORM, _),
|
||||
AIL_FMT(DXT5_RGBA, BC3, UNORM, _),
|
||||
AIL_FMT(DXT5_SRGBA, BC3, UNORM, _),
|
||||
AIL_FMT(RGTC1_UNORM, BC4, UNORM, _),
|
||||
AIL_FMT(RGTC1_SNORM, BC4, SNORM, _),
|
||||
AIL_FMT(RGTC2_UNORM, BC5, UNORM, _),
|
||||
AIL_FMT(RGTC2_SNORM, BC5, SNORM, _),
|
||||
AIL_FMT(BPTC_RGB_FLOAT, BC6H, FLOAT, _),
|
||||
AIL_FMT(BPTC_RGB_UFLOAT, BC6H_UFLOAT, FLOAT, _),
|
||||
AIL_FMT(BPTC_RGBA_UNORM, BC7, UNORM, _),
|
||||
AIL_FMT(BPTC_SRGBA, BC7, UNORM, _),
|
||||
};
|
||||
/* clang-format on */
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "layout.h"
|
||||
#include "util/format/u_format.h"
|
||||
#include "util/u_math.h"
|
||||
|
||||
static void
|
||||
ail_initialize_linear(struct ail_layout *layout)
|
||||
{
|
||||
/* Select the optimal stride if none is forced */
|
||||
if (layout->linear_stride_B == 0) {
|
||||
uint32_t minimum_stride_B =
|
||||
util_format_get_stride(layout->format, layout->width_px);
|
||||
|
||||
layout->linear_stride_B = ALIGN_POT(minimum_stride_B, AIL_CACHELINE);
|
||||
}
|
||||
|
||||
assert((layout->linear_stride_B % 16) == 0 && "Strides must be aligned");
|
||||
|
||||
/* Layer stride must be cache line aligned to pack linear 2D arrays */
|
||||
layout->layer_stride_B = align64(
|
||||
(uint64_t)layout->linear_stride_B * layout->height_px, AIL_CACHELINE);
|
||||
|
||||
layout->size_B =
|
||||
layout->level_offsets_B[0] + (layout->layer_stride_B * layout->depth_px);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the maximum tile size possible for a given block size. This satisfy
|
||||
* width * height * blocksize = 16384 = page size, so each tile is one page.
|
||||
*/
|
||||
struct ail_tile
|
||||
ail_get_max_tile_size(unsigned blocksize_B)
|
||||
{
|
||||
/* clang-format off */
|
||||
switch (blocksize_B) {
|
||||
case 1: return (struct ail_tile) { 128, 128 };
|
||||
case 2: return (struct ail_tile) { 128, 64 };
|
||||
case 4: return (struct ail_tile) { 64, 64 };
|
||||
case 8: return (struct ail_tile) { 64, 32 };
|
||||
case 16: return (struct ail_tile) { 32, 32 };
|
||||
case 32: return (struct ail_tile) { 32, 16 };
|
||||
case 64: return (struct ail_tile) { 16, 16 };
|
||||
default: UNREACHABLE("Invalid blocksize");
|
||||
}
|
||||
/* clang-format on */
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the number of bytes in a block. This must take both block
|
||||
* dimensions and multisampling into account.
|
||||
*/
|
||||
static uint32_t
|
||||
ail_get_block_size_B(struct ail_layout *layout)
|
||||
{
|
||||
ASSERTED const struct util_format_description *desc =
|
||||
util_format_description(layout->format);
|
||||
|
||||
assert(((layout->sample_count_sa == 1) ||
|
||||
(desc->block.width == 1 && desc->block.height == 1)) &&
|
||||
"multisampling and block-compression are mutually-exclusive");
|
||||
|
||||
return util_format_get_blocksize(layout->format) * layout->sample_count_sa;
|
||||
}
|
||||
|
||||
static void
|
||||
ail_initialize_gpu_tiled(struct ail_layout *layout)
|
||||
{
|
||||
unsigned offset_B = 0;
|
||||
unsigned blocksize_B = ail_get_block_size_B(layout);
|
||||
unsigned w_el = util_format_get_nblocksx(layout->format, layout->width_px);
|
||||
unsigned h_el = util_format_get_nblocksy(layout->format, layout->height_px);
|
||||
unsigned bw_px = util_format_get_blockwidth(layout->format);
|
||||
unsigned bh_px = util_format_get_blockheight(layout->format);
|
||||
bool compressed = util_format_is_compressed(layout->format);
|
||||
|
||||
/* Calculate the tile size used for the large miptree, and the dimensions of
|
||||
* level 0 given that tile size.
|
||||
*/
|
||||
struct ail_tile tilesize_el = ail_get_max_tile_size(blocksize_B);
|
||||
unsigned stx_tiles = DIV_ROUND_UP(w_el, tilesize_el.width_el);
|
||||
unsigned sty_tiles = DIV_ROUND_UP(h_el, tilesize_el.height_el);
|
||||
unsigned sarea_tiles = stx_tiles * sty_tiles;
|
||||
|
||||
/* Calculate which level the small power-of-two miptree begins at. The
|
||||
* power-of-two miptree is used when either the width or the height is
|
||||
* smaller than a single large tile.
|
||||
*/
|
||||
unsigned pot_level = 0;
|
||||
unsigned pot_w_px = bw_px * w_el;
|
||||
unsigned pot_h_px = bh_px * h_el;
|
||||
do {
|
||||
unsigned pot_w_el = util_format_get_nblocksx(layout->format, pot_w_px);
|
||||
unsigned pot_h_el = util_format_get_nblocksy(layout->format, pot_h_px);
|
||||
if (pot_w_el < tilesize_el.width_el || pot_h_el < tilesize_el.height_el)
|
||||
break;
|
||||
pot_w_px = u_minify(pot_w_px, 1);
|
||||
pot_h_px = u_minify(pot_h_px, 1);
|
||||
pot_level++;
|
||||
} while (1);
|
||||
|
||||
/* First allocate the large miptree. All tiles in the large miptree are of
|
||||
* size tilesize_el and have their dimensions given by stx/sty/sarea.
|
||||
*/
|
||||
for (unsigned l = 0; l < MIN2(pot_level, layout->levels); ++l) {
|
||||
unsigned tiles = (sarea_tiles >> (2 * l));
|
||||
|
||||
bool pad_left = (stx_tiles & BITFIELD_MASK(l));
|
||||
bool pad_bottom = (sty_tiles & BITFIELD_MASK(l));
|
||||
bool pad_corner = pad_left && pad_bottom;
|
||||
|
||||
if (pad_left)
|
||||
tiles += (sty_tiles >> l);
|
||||
|
||||
if (pad_bottom)
|
||||
tiles += (stx_tiles >> l);
|
||||
|
||||
if (pad_corner)
|
||||
tiles += 1;
|
||||
|
||||
unsigned size_el = tiles * tilesize_el.width_el * tilesize_el.height_el;
|
||||
layout->level_offsets_B[l] = offset_B;
|
||||
offset_B = ALIGN_POT(offset_B + (blocksize_B * size_el), AIL_CACHELINE);
|
||||
|
||||
layout->stride_el[l] = util_format_get_nblocksx(
|
||||
layout->format, u_minify(layout->width_px, l));
|
||||
|
||||
/* Compressed textures pad the stride in this case */
|
||||
if (compressed && pad_left)
|
||||
layout->stride_el[l]++;
|
||||
|
||||
layout->tilesize_el[l] = tilesize_el;
|
||||
}
|
||||
|
||||
/* Then begin the POT miptree. Note that we round up to a power-of-two
|
||||
* outside the loop. That ensures correct handling of cases like 33x33
|
||||
* images, where the round-down error of right-shifting could cause incorrect
|
||||
* tile size calculations.
|
||||
*/
|
||||
unsigned potw_el, poth_el;
|
||||
if (compressed) {
|
||||
/* Compressed formats round then minify instead of minifying then rounding
|
||||
*/
|
||||
potw_el = u_minify(util_next_power_of_two(w_el), pot_level);
|
||||
poth_el = u_minify(util_next_power_of_two(h_el), pot_level);
|
||||
} else {
|
||||
potw_el = util_next_power_of_two(u_minify(w_el, pot_level));
|
||||
poth_el = util_next_power_of_two(u_minify(h_el, pot_level));
|
||||
}
|
||||
|
||||
/* Finally we allocate the POT miptree, starting at level pot_level. Each
|
||||
* level uses the largest power-of-two tile that fits the level.
|
||||
*/
|
||||
for (unsigned l = pot_level; l < layout->levels; ++l) {
|
||||
unsigned size_el = potw_el * poth_el;
|
||||
layout->level_offsets_B[l] = offset_B;
|
||||
offset_B = ALIGN_POT(offset_B + (blocksize_B * size_el), AIL_CACHELINE);
|
||||
|
||||
/* The tilesize is based on the true mipmap level size, not the POT
|
||||
* rounded size, except for compressed textures */
|
||||
unsigned tilesize_el;
|
||||
if (compressed)
|
||||
tilesize_el = util_next_power_of_two(MIN2(potw_el, poth_el));
|
||||
else
|
||||
tilesize_el = util_next_power_of_two(u_minify(MIN2(w_el, h_el), l));
|
||||
layout->tilesize_el[l] = (struct ail_tile){tilesize_el, tilesize_el};
|
||||
layout->stride_el[l] = util_format_get_nblocksx(
|
||||
layout->format, u_minify(layout->width_px, l));
|
||||
|
||||
potw_el = u_minify(potw_el, 1);
|
||||
poth_el = u_minify(poth_el, 1);
|
||||
}
|
||||
|
||||
/* Add the end offset so we can easily recover the size of a level */
|
||||
assert(layout->levels < ARRAY_SIZE(layout->level_offsets_B));
|
||||
layout->level_offsets_B[layout->levels] = offset_B;
|
||||
|
||||
/* Determine the start of the miptail. From that level on, we can no longer
|
||||
* precisely bind at page granularity.
|
||||
*/
|
||||
layout->mip_tail_first_lod = MIN2(pot_level, layout->levels);
|
||||
|
||||
/* Determine the stride of the miptail. Sparse arrayed images inherently
|
||||
* require page-aligned layers to be able to bind individual layers.
|
||||
*/
|
||||
unsigned tail_offset_B = layout->level_offsets_B[layout->mip_tail_first_lod];
|
||||
layout->mip_tail_stride = align(offset_B - tail_offset_B, AIL_PAGESIZE);
|
||||
|
||||
/* Align layer size if we have mipmaps and one miptree is larger than one
|
||||
* page */
|
||||
layout->page_aligned_layers = layout->levels != 1 && offset_B > AIL_PAGESIZE;
|
||||
|
||||
/* Single-layer images are not padded unless they are Z/S */
|
||||
bool zs = util_format_is_depth_or_stencil(layout->format);
|
||||
if (layout->depth_px == 1 && !zs)
|
||||
layout->page_aligned_layers = false;
|
||||
|
||||
/* For writable images, we require page-aligned layers. This appears to be
|
||||
* required for PBE stores, including block stores for colour rendering.
|
||||
* Likewise, we specify the ZLS layer stride in pages, so we need
|
||||
* page-aligned layers for renderable depth/stencil targets.
|
||||
*/
|
||||
layout->page_aligned_layers |= layout->writeable_image;
|
||||
layout->page_aligned_layers |= layout->renderable && layout->depth_px > 1;
|
||||
|
||||
if (layout->page_aligned_layers)
|
||||
layout->layer_stride_B = ALIGN_POT(offset_B, AIL_PAGESIZE);
|
||||
else
|
||||
layout->layer_stride_B = offset_B;
|
||||
|
||||
layout->size_B = (uint64_t)layout->layer_stride_B * layout->depth_px;
|
||||
}
|
||||
|
||||
static void
|
||||
ail_initialize_twiddled(struct ail_layout *layout)
|
||||
{
|
||||
unsigned offset_B = 0;
|
||||
unsigned blocksize_B = ail_get_block_size_B(layout);
|
||||
unsigned w_el = util_format_get_nblocksx(layout->format, layout->width_px);
|
||||
unsigned h_el = util_format_get_nblocksy(layout->format, layout->height_px);
|
||||
bool compressed = util_format_is_compressed(layout->format);
|
||||
|
||||
for (unsigned l = 0; l < layout->levels; ++l) {
|
||||
unsigned alloc_w_el = u_minify(util_next_power_of_two(w_el), l);
|
||||
unsigned alloc_h_el = u_minify(util_next_power_of_two(h_el), l);
|
||||
unsigned logical_w_el = util_next_power_of_two(u_minify(w_el, l));
|
||||
unsigned logical_h_el = util_next_power_of_two(u_minify(h_el, l));
|
||||
|
||||
/* AGX: Even the exceptions have exceptions */
|
||||
if (compressed) {
|
||||
logical_w_el = alloc_w_el;
|
||||
logical_h_el = alloc_h_el;
|
||||
}
|
||||
|
||||
unsigned size_el = alloc_w_el * alloc_h_el;
|
||||
layout->level_offsets_B[l] = offset_B;
|
||||
offset_B = ALIGN_POT(offset_B + (blocksize_B * size_el), AIL_CACHELINE);
|
||||
|
||||
unsigned minor_el = MIN2(logical_w_el, logical_h_el);
|
||||
layout->stride_el[l] = logical_w_el;
|
||||
layout->tilesize_el[l] = (struct ail_tile){minor_el, minor_el};
|
||||
}
|
||||
|
||||
/* Add the end offset so we can easily recover the size of a level */
|
||||
assert(layout->levels < ARRAY_SIZE(layout->level_offsets_B));
|
||||
layout->level_offsets_B[layout->levels] = offset_B;
|
||||
|
||||
/* Determine the start of the miptail. From that level on, we can no longer
|
||||
* precisely bind at page granularity.
|
||||
*/
|
||||
/* XXX TODO */
|
||||
layout->mip_tail_first_lod = 0;
|
||||
|
||||
/* Determine the stride of the miptail. Sparse arrayed images inherently
|
||||
* require page-aligned layers to be able to bind individual layers.
|
||||
*/
|
||||
unsigned tail_offset_B = layout->level_offsets_B[layout->mip_tail_first_lod];
|
||||
layout->mip_tail_stride = align(offset_B - tail_offset_B, AIL_PAGESIZE);
|
||||
|
||||
layout->page_aligned_layers = true;
|
||||
layout->layer_stride_B = ALIGN_POT(offset_B, AIL_PAGESIZE);
|
||||
layout->size_B = (uint64_t)layout->layer_stride_B * layout->depth_px;
|
||||
}
|
||||
|
||||
static void
|
||||
ail_initialize_compression(struct ail_layout *layout)
|
||||
{
|
||||
assert(!util_format_is_compressed(layout->format) &&
|
||||
"Compressed pixel formats not supported");
|
||||
assert(util_format_get_blockwidth(layout->format) == 1);
|
||||
assert(util_format_get_blockheight(layout->format) == 1);
|
||||
|
||||
unsigned width_sa =
|
||||
ail_effective_width_sa(layout->width_px, layout->sample_count_sa);
|
||||
unsigned height_sa =
|
||||
ail_effective_height_sa(layout->height_px, layout->sample_count_sa);
|
||||
|
||||
assert(width_sa >= 16 && "Small textures are never compressed");
|
||||
assert(height_sa >= 16 && "Small textures are never compressed");
|
||||
|
||||
layout->metadata_offset_B = layout->size_B;
|
||||
|
||||
width_sa = ALIGN_POT(width_sa, 16);
|
||||
height_sa = ALIGN_POT(height_sa, 16);
|
||||
|
||||
unsigned compbuf_B = 0;
|
||||
|
||||
for (unsigned l = 0;
|
||||
l < layout->levels && ail_is_level_allocated_compressed(layout, l);
|
||||
++l) {
|
||||
|
||||
layout->level_offsets_compressed_B[l] = compbuf_B;
|
||||
|
||||
/* The metadata buffer contains 8 bytes per 16x16 compression tile.
|
||||
* Addressing is fully twiddled, so both width and height are padded to
|
||||
* powers-of-two.
|
||||
*/
|
||||
unsigned w_tl = DIV_ROUND_UP(util_next_power_of_two(width_sa), 16);
|
||||
unsigned h_tl = DIV_ROUND_UP(util_next_power_of_two(height_sa), 16);
|
||||
unsigned B_per_tl_2 = 8;
|
||||
|
||||
compbuf_B += ALIGN_POT(w_tl * h_tl * B_per_tl_2, AIL_CACHELINE);
|
||||
|
||||
width_sa = DIV_ROUND_UP(width_sa, 2);
|
||||
height_sa = DIV_ROUND_UP(height_sa, 2);
|
||||
}
|
||||
|
||||
layout->compression_layer_stride_B = compbuf_B;
|
||||
layout->size_B +=
|
||||
(uint64_t)(layout->compression_layer_stride_B * layout->depth_px);
|
||||
}
|
||||
|
||||
static void
|
||||
ail_initialize_sparse_table(struct ail_layout *layout)
|
||||
{
|
||||
layout->sparse_folios_per_layer =
|
||||
DIV_ROUND_UP(layout->layer_stride_B, AIL_IMAGE_SIZE_PER_FOLIO_B);
|
||||
|
||||
unsigned folios = layout->sparse_folios_per_layer * layout->depth_px;
|
||||
layout->sparse_table_size_B = folios * AIL_FOLIO_SIZE_B;
|
||||
}
|
||||
|
||||
void
|
||||
ail_make_miptree(struct ail_layout *layout)
|
||||
{
|
||||
assert(layout->width_px >= 1 && "Invalid dimensions");
|
||||
assert(layout->height_px >= 1 && "Invalid dimensions");
|
||||
assert(layout->depth_px >= 1 && "Invalid dimensions");
|
||||
|
||||
if (layout->tiling == AIL_TILING_LINEAR) {
|
||||
assert(layout->levels == 1 && "Invalid linear layout");
|
||||
assert(layout->sample_count_sa == 1 &&
|
||||
"Multisampled linear layouts not supported");
|
||||
assert(util_format_get_blockwidth(layout->format) == 1 &&
|
||||
"Strided linear block formats unsupported");
|
||||
assert(util_format_get_blockheight(layout->format) == 1 &&
|
||||
"Strided linear block formats unsupported");
|
||||
} else {
|
||||
assert(layout->linear_stride_B == 0 && "Invalid nonlinear layout");
|
||||
assert(layout->levels >= 1 && "Invalid dimensions");
|
||||
assert(layout->sample_count_sa >= 1 && "Invalid sample count");
|
||||
assert(layout->level_offsets_B[0] == 0 && "Invalid offset");
|
||||
}
|
||||
|
||||
assert(!(layout->writeable_image && layout->compressed) &&
|
||||
"Writeable images must not be compressed");
|
||||
|
||||
/* Hardware strides are based on the maximum number of levels, so always
|
||||
* allocate them all.
|
||||
*/
|
||||
if (layout->levels > 1) {
|
||||
unsigned major_axis_px = MAX2(layout->width_px, layout->height_px);
|
||||
|
||||
if (layout->mipmapped_z)
|
||||
major_axis_px = MAX2(major_axis_px, layout->depth_px);
|
||||
|
||||
layout->levels = util_logbase2(major_axis_px) + 1;
|
||||
}
|
||||
|
||||
assert(util_format_get_blockdepth(layout->format) == 1 &&
|
||||
"Deep formats unsupported");
|
||||
|
||||
switch (layout->tiling) {
|
||||
case AIL_TILING_LINEAR:
|
||||
ail_initialize_linear(layout);
|
||||
break;
|
||||
case AIL_TILING_GPU:
|
||||
ail_initialize_gpu_tiled(layout);
|
||||
break;
|
||||
case AIL_TILING_TWIDDLED:
|
||||
ail_initialize_twiddled(layout);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE("Unsupported tiling");
|
||||
}
|
||||
|
||||
if (layout->compressed) {
|
||||
ail_initialize_compression(layout);
|
||||
}
|
||||
|
||||
ail_initialize_sparse_table(layout);
|
||||
|
||||
layout->size_B = ALIGN_POT(layout->size_B, AIL_CACHELINE);
|
||||
assert(layout->size_B > 0 && "Invalid dimensions");
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drm-uapi/drm_fourcc.h"
|
||||
#include "util/format/u_format.h"
|
||||
#include "util/macros.h"
|
||||
#include "util/u_math.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define AIL_CACHELINE 0x80
|
||||
#define AIL_PAGESIZE 0x4000
|
||||
#define AIL_MAX_MIP_LEVELS 16
|
||||
|
||||
enum ail_tiling {
|
||||
/**
|
||||
* Strided linear (raster order). Only allowed for 1D or 2D, without
|
||||
* mipmapping, multisampling, block-compression, or arrays.
|
||||
*/
|
||||
AIL_TILING_LINEAR,
|
||||
|
||||
/**
|
||||
* GPU-tiled. Always allowed.
|
||||
*/
|
||||
AIL_TILING_GPU,
|
||||
|
||||
/**
|
||||
* Fully twiddled.
|
||||
*/
|
||||
AIL_TILING_TWIDDLED,
|
||||
};
|
||||
|
||||
/*
|
||||
* Represents the dimensions of a single tile. Used to describe tiled layouts.
|
||||
* Width and height are in units of elements, not pixels, to model compressed
|
||||
* textures corrects.
|
||||
*
|
||||
* Invariant: width_el and height_el are powers of two.
|
||||
*/
|
||||
struct ail_tile {
|
||||
unsigned width_el, height_el;
|
||||
};
|
||||
|
||||
/*
|
||||
* An AGX image layout.
|
||||
*/
|
||||
struct ail_layout {
|
||||
/** Width, height, and depth in pixels at level 0 */
|
||||
uint32_t width_px, height_px, depth_px;
|
||||
|
||||
/** Number of samples per pixel. 1 if multisampling is disabled. */
|
||||
uint8_t sample_count_sa;
|
||||
|
||||
/** Number of miplevels. 1 if no mipmapping is used. */
|
||||
uint8_t levels;
|
||||
|
||||
/** Should this image be mipmapped along the Z-axis in addition to the X- and
|
||||
* Y-axes? This should be set for API-level 3D images, but not 2D arrays or
|
||||
* cubes.
|
||||
*/
|
||||
bool mipmapped_z;
|
||||
|
||||
/** Tiling mode used */
|
||||
enum ail_tiling tiling;
|
||||
|
||||
/** Whether compression is used. Requires a non-linear layout. */
|
||||
bool compressed;
|
||||
|
||||
/** Texture format */
|
||||
enum pipe_format format;
|
||||
|
||||
/**
|
||||
* If tiling is LINEAR, the number of bytes between adjacent rows of
|
||||
* elements. Otherwise, this field is zero.
|
||||
*/
|
||||
uint32_t linear_stride_B;
|
||||
|
||||
/**
|
||||
* Stride between layers of an array texture, including a cube map. Layer i
|
||||
* begins at offset (i * layer_stride_B) from the beginning of the texture.
|
||||
*
|
||||
* If depth_px = 1, the value of this field is UNDEFINED.
|
||||
*/
|
||||
uint64_t layer_stride_B;
|
||||
|
||||
/**
|
||||
* Whether the layer stride is aligned to the page size or not. The hardware
|
||||
* needs this flag to compute the implicit layer stride.
|
||||
*/
|
||||
bool page_aligned_layers;
|
||||
|
||||
/**
|
||||
* Offsets of mip levels within a layer.
|
||||
*/
|
||||
uint64_t level_offsets_B[AIL_MAX_MIP_LEVELS];
|
||||
|
||||
/**
|
||||
* For the compressed buffer, offsets of mip levels within a layer.
|
||||
*/
|
||||
uint64_t level_offsets_compressed_B[AIL_MAX_MIP_LEVELS];
|
||||
|
||||
/**
|
||||
* If tiling is nonlinear, the tile size used for each mip level within a
|
||||
* layer. Calculating tile sizes is the sole responsibility of
|
||||
* ail_initialized_twiddled.
|
||||
*/
|
||||
struct ail_tile tilesize_el[AIL_MAX_MIP_LEVELS];
|
||||
|
||||
/**
|
||||
* If tiling is nonlinear, the stride in elements used for each mip level
|
||||
* within a layer. Calculating level strides is the sole responsibility of
|
||||
* ail_initialized_twiddled. This is necessary because compressed pixel
|
||||
* formats may add extra stride padding.
|
||||
*/
|
||||
uint32_t stride_el[AIL_MAX_MIP_LEVELS];
|
||||
|
||||
/**
|
||||
* If the image is bound sparsely, the first LOD/level of the miptail.
|
||||
* This corresponds to Vulkan imageMipTailFirstLod.
|
||||
*/
|
||||
uint32_t mip_tail_first_lod;
|
||||
|
||||
/**
|
||||
* If the image is bound sparsely, the layer stride of the miptail.
|
||||
* Conceptually, this corresponds to Vulkan imageMipTailStride. However, it
|
||||
* is not actually used for imageMipTailStride due to complications with
|
||||
* standard sparse block sizes.
|
||||
*/
|
||||
uint32_t mip_tail_stride;
|
||||
|
||||
/* Offset of the start of the compression metadata buffer */
|
||||
uint32_t metadata_offset_B;
|
||||
|
||||
/* Stride between subsequent layers in the compression metadata buffer */
|
||||
uint64_t compression_layer_stride_B;
|
||||
|
||||
/* Size of entire texture */
|
||||
uint64_t size_B;
|
||||
|
||||
/* Size of the sparse page table required to address this image. This is
|
||||
* calculated for all images (whether sparse is used or not), since the
|
||||
* hardware has few requirements imposed on sparse.
|
||||
*
|
||||
* This size does not contribute to size_B. In a sparse image, size_B is the
|
||||
* logical size of the image (if it were completely resident), whereas
|
||||
* sparse_table_size_B is the physical size of the page table required to
|
||||
* address that logical image.
|
||||
*/
|
||||
uint64_t sparse_table_size_B;
|
||||
|
||||
/* Number of sparse folios required to cover a single layer of an image. This
|
||||
* is proportional to sparse_table_size_B but split out for convenience.
|
||||
*/
|
||||
uint32_t sparse_folios_per_layer;
|
||||
|
||||
/* Must the layout support writeable images? If false, the layout MUST NOT be
|
||||
* used as a writeable image (either PBE or image atomics).
|
||||
*/
|
||||
bool writeable_image;
|
||||
|
||||
/* Must the layout support rendering? If false, the layout MUST NOT be used
|
||||
* for rendering, either PBE or ZLS.
|
||||
*/
|
||||
bool renderable;
|
||||
};
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_linear_stride_B(const struct ail_layout *layout, ASSERTED uint8_t level)
|
||||
{
|
||||
assert(layout->tiling == AIL_TILING_LINEAR && "Invalid usage");
|
||||
assert(level == 0 && "Strided linear mipmapped textures are unsupported");
|
||||
|
||||
return layout->linear_stride_B;
|
||||
}
|
||||
|
||||
/*
|
||||
* For WSI purposes, we need to associate a stride with all layouts. In the
|
||||
* hardware, only strided linear images have an associated stride, there is no
|
||||
* natural stride associated with nonlinear images. However, various clients
|
||||
* assert that the stride is valid for the image if it were linear (even if it
|
||||
* is in fact not linear). In those cases, by convention we use the minimum
|
||||
* valid such stride.
|
||||
*/
|
||||
static inline uint32_t
|
||||
ail_get_wsi_stride_B(const struct ail_layout *layout, unsigned level)
|
||||
{
|
||||
assert(level == 0 && "Mipmaps cannot be shared as WSI");
|
||||
|
||||
if (layout->tiling == AIL_TILING_LINEAR)
|
||||
return ail_get_linear_stride_B(layout, level);
|
||||
else
|
||||
return util_format_get_stride(layout->format, layout->width_px);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_layer_offset_B(const struct ail_layout *layout, unsigned z_px)
|
||||
{
|
||||
return z_px * layout->layer_stride_B;
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_level_offset_B(const struct ail_layout *layout, unsigned level)
|
||||
{
|
||||
return layout->level_offsets_B[level];
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_layer_level_B(const struct ail_layout *layout, unsigned z_px,
|
||||
unsigned level)
|
||||
{
|
||||
return ail_get_layer_offset_B(layout, z_px) +
|
||||
ail_get_level_offset_B(layout, level);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_level_size_B(const struct ail_layout *layout, unsigned level)
|
||||
{
|
||||
if (layout->tiling == AIL_TILING_LINEAR) {
|
||||
assert(level == 0);
|
||||
return layout->layer_stride_B;
|
||||
} else {
|
||||
assert(level + 1 < ARRAY_SIZE(layout->level_offsets_B));
|
||||
return layout->level_offsets_B[level + 1] -
|
||||
layout->level_offsets_B[level];
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_linear_pixel_B(const struct ail_layout *layout, ASSERTED unsigned level,
|
||||
uint32_t x_px, uint32_t y_px, uint32_t z_px)
|
||||
{
|
||||
assert(level == 0 && "Strided linear mipmapped textures are unsupported");
|
||||
assert(util_format_get_blockwidth(layout->format) == 1 &&
|
||||
"Strided linear block formats unsupported");
|
||||
assert(util_format_get_blockheight(layout->format) == 1 &&
|
||||
"Strided linear block formats unsupported");
|
||||
assert(layout->sample_count_sa == 1 &&
|
||||
"Strided linear multisampling unsupported");
|
||||
|
||||
return ail_get_layer_offset_B(layout, z_px) +
|
||||
(y_px * ail_get_linear_stride_B(layout, level)) +
|
||||
(x_px * util_format_get_blocksize(layout->format));
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_space_bits(unsigned x)
|
||||
{
|
||||
uint32_t accum = 0;
|
||||
|
||||
/* With fully twiddled images, we can have up to 16384x16384 tiles,
|
||||
* justifying stopping the loop at 14.
|
||||
*/
|
||||
assert(x < (1 << 14) && "offset must be inside the tile");
|
||||
|
||||
for (unsigned i = 0; i < 14; ++i) {
|
||||
accum |= (x & (1 << i)) << i;
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
#define MOD_POT(x, y) (x) & ((y) - 1)
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_blocksize_B(const struct ail_layout *layout)
|
||||
{
|
||||
return util_format_get_blocksize(layout->format);
|
||||
}
|
||||
|
||||
static inline uint32_t
|
||||
ail_get_twiddled_block_B(const struct ail_layout *layout, unsigned level,
|
||||
uint32_t x_px, uint32_t y_px, uint32_t z_px)
|
||||
{
|
||||
assert(layout->tiling == AIL_TILING_GPU);
|
||||
assert(level < layout->levels);
|
||||
|
||||
unsigned x_el = util_format_get_nblocksx(layout->format, x_px);
|
||||
unsigned y_el = util_format_get_nblocksy(layout->format, y_px);
|
||||
|
||||
struct ail_tile tile_size = layout->tilesize_el[level];
|
||||
assert((x_el % tile_size.width_el) == 0 && "must be aligned");
|
||||
assert((y_el % tile_size.height_el) == 0 && "must be aligned");
|
||||
|
||||
unsigned stride_el = layout->stride_el[level];
|
||||
unsigned tile_area_el = tile_size.width_el * tile_size.height_el;
|
||||
unsigned tiles_per_row = DIV_ROUND_UP(stride_el, tile_size.width_el);
|
||||
|
||||
unsigned y_rowtile = y_el / tile_size.height_el;
|
||||
unsigned y_tile = y_rowtile * tiles_per_row;
|
||||
|
||||
unsigned tile_idx = y_tile + (x_el / tile_size.width_el);
|
||||
unsigned tile_offset_el = tile_idx * tile_area_el;
|
||||
unsigned tile_offset_B = tile_offset_el * ail_get_blocksize_B(layout);
|
||||
|
||||
return ail_get_layer_level_B(layout, z_px, level) + tile_offset_B;
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
ail_effective_width_sa(unsigned width_px, unsigned sample_count_sa)
|
||||
{
|
||||
return width_px * (sample_count_sa == 4 ? 2 : 1);
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
ail_effective_height_sa(unsigned height_px, unsigned sample_count_sa)
|
||||
{
|
||||
return height_px * (sample_count_sa >= 2 ? 2 : 1);
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
ail_metadata_width_tl(struct ail_layout *layout, unsigned level)
|
||||
{
|
||||
unsigned px = u_minify(layout->width_px, level);
|
||||
uint32_t sa = ail_effective_width_sa(px, layout->sample_count_sa);
|
||||
return DIV_ROUND_UP(sa, 16);
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
ail_metadata_height_tl(struct ail_layout *layout, unsigned level)
|
||||
{
|
||||
unsigned px = u_minify(layout->height_px, level);
|
||||
uint32_t sa = ail_effective_height_sa(px, layout->sample_count_sa);
|
||||
return DIV_ROUND_UP(sa, 16);
|
||||
}
|
||||
|
||||
/*
|
||||
* Even when the base mip level is compressed, high levels of the miptree
|
||||
* (smaller than 16 pixels on either axis) are not compressed as it would be
|
||||
* pointless. This queries this case.
|
||||
*/
|
||||
static inline bool
|
||||
ail_is_level_allocated_compressed(const struct ail_layout *layout,
|
||||
unsigned level)
|
||||
{
|
||||
unsigned width_sa = align(
|
||||
ail_effective_width_sa(layout->width_px, layout->sample_count_sa), 16);
|
||||
|
||||
unsigned height_sa = align(
|
||||
ail_effective_height_sa(layout->height_px, layout->sample_count_sa), 16);
|
||||
|
||||
return layout->compressed &&
|
||||
u_minify(MAX2(width_sa, height_sa), level) >= 16;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ail_is_level_logically_compressed(const struct ail_layout *layout,
|
||||
unsigned level)
|
||||
{
|
||||
unsigned width_sa =
|
||||
ail_effective_width_sa(layout->width_px, layout->sample_count_sa);
|
||||
|
||||
unsigned height_sa =
|
||||
ail_effective_height_sa(layout->height_px, layout->sample_count_sa);
|
||||
|
||||
return layout->compressed &&
|
||||
u_minify(MIN2(width_sa, height_sa), level) >= 16;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ail_is_level_twiddled_uncompressed(const struct ail_layout *layout,
|
||||
unsigned level)
|
||||
{
|
||||
if (layout->compressed) {
|
||||
return !ail_is_level_logically_compressed(layout, level);
|
||||
} else {
|
||||
return layout->tiling != AIL_TILING_LINEAR;
|
||||
}
|
||||
}
|
||||
|
||||
struct ail_tile ail_get_max_tile_size(unsigned blocksize_B);
|
||||
|
||||
void ail_make_miptree(struct ail_layout *layout);
|
||||
|
||||
void ail_detile(void *_tiled, void *_linear,
|
||||
const struct ail_layout *tiled_layout, unsigned level,
|
||||
unsigned linear_pitch_B, unsigned sx_px, unsigned sy_px,
|
||||
unsigned width_px, unsigned height_px);
|
||||
|
||||
void ail_tile(void *_tiled, void *_linear,
|
||||
const struct ail_layout *tiled_layout, unsigned level,
|
||||
unsigned linear_pitch_B, unsigned sx_px, unsigned sy_px,
|
||||
unsigned width_px, unsigned height_px);
|
||||
|
||||
/* Define aliases for the subset formats that are accessible in the ISA. These
|
||||
* subsets disregard component mapping and number of components. This
|
||||
* constitutes ABI with the compiler.
|
||||
*/
|
||||
enum ail_isa_format {
|
||||
AIL_ISA_FORMAT_I8 = PIPE_FORMAT_R8_UINT,
|
||||
AIL_ISA_FORMAT_I16 = PIPE_FORMAT_R16_UINT,
|
||||
AIL_ISA_FORMAT_I32 = PIPE_FORMAT_R32_UINT,
|
||||
AIL_ISA_FORMAT_F16 = PIPE_FORMAT_R16_FLOAT,
|
||||
AIL_ISA_FORMAT_U8NORM = PIPE_FORMAT_R8_UNORM,
|
||||
AIL_ISA_FORMAT_S8NORM = PIPE_FORMAT_R8_SNORM,
|
||||
AIL_ISA_FORMAT_U16NORM = PIPE_FORMAT_R16_UNORM,
|
||||
AIL_ISA_FORMAT_S16NORM = PIPE_FORMAT_R16_SNORM,
|
||||
AIL_ISA_FORMAT_RGB10A2 = PIPE_FORMAT_R10G10B10A2_UNORM,
|
||||
AIL_ISA_FORMAT_SRGBA8 = PIPE_FORMAT_R8G8B8A8_SRGB,
|
||||
AIL_ISA_FORMAT_RG11B10F = PIPE_FORMAT_R11G11B10_FLOAT,
|
||||
AIL_ISA_FORMAT_RGB9E5 = PIPE_FORMAT_R9G9B9E5_FLOAT
|
||||
};
|
||||
|
||||
/*
|
||||
* The architecture load/store instructions support masking, but packed formats
|
||||
* are not compatible with masking. Check if a format is packed.
|
||||
*/
|
||||
static inline bool
|
||||
ail_isa_format_supports_mask(enum ail_isa_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case AIL_ISA_FORMAT_RGB10A2:
|
||||
case AIL_ISA_FORMAT_RG11B10F:
|
||||
case AIL_ISA_FORMAT_RGB9E5:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
struct ail_pixel_format_entry {
|
||||
uint8_t channels;
|
||||
uint8_t type;
|
||||
bool texturable : 1;
|
||||
enum pipe_format renderable;
|
||||
};
|
||||
|
||||
extern const struct ail_pixel_format_entry ail_pixel_format[PIPE_FORMAT_COUNT];
|
||||
|
||||
static inline bool
|
||||
ail_is_valid_pixel_format(enum pipe_format format)
|
||||
{
|
||||
return ail_pixel_format[format].texturable;
|
||||
}
|
||||
|
||||
/* Query whether an image with the specified layout is compressible */
|
||||
static inline bool
|
||||
ail_can_compress(enum pipe_format format, unsigned w_px, unsigned h_px,
|
||||
unsigned sample_count_sa)
|
||||
{
|
||||
assert(sample_count_sa == 1 || sample_count_sa == 2 || sample_count_sa == 4);
|
||||
|
||||
/* We compress via the PBE, so we can only compress PBE-writeable formats. */
|
||||
if (ail_pixel_format[format].renderable == PIPE_FORMAT_NONE &&
|
||||
!util_format_is_depth_or_stencil(format))
|
||||
return false;
|
||||
|
||||
/* Lossy-compressed texture formats cannot be compressed */
|
||||
assert(!util_format_is_compressed(format) &&
|
||||
"block-compressed formats are not renderable");
|
||||
|
||||
/* Small textures cannot be compressed */
|
||||
return ail_effective_width_sa(w_px, sample_count_sa) >= 16 &&
|
||||
ail_effective_height_sa(h_px, sample_count_sa) >= 16;
|
||||
}
|
||||
|
||||
/* AGX compression mode for an uncompessed subtile. Frustratingly, this seems to
|
||||
* depend on the format. It is possible that modes are actual 8-bit structures
|
||||
* with multiple fields rather than plain enumerations.
|
||||
*/
|
||||
#define AIL_COMP_UNCOMPRESSED_1 0x1f
|
||||
#define AIL_COMP_UNCOMPRESSED_2 0x3f
|
||||
#define AIL_COMP_UNCOMPRESSED_4 0x7f
|
||||
#define AIL_COMP_UNCOMPRESSED_8_16 0xff
|
||||
|
||||
/* AGX compression mode for a solid colour for the subtile. */
|
||||
#define AIL_COMP_SOLID_1 0x60
|
||||
#define AIL_COMP_SOLID_2 0x01
|
||||
#define AIL_COMP_SOLID_4 0x03
|
||||
#define AIL_COMP_SOLID_8_16 0x07
|
||||
|
||||
static inline uint8_t
|
||||
ail_subtile_uncompressed_mode(enum pipe_format format)
|
||||
{
|
||||
/* clang-format off */
|
||||
switch (util_format_get_blocksize(format)) {
|
||||
case 1: return AIL_COMP_UNCOMPRESSED_1;
|
||||
case 2: return AIL_COMP_UNCOMPRESSED_2;
|
||||
case 4: return AIL_COMP_UNCOMPRESSED_4;
|
||||
case 8:
|
||||
case 16: return AIL_COMP_UNCOMPRESSED_8_16;
|
||||
default: UNREACHABLE("invalid block size");
|
||||
}
|
||||
/* clang-format on */
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
ail_subtile_solid_mode(enum pipe_format format)
|
||||
{
|
||||
/* clang-format off */
|
||||
switch (util_format_get_blocksize(format)) {
|
||||
case 1: return AIL_COMP_SOLID_1;
|
||||
case 2: return AIL_COMP_SOLID_2;
|
||||
case 4: return AIL_COMP_SOLID_4;
|
||||
case 8:
|
||||
case 16: return AIL_COMP_SOLID_8_16;
|
||||
default: UNREACHABLE("invalid block size");
|
||||
}
|
||||
/* clang-format on */
|
||||
}
|
||||
|
||||
/*
|
||||
* Compression modes are 8-bit per 8x4 subtile, but grouped into 64-bit for all
|
||||
* modes in a 16x16 tile. This helper replicates a subtile mode to a tile mode
|
||||
* using a SWAR idiom.
|
||||
*/
|
||||
static inline uint64_t
|
||||
ail_tile_mode_replicated(uint8_t subtile_mode)
|
||||
{
|
||||
return (uint64_t)subtile_mode * 0x0101010101010101ULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Composed convenience function.
|
||||
*/
|
||||
static inline uint64_t
|
||||
ail_tile_mode_uncompressed(enum pipe_format format)
|
||||
{
|
||||
return ail_tile_mode_replicated(ail_subtile_uncompressed_mode(format));
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
ail_tile_mode_solid(enum pipe_format format)
|
||||
{
|
||||
return ail_tile_mode_replicated(ail_subtile_solid_mode(format));
|
||||
}
|
||||
|
||||
/*
|
||||
* For compression, compatible formats must have the same number/size/order of
|
||||
* channels, but may differ in data type. For example, R32_SINT is compatible
|
||||
* with Z32_FLOAT, but not with R16G16_SINT. This is the relation given by the
|
||||
* "channels" part of the decomposed format.
|
||||
*
|
||||
* This has not been exhaustively tested and might be missing some corner cases
|
||||
* around XR formats, but is well-motivated and seems to work.
|
||||
*/
|
||||
static inline bool
|
||||
ail_formats_compatible(enum pipe_format a, enum pipe_format b)
|
||||
{
|
||||
return ail_pixel_format[a].channels == ail_pixel_format[b].channels;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ail_is_view_compatible(struct ail_layout *layout, enum pipe_format view)
|
||||
{
|
||||
return !layout->compressed || ail_formats_compatible(layout->format, view);
|
||||
}
|
||||
|
||||
/*
|
||||
* We generally use ail enums instead of DRM format modifiers. These helpers
|
||||
* bridges the gap.
|
||||
*/
|
||||
static inline enum ail_tiling
|
||||
ail_drm_modifier_to_tiling(uint64_t modifier)
|
||||
{
|
||||
switch (modifier) {
|
||||
case DRM_FORMAT_MOD_LINEAR:
|
||||
return AIL_TILING_LINEAR;
|
||||
case DRM_FORMAT_MOD_APPLE_GPU_TILED:
|
||||
case DRM_FORMAT_MOD_APPLE_GPU_TILED_COMPRESSED:
|
||||
return AIL_TILING_GPU;
|
||||
default:
|
||||
UNREACHABLE("Unsupported modifier");
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ail_is_drm_modifier_compressed(uint64_t modifier)
|
||||
{
|
||||
switch (modifier) {
|
||||
case DRM_FORMAT_MOD_LINEAR:
|
||||
case DRM_FORMAT_MOD_APPLE_GPU_TILED:
|
||||
return false;
|
||||
case DRM_FORMAT_MOD_APPLE_GPU_TILED_COMPRESSED:
|
||||
return true;
|
||||
default:
|
||||
UNREACHABLE("Unsupported modifier");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constants for sparse page tables. See docs/drivers/asahi.rst.
|
||||
*/
|
||||
#define AIL_SPARSE_ELSIZE_B 4
|
||||
#define AIL_PAGES_PER_FOLIO 256
|
||||
#define AIL_IMAGE_SIZE_PER_FOLIO_B (AIL_PAGESIZE * AIL_PAGES_PER_FOLIO)
|
||||
#define AIL_FOLIO_SIZE_EL (AIL_PAGES_PER_FOLIO * 2)
|
||||
#define AIL_FOLIO_SIZE_B (AIL_FOLIO_SIZE_EL * AIL_SPARSE_ELSIZE_B)
|
||||
|
||||
static inline unsigned
|
||||
ail_bytes_to_pages(unsigned x_B)
|
||||
{
|
||||
assert((x_B % AIL_PAGESIZE) == 0);
|
||||
return x_B / AIL_PAGESIZE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Map a logical page of the image (i.e. page = logical address / page_size) to
|
||||
* a word-index into the sparse table.
|
||||
*/
|
||||
static inline unsigned
|
||||
ail_page_to_sparse_index_el(const struct ail_layout *layout, unsigned z,
|
||||
unsigned page)
|
||||
{
|
||||
unsigned z_base = z * layout->sparse_folios_per_layer;
|
||||
unsigned folio = page / AIL_PAGES_PER_FOLIO;
|
||||
unsigned index_in_folio = page % AIL_PAGES_PER_FOLIO;
|
||||
unsigned idx = ((z_base + folio) * AIL_FOLIO_SIZE_EL) + index_in_folio;
|
||||
|
||||
assert((idx * AIL_SPARSE_ELSIZE_B) < layout->sparse_table_size_B);
|
||||
return idx;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern C */
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright 2022 Alyssa Rosenzweig
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
libasahi_layout_files = files(
|
||||
'formats.c',
|
||||
'layout.c',
|
||||
'tiling.cc',
|
||||
)
|
||||
|
||||
libasahi_layout = static_library(
|
||||
'asahi_layout',
|
||||
[libasahi_layout_files],
|
||||
include_directories : [inc_include, inc_src],
|
||||
dependencies: [idep_mesautil, idep_agx_pack],
|
||||
c_args : [no_override_init_args],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
build_by_default : false,
|
||||
)
|
||||
|
||||
if with_tests
|
||||
test(
|
||||
'asahi_layout_tests',
|
||||
executable(
|
||||
'asahi_layout_tests',
|
||||
files(
|
||||
'tests/test-layout.cpp',
|
||||
'tests/test-generated.cpp',
|
||||
'tests/test-twiddling.cpp',
|
||||
),
|
||||
c_args : [c_msvc_compat_args, no_override_init_args],
|
||||
gnu_symbol_visibility : 'hidden',
|
||||
dependencies: [idep_gtest, idep_mesautil],
|
||||
link_with : [libasahi_layout],
|
||||
),
|
||||
suite : ['asahi'],
|
||||
protocol : 'gtest',
|
||||
)
|
||||
endif
|
||||
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
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* Copyright 2022 Asahi Lina
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "layout.h"
|
||||
|
||||
/*
|
||||
* Test texture layouts with test cases extracted from texture structure dumps
|
||||
* produced by Metal.
|
||||
*
|
||||
* The extracted test cases are stored in separate files which do not get
|
||||
* clang-formatted. They do still get parsed as C code, though. They may be
|
||||
* sorted with `sort -t"," -k1,1 -k2,5n | uniq`.
|
||||
*/
|
||||
struct sizetest {
|
||||
enum pipe_format format;
|
||||
uint32_t width, height, depth;
|
||||
uint8_t levels;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct miptest {
|
||||
enum pipe_format format;
|
||||
uint32_t width, height;
|
||||
uint8_t levels;
|
||||
uint32_t offsets[16];
|
||||
};
|
||||
|
||||
struct msaatest {
|
||||
enum pipe_format format;
|
||||
uint32_t width, height, depth;
|
||||
uint8_t levels;
|
||||
uint8_t samples;
|
||||
bool is_compressed;
|
||||
uint32_t meta_offset;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
static struct sizetest comptests[] = {
|
||||
#include "comp-twiddled.txt"
|
||||
};
|
||||
|
||||
static struct sizetest sizetests[] = {
|
||||
#include "uncomp-twiddled.txt"
|
||||
};
|
||||
|
||||
static struct miptest miptests[] = {
|
||||
#include "miptree.txt"
|
||||
};
|
||||
|
||||
static struct msaatest msaatests[] = {
|
||||
#include "msaa.txt"
|
||||
};
|
||||
|
||||
TEST(Generated, CompTwiddled)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(comptests); ++i) {
|
||||
struct sizetest test = comptests[i];
|
||||
|
||||
struct ail_layout layout = {
|
||||
.width_px = test.width,
|
||||
.height_px = test.height,
|
||||
.depth_px = test.depth,
|
||||
.sample_count_sa = 1,
|
||||
.levels = test.levels,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.compressed = true,
|
||||
.format = test.format,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, test.size)
|
||||
<< test.width << "x" << test.height << "x" << test.depth << " "
|
||||
<< (int)test.levels << "L " << util_format_short_name(test.format)
|
||||
<< " compressed texture has wrong allocation size, off by "
|
||||
<< ((int)layout.size_B - (int)test.size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Generated, UncompTwiddled)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(sizetests); ++i) {
|
||||
struct sizetest test = sizetests[i];
|
||||
|
||||
struct ail_layout layout = {
|
||||
.width_px = test.width,
|
||||
.height_px = test.height,
|
||||
.depth_px = test.depth,
|
||||
.sample_count_sa = 1,
|
||||
.levels = test.levels,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = test.format,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, test.size)
|
||||
<< test.width << "x" << test.height << "x" << test.depth << " "
|
||||
<< (int)test.levels << "L " << util_format_short_name(test.format)
|
||||
<< " uncompressed texture has wrong allocation size, off by "
|
||||
<< ((int)layout.size_B - (int)test.size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Generated, Miptree2D)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(miptests); ++i) {
|
||||
struct miptest test = miptests[i];
|
||||
|
||||
struct ail_layout layout = {
|
||||
.width_px = test.width,
|
||||
.height_px = test.height,
|
||||
.depth_px = 1,
|
||||
.sample_count_sa = 1,
|
||||
.levels = test.levels,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = test.format,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
for (unsigned l = 0; l < test.levels; ++l) {
|
||||
EXPECT_EQ(ail_get_level_offset_B(&layout, l), test.offsets[l])
|
||||
<< test.width << "x" << test.height << " "
|
||||
<< util_format_short_name(test.format)
|
||||
<< " texture has wrong offset at level " << l << ", off by "
|
||||
<< test.offsets[l] - ail_get_level_offset_B(&layout, l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Generated, MSAA)
|
||||
{
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(msaatests); ++i) {
|
||||
struct msaatest test = msaatests[i];
|
||||
|
||||
struct ail_layout layout = {
|
||||
.width_px = test.width,
|
||||
.height_px = test.height,
|
||||
.depth_px = test.depth,
|
||||
.sample_count_sa = test.samples,
|
||||
.levels = test.levels,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.compressed = test.is_compressed,
|
||||
.format = test.format,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, test.size)
|
||||
<< test.width << "x" << test.height << "x" << test.depth << " "
|
||||
<< (int)test.levels << "L " << (int)test.samples << "S "
|
||||
<< util_format_short_name(test.format)
|
||||
<< (test.is_compressed ? " " : " un")
|
||||
<< "compressed texture has wrong allocation size, off by "
|
||||
<< ((int)layout.size_B - (int)test.size);
|
||||
|
||||
if (test.is_compressed) {
|
||||
EXPECT_EQ(layout.metadata_offset_B, test.meta_offset)
|
||||
<< test.width << "x" << test.height << "x" << test.depth << " "
|
||||
<< (int)test.levels << "L " << (int)test.samples << "S "
|
||||
<< util_format_short_name(test.format)
|
||||
<< "compressed texture has wrong metadata offset size, off by "
|
||||
<< ((int)layout.metadata_offset_B - (int)test.meta_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2022 Alyssa Rosenzweig
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "layout.h"
|
||||
|
||||
TEST(Cubemap, Nonmipmapped)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 512,
|
||||
.height_px = 512,
|
||||
.depth_px = 6,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 1,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R8G8B8A8_UNORM,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.layer_stride_B, ALIGN_POT(512 * 512 * 4, 0x4000));
|
||||
EXPECT_EQ(layout.size_B, ALIGN_POT(512 * 512 * 4 * 6, 0x4000));
|
||||
}
|
||||
|
||||
TEST(Cubemap, RoundsToOnePage)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 63,
|
||||
.height_px = 63,
|
||||
.depth_px = 6,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 6,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R32_FLOAT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.level_offsets_B[0], 0);
|
||||
EXPECT_EQ(layout.level_offsets_B[1], 0x4000);
|
||||
EXPECT_EQ(layout.level_offsets_B[2], 0x5000);
|
||||
EXPECT_EQ(layout.level_offsets_B[3], 0x5400);
|
||||
EXPECT_EQ(layout.level_offsets_B[4], 0x5500);
|
||||
EXPECT_TRUE(layout.page_aligned_layers);
|
||||
EXPECT_EQ(layout.layer_stride_B, 0x8000);
|
||||
EXPECT_EQ(layout.size_B, 0x30000);
|
||||
}
|
||||
|
||||
TEST(Linear, SmokeTestBuffer)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 81946,
|
||||
.height_px = 1,
|
||||
.depth_px = 1,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 1,
|
||||
.tiling = AIL_TILING_LINEAR,
|
||||
.format = PIPE_FORMAT_R8_UINT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, ALIGN_POT(81946, AIL_CACHELINE));
|
||||
}
|
||||
|
||||
TEST(Miptree, AllMipLevels)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 1024,
|
||||
.height_px = 1024,
|
||||
.depth_px = 1,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 11,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R8G8B8A8_UINT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, 0x555680);
|
||||
EXPECT_EQ(layout.sparse_folios_per_layer, 2);
|
||||
EXPECT_EQ(layout.sparse_table_size_B, 0x1000);
|
||||
}
|
||||
|
||||
TEST(Miptree, SomeMipLevels)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 1024,
|
||||
.height_px = 1024,
|
||||
.depth_px = 1,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 4,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R8G8B8A8_UINT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.size_B, 0x555680);
|
||||
EXPECT_EQ(layout.sparse_folios_per_layer, 2);
|
||||
EXPECT_EQ(layout.sparse_table_size_B, 0x1000);
|
||||
}
|
||||
|
||||
TEST(Miptree, SmallPartialMiptree2DArray)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 32,
|
||||
.height_px = 16,
|
||||
.depth_px = 64,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 4,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R32_FLOAT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.layer_stride_B, 0xc00);
|
||||
EXPECT_EQ(layout.size_B, 0x30000);
|
||||
EXPECT_EQ(layout.sparse_folios_per_layer, 1);
|
||||
EXPECT_EQ(layout.sparse_table_size_B, 0x20000);
|
||||
}
|
||||
|
||||
TEST(Miptree, SmallPartialMiptree3D)
|
||||
{
|
||||
struct ail_layout layout = {
|
||||
.width_px = 32,
|
||||
.height_px = 16,
|
||||
.depth_px = 64,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 4,
|
||||
.mipmapped_z = true,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = PIPE_FORMAT_R32_FLOAT,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
EXPECT_EQ(layout.layer_stride_B, 0xc80);
|
||||
EXPECT_EQ(layout.size_B, 0x32000);
|
||||
EXPECT_EQ(layout.sparse_folios_per_layer, 1);
|
||||
EXPECT_EQ(layout.sparse_table_size_B, 0x20000);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright 2024 Alyssa Rosenzweig
|
||||
* Copyright 2022 Collabora, Ltd.
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "layout.h"
|
||||
|
||||
#include "util/format/u_format.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
/*
|
||||
* Reference tiling algorithm, written for clarity rather than performance. See
|
||||
* docs/drivers/asahi.rst for details on the format.
|
||||
*/
|
||||
|
||||
static unsigned
|
||||
z_order(unsigned x, unsigned y)
|
||||
{
|
||||
unsigned out = 0;
|
||||
|
||||
for (unsigned i = 0; i < 8; ++i) {
|
||||
unsigned bit = (1 << (2 * i));
|
||||
|
||||
if (x & (1 << i))
|
||||
out |= bit;
|
||||
|
||||
if (y & (1 << i))
|
||||
out |= bit << 1;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* x/y are in blocks */
|
||||
static unsigned
|
||||
tiled_offset_el(struct ail_layout *layout, unsigned level, unsigned x_el,
|
||||
unsigned y_el)
|
||||
{
|
||||
unsigned x_tl = x_el / layout->tilesize_el[level].width_el;
|
||||
unsigned y_tl = y_el / layout->tilesize_el[level].height_el;
|
||||
|
||||
unsigned offs_x_el = x_el % layout->tilesize_el[level].width_el;
|
||||
unsigned offs_y_el = y_el % layout->tilesize_el[level].height_el;
|
||||
|
||||
unsigned offs_in_tile_el = z_order(offs_x_el, offs_y_el);
|
||||
|
||||
unsigned offs_row_el =
|
||||
y_tl *
|
||||
align(layout->stride_el[level], layout->tilesize_el[level].width_el) *
|
||||
layout->tilesize_el[level].height_el;
|
||||
|
||||
unsigned offs_col_el = x_tl * layout->tilesize_el[level].width_el *
|
||||
layout->tilesize_el[level].height_el;
|
||||
|
||||
return offs_row_el + offs_col_el + offs_in_tile_el;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
linear_offset_B(unsigned x_el, unsigned y_el, unsigned stride_B,
|
||||
unsigned blocksize_B)
|
||||
{
|
||||
return (stride_B * y_el) + (x_el * blocksize_B);
|
||||
}
|
||||
|
||||
static void
|
||||
ref_access_tiled(uint8_t *tiled, uint8_t *linear, struct ail_layout *layout,
|
||||
unsigned region_x_px, unsigned region_y_px, unsigned w_px,
|
||||
unsigned h_px, uint32_t linear_stride_B, bool dst_is_tiled)
|
||||
{
|
||||
unsigned blocksize_B = util_format_get_blocksize(layout->format);
|
||||
|
||||
unsigned w_el = util_format_get_nblocksx(layout->format, w_px);
|
||||
unsigned h_el = util_format_get_nblocksy(layout->format, h_px);
|
||||
|
||||
unsigned region_x_el = util_format_get_nblocksx(layout->format, region_x_px);
|
||||
unsigned region_y_el = util_format_get_nblocksy(layout->format, region_y_px);
|
||||
|
||||
for (unsigned linear_y_el = 0; linear_y_el < h_el; ++linear_y_el) {
|
||||
for (unsigned linear_x_el = 0; linear_x_el < w_el; ++linear_x_el) {
|
||||
unsigned tiled_x_el = region_x_el + linear_x_el;
|
||||
unsigned tiled_y_el = region_y_el + linear_y_el;
|
||||
|
||||
uint8_t *linear_ptr =
|
||||
linear + linear_offset_B(linear_x_el, linear_y_el, linear_stride_B,
|
||||
blocksize_B);
|
||||
|
||||
unsigned twiddled_offs_B =
|
||||
blocksize_B * tiled_offset_el(layout, 0, tiled_x_el, tiled_y_el);
|
||||
|
||||
/* While we're at it, check that ail_get_twiddled_block_B does the
|
||||
* right thing where it is defined (at tile boundaries). We can't
|
||||
* otherwise use it here as it is part of the routine under test.
|
||||
*/
|
||||
if ((tiled_x_el % layout->tilesize_el[0].width_el) == 0 &&
|
||||
(tiled_y_el % layout->tilesize_el[0].height_el) == 0) {
|
||||
|
||||
unsigned tiled_x_px =
|
||||
tiled_x_el * util_format_get_blockwidth(layout->format);
|
||||
|
||||
unsigned tiled_y_px =
|
||||
tiled_y_el * util_format_get_blockwidth(layout->format);
|
||||
|
||||
EXPECT_EQ(
|
||||
ail_get_twiddled_block_B(layout, 0, tiled_x_px, tiled_y_px, 0),
|
||||
twiddled_offs_B);
|
||||
}
|
||||
|
||||
uint8_t *tiled_ptr = tiled + twiddled_offs_B;
|
||||
|
||||
if (dst_is_tiled) {
|
||||
memcpy(tiled_ptr, linear_ptr, blocksize_B);
|
||||
} else {
|
||||
memcpy(linear_ptr, tiled_ptr, blocksize_B);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper to build test cases for tiled texture access. This test suite compares
|
||||
* the above reference tiling algorithm to the optimized algorithm used in
|
||||
* production.
|
||||
*/
|
||||
static void
|
||||
test(unsigned width, unsigned height, unsigned rx, unsigned ry, unsigned rw,
|
||||
unsigned rh, unsigned linear_stride, enum pipe_format format, bool store)
|
||||
{
|
||||
unsigned bpp = util_format_get_blocksize(format);
|
||||
struct ail_layout layout = {
|
||||
.width_px = width,
|
||||
.height_px = height,
|
||||
.depth_px = 1,
|
||||
.sample_count_sa = 1,
|
||||
.levels = 1,
|
||||
.tiling = AIL_TILING_GPU,
|
||||
.format = format,
|
||||
};
|
||||
|
||||
ail_make_miptree(&layout);
|
||||
|
||||
uint8_t *tiled = (uint8_t *)calloc(bpp, layout.size_B);
|
||||
uint8_t *linear = (uint8_t *)calloc(bpp, rh * linear_stride);
|
||||
uint8_t *ref =
|
||||
(uint8_t *)calloc(bpp, store ? layout.size_B : (rh * linear_stride));
|
||||
|
||||
if (store) {
|
||||
for (unsigned i = 0; i < bpp * rh * linear_stride; ++i) {
|
||||
linear[i] = (i & 0xFF);
|
||||
}
|
||||
|
||||
ail_tile(tiled, linear, &layout, 0, linear_stride, rx, ry, rw, rh);
|
||||
ref_access_tiled(ref, linear, &layout, rx, ry, rw, rh, linear_stride,
|
||||
true);
|
||||
|
||||
EXPECT_EQ(memcmp(ref, tiled, layout.size_B), 0);
|
||||
} else {
|
||||
for (unsigned i = 0; i < layout.size_B; ++i) {
|
||||
tiled[i] = (i & 0xFF);
|
||||
}
|
||||
|
||||
ail_detile(tiled, linear, &layout, 0, linear_stride, rx, ry, rw, rh);
|
||||
ref_access_tiled(tiled, ref, &layout, rx, ry, rw, rh, linear_stride,
|
||||
false);
|
||||
|
||||
EXPECT_EQ(memcmp(ref, linear, bpp * rh * linear_stride), 0);
|
||||
}
|
||||
|
||||
free(ref);
|
||||
free(tiled);
|
||||
free(linear);
|
||||
}
|
||||
|
||||
static void
|
||||
test_ldst(unsigned width, unsigned height, unsigned rx, unsigned ry,
|
||||
unsigned rw, unsigned rh, unsigned linear_stride,
|
||||
enum pipe_format format)
|
||||
{
|
||||
test(width, height, rx, ry, rw, rh, linear_stride, format, true);
|
||||
test(width, height, rx, ry, rw, rh, linear_stride, format, false);
|
||||
}
|
||||
|
||||
TEST(Twiddling, RegularFormats)
|
||||
{
|
||||
test_ldst(233, 173, 0, 0, 233, 173, 233, PIPE_FORMAT_R8_UINT);
|
||||
test_ldst(233, 173, 0, 0, 233, 173, 233 * 2, PIPE_FORMAT_R8G8_UINT);
|
||||
test_ldst(233, 173, 0, 0, 233, 173, 233 * 4, PIPE_FORMAT_R32_UINT);
|
||||
test_ldst(173, 143, 0, 0, 173, 143, 173 * 8, PIPE_FORMAT_R32G32_UINT);
|
||||
test_ldst(133, 143, 0, 0, 133, 143, 133 * 16, PIPE_FORMAT_R32G32B32A32_UINT);
|
||||
}
|
||||
|
||||
TEST(Twiddling, UnpackedStrides)
|
||||
{
|
||||
test_ldst(213, 17, 0, 0, 213, 17, 369 * 1, PIPE_FORMAT_R8_SINT);
|
||||
test_ldst(213, 17, 0, 0, 213, 17, 369 * 2, PIPE_FORMAT_R8G8_SINT);
|
||||
test_ldst(213, 17, 0, 0, 213, 17, 369 * 4, PIPE_FORMAT_R32_SINT);
|
||||
test_ldst(213, 17, 0, 0, 213, 17, 369 * 8, PIPE_FORMAT_R32G32_SINT);
|
||||
test_ldst(213, 17, 0, 0, 213, 17, 369 * 16, PIPE_FORMAT_R32G32B32A32_SINT);
|
||||
}
|
||||
|
||||
TEST(Twiddling, PartialAccess)
|
||||
{
|
||||
test_ldst(283, 171, 3, 1, 131, 7, 369 * 1, PIPE_FORMAT_R8_UNORM);
|
||||
test_ldst(283, 171, 3, 1, 131, 7, 369 * 2, PIPE_FORMAT_R8G8_UNORM);
|
||||
test_ldst(283, 171, 3, 1, 131, 7, 369 * 4, PIPE_FORMAT_R32_UNORM);
|
||||
test_ldst(283, 171, 3, 1, 131, 7, 369 * 8, PIPE_FORMAT_R32G32_UNORM);
|
||||
test_ldst(283, 171, 3, 1, 131, 7, 369 * 16, PIPE_FORMAT_R32G32B32A32_UNORM);
|
||||
}
|
||||
|
||||
TEST(Twiddling, ETC)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_ETC1_RGB8);
|
||||
test_ldst(32, 256, 0, 0, 32, 256, 512, PIPE_FORMAT_ETC2_RGB8A1);
|
||||
test_ldst(32, 512, 0, 0, 32, 512, 512, PIPE_FORMAT_ETC2_RG11_SNORM);
|
||||
}
|
||||
|
||||
TEST(Twiddling, PartialETC)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC1_RGB8);
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC2_RGB8A1);
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_ETC2_RG11_SNORM);
|
||||
}
|
||||
|
||||
TEST(Twiddling, DXT)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT1_RGB);
|
||||
test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT3_RGBA);
|
||||
test_ldst(32, 32, 0, 0, 32, 32, 512, PIPE_FORMAT_DXT5_RGBA);
|
||||
}
|
||||
|
||||
TEST(Twiddling, PartialDXT)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT1_RGB);
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT3_RGBA);
|
||||
test_ldst(32, 32, 4, 8, 16, 12, 512, PIPE_FORMAT_DXT5_RGBA);
|
||||
}
|
||||
|
||||
TEST(Twiddling, ASTC)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(40, 40, 0, 0, 40, 40, 512, PIPE_FORMAT_ASTC_4x4);
|
||||
test_ldst(50, 40, 0, 0, 50, 40, 512, PIPE_FORMAT_ASTC_5x4);
|
||||
test_ldst(50, 50, 0, 0, 50, 50, 512, PIPE_FORMAT_ASTC_5x5);
|
||||
}
|
||||
|
||||
TEST(Twiddling, PartialASTC)
|
||||
{
|
||||
/* Block alignment assumed */
|
||||
test_ldst(40, 40, 4, 4, 16, 8, 512, PIPE_FORMAT_ASTC_4x4);
|
||||
test_ldst(50, 40, 5, 4, 10, 8, 512, PIPE_FORMAT_ASTC_5x4);
|
||||
test_ldst(50, 50, 5, 5, 10, 10, 512, PIPE_FORMAT_ASTC_5x5);
|
||||
}
|
||||
Loaded 100 of 12511 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user