agx: handle non-immediate shuffles in divergent CF

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/29179>
This commit is contained in:
Alyssa Rosenzweig
2024-04-19 22:00:15 -04:00
committed by Marge Bot
parent 589c69a646
commit 7b33c549b9
5 changed files with 112 additions and 9 deletions
+7
View File
@@ -1488,6 +1488,9 @@ agx_emit_intrinsic(agx_builder *b, nir_intrinsic_instr *instr)
}
case nir_intrinsic_read_invocation: {
/* TODO: Check if we're actually inside divergent control flow */
b->shader->any_quad_divergent_shuffle |= b->shader->any_cf;
/* Lane ID guaranteed to be uniform */
return agx_shuffle_to(b, dst, agx_src_index(&instr->src[0]),
agx_src_index(&instr->src[1]));
@@ -3096,6 +3099,10 @@ agx_compile_function_nir(nir_shader *nir, nir_function_impl *impl,
agx_opt_empty_else(ctx);
agx_opt_break_if(ctx);
agx_opt_jmp_none(ctx);
if (ctx->any_quad_divergent_shuffle)
agx_lower_divergent_shuffle(ctx);
agx_lower_pseudo(ctx);
if (agx_should_dump(nir, AGX_DBG_SHADERS))
+6
View File
@@ -455,6 +455,11 @@ typedef struct {
/* Has r0l been zeroed yet due to control flow? */
bool any_cf;
/* Do we need r0h zero throughout the program to handle quad-divergent
* shuffle?
*/
bool any_quad_divergent_shuffle;
/* Number of nested control flow structures within the innermost loop. Since
* NIR is just loop and if-else, this is the number of nested if-else
* statements in the loop */
@@ -946,6 +951,7 @@ void agx_print_instr(const agx_instr *I, FILE *fp);
void agx_print_block(const agx_block *block, FILE *fp);
void agx_print_shader(const agx_context *ctx, FILE *fp);
void agx_optimizer(agx_context *ctx);
void agx_lower_divergent_shuffle(agx_context *ctx);
void agx_lower_pseudo(agx_context *ctx);
void agx_lower_spill(agx_context *ctx);
void agx_lower_uniform_sources(agx_context *ctx);
@@ -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);
}
}
}
+27 -9
View File
@@ -177,6 +177,9 @@ agx_calc_register_demand(agx_context *ctx)
if (ctx->any_cf)
demand++;
if (ctx->any_quad_divergent_shuffle)
demand++;
/* Everything live-in */
{
int i;
@@ -299,16 +302,23 @@ find_best_region_to_evict(struct ra_ctx *rctx, enum ra_class cls, unsigned size,
unsigned best_base = ~0;
unsigned best_moves = ~0;
/* r0h unevictable only when r0l unevictable */
assert(!rctx->shader->any_quad_divergent_shuffle || rctx->shader->any_cf);
for (unsigned base = 0; base + size <= rctx->bound[cls]; base += size) {
/* r0l is unevictable, skip it. By itself, this does not pose a problem.
* We are allocating n registers, but the region containing r0l has at
* most n-1 free. Since there are at least n free registers total, there
* is at least 1 free register outside this region. Thus the region
* containing that free register contains at most n-1 occupied registers.
* In the worst case, those n-1 occupied registers are moved to the region
* with r0l and then the n free registers are used for the destination.
* Thus, we do not need extra registers to handle "single point"
* unevictability.
/* The first k registers are preallocated and unevictable, so must be
* skipped. By itself, this does not pose a problem. We are allocating n
* registers, but this region has at most n-k free. Since there are at
* least n free registers total, there is at least k free registers
* outside this region. Choose any such free register. The region
* containing it has at most n-1 occupied registers. In the worst case,
* n-k of those registers are are moved to the beginning region and the
* remaining (n-1)-(n-k) = k-1 registers are moved to the k-1 free
* registers in other regions, given there are k free registers total.
* These recursive shuffles work out because everything is power-of-two
* sized and naturally aligned, so the sizes shuffled are strictly
* descending. So, we do not need extra registers to handle "single
* region" unevictability.
*/
if (base == 0 && rctx->shader->any_cf)
continue;
@@ -509,6 +519,8 @@ insert_copies_for_clobbered_killed(struct ra_ctx *rctx, unsigned reg,
* have to move it. find_best_region_to_evict knows better than to try.
*/
assert(!(reg == 0 && rctx->shader->any_cf) && "r0l is never moved");
assert(!(reg == 1 && rctx->shader->any_quad_divergent_shuffle) &&
"r0h is never moved");
/* Consider the destination clobbered for the purpose of source collection.
* This way, killed sources already in the destination will be preserved
@@ -1069,6 +1081,12 @@ agx_ra_assign_local(struct ra_ctx *rctx)
if (rctx->shader->any_cf)
BITSET_SET(used_regs_gpr, 0);
/* Force the zero r0h live throughout shaders using divergent shuffles. */
if (rctx->shader->any_quad_divergent_shuffle) {
assert(rctx->shader->any_cf);
BITSET_SET(used_regs_gpr, 1);
}
agx_foreach_instr_in_block(block, I) {
rctx->instr = I;
+1
View File
@@ -17,6 +17,7 @@ libasahi_agx_files = files(
'agx_nir_lower_subgroups.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',