aco/assembler: chain branches instead of emitting long jumps

As regular branch instructions cannot jump further than
32768 dwords, previously we used long jumps as fallback
solution. The disadvantage of that is that an extra SGPR
pair must be provided in order to temporarily store the PC.

This patch changes that to chained branch instructions by
inserting an artificial extra block into the code to be
targeted by the original branch. This block contains a
single branch instruction jumping to the original target.
Before the block, if necessary, we insert a <branch 1>
instruction for the existing code in order to jump over
the newly inserted block.

Only a few RT shaders are affected.

Totals from 29 (0.04% of 79395) affected shaders: (Navi31)

CodeSize: 17281176 -> 17276332 (-0.03%)
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32037>
This commit is contained in:
Daniel Schürmann
2024-11-06 14:56:53 +01:00
committed by Marge Bot
parent c3d777d8ac
commit cab5639a09
2 changed files with 150 additions and 176 deletions
+112 -105
View File
@@ -13,6 +13,7 @@
#include "ac_shader_util.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include <vector>
@@ -23,10 +24,15 @@ struct constaddr_info {
unsigned add_literal;
};
struct branch_info {
unsigned pos;
unsigned target;
};
struct asm_context {
Program* program;
enum amd_gfx_level gfx_level;
std::vector<std::pair<int, SALU_instruction*>> branches;
std::vector<branch_info> branches;
std::map<unsigned, constaddr_info> constaddrs;
std::map<unsigned, constaddr_info> resumeaddrs;
std::vector<struct aco_symbol>* symbols;
@@ -202,18 +208,17 @@ emit_sopc_instruction(asm_context& ctx, std::vector<uint32_t>& out, const Instru
}
void
emit_sopp_instruction(asm_context& ctx, std::vector<uint32_t>& out, Instruction* instr,
emit_sopp_instruction(asm_context& ctx, std::vector<uint32_t>& out, const Instruction* instr,
bool force_imm = false)
{
uint32_t opcode = ctx.opcode[(int)instr->opcode];
SALU_instruction& sopp = instr->salu();
const SALU_instruction& sopp = instr->salu();
uint32_t encoding = (0b101111111 << 23);
encoding |= opcode << 16;
if (!force_imm && instr_info.classes[(int)instr->opcode] == instr_class::branch) {
sopp.pass_flags = 0;
ctx.branches.emplace_back(out.size(), &sopp);
ctx.branches.push_back({(unsigned)out.size(), sopp.imm});
} else {
assert(sopp.imm <= UINT16_MAX);
encoding |= (uint16_t)sopp.imm;
@@ -1465,14 +1470,11 @@ insert_code(asm_context& ctx, std::vector<uint32_t>& out, unsigned insert_before
block.offset += insert_count;
}
/* Find first branch after the inserted code */
auto branch_it = std::find_if(ctx.branches.begin(), ctx.branches.end(),
[insert_before](const auto& branch) -> bool
{ return (unsigned)branch.first >= insert_before; });
/* Update the locations of branches */
for (; branch_it != ctx.branches.end(); ++branch_it)
branch_it->first += insert_count;
for (branch_info& info : ctx.branches) {
if (info.pos >= insert_before)
info.pos += insert_count;
}
/* Update the locations of p_constaddr instructions */
for (auto& constaddr : ctx.constaddrs) {
@@ -1508,101 +1510,120 @@ fix_branches_gfx10(asm_context& ctx, std::vector<uint32_t>& out)
do {
auto buggy_branch_it = std::find_if(
ctx.branches.begin(), ctx.branches.end(),
[&ctx](const auto& branch) -> bool {
return ((int)ctx.program->blocks[branch.second->imm].offset - branch.first - 1) == 0x3f;
});
ctx.branches.begin(), ctx.branches.end(), [&](const branch_info& branch) -> bool
{ return ((int)ctx.program->blocks[branch.target].offset - branch.pos - 1) == 0x3f; });
gfx10_3f_bug = buggy_branch_it != ctx.branches.end();
if (gfx10_3f_bug) {
/* Insert an s_nop after the branch */
constexpr uint32_t s_nop_0 = 0xbf800000u;
insert_code(ctx, out, buggy_branch_it->first + 1, 1, &s_nop_0);
insert_code(ctx, out, buggy_branch_it->pos + 1, 1, &s_nop_0);
}
} while (gfx10_3f_bug);
}
void
emit_long_jump(asm_context& ctx, SALU_instruction* branch, bool backwards,
std::vector<uint32_t>& out)
chain_branches(asm_context& ctx, std::vector<uint32_t>& out, branch_info& branch)
{
/* Create an empty block in order to remember the offset of the chained branch instruction.
* The new branch instructions are inserted into the program in source code order.
*/
Block* new_block = ctx.program->create_and_insert_block();
Builder bld(ctx.program);
std::vector<uint32_t> code;
Instruction* branch_instr;
auto emit = [&](Instruction *instr_ptr, uint32_t *size=NULL) {
aco_ptr<Instruction> instr(instr_ptr);
emit_instruction(ctx, out, instr.get());
/* Re-direct original branch to new block (offset). */
unsigned target = branch.target;
branch.target = new_block->index;
if (size)
*size = out.size();
/* Find suitable insertion point:
* We define two offset ranges within our new branch instruction should be placed.
* Then we try to maximize the distance from either the previous branch or the target.
*/
const int half_dist = (INT16_MAX - 31) / 2;
const unsigned upper_start = MIN2(ctx.program->blocks[target].offset, branch.pos) + half_dist;
const unsigned upper_end = upper_start + half_dist;
const unsigned lower_end = MAX2(ctx.program->blocks[target].offset, branch.pos) - half_dist;
const unsigned lower_start = lower_end - half_dist;
unsigned insert_at = 0;
for (unsigned i = 0; i < ctx.program->blocks.size() - 1; i++) {
Block& block = ctx.program->blocks[i];
Block& next = ctx.program->blocks[i + 1];
if (next.offset >= lower_end)
break;
if (next.offset < upper_start || (next.offset > upper_end && next.offset < lower_start))
continue;
/* VALUMaskWriteHazard and VALUReadSGPRHazard needs sa_sdst=0 wait */
if (ctx.gfx_level >= GFX11 && !instr_ptr->definitions.empty() &&
instr_ptr->definitions[0].physReg() != scc) {
instr.reset(bld.sopp(aco_opcode::s_waitcnt_depctr, 0xfffe));
emit_instruction(ctx, out, instr.get());
/* If this block ends in an unconditional branch, we can insert
* another branch right after it without additional cost for the
* existing code.
*/
if (!block.instructions.empty() &&
block.instructions.back()->opcode == aco_opcode::s_branch) {
insert_at = next.offset;
bld.reset(&block.instructions);
if (next.offset >= lower_start)
break;
}
};
Definition def;
if (branch->definitions.empty()) {
assert(ctx.program->blocks[branch->imm].kind & block_kind_discard_early_exit);
def = Definition(PhysReg(0), s2); /* The discard early exit block doesn't use SGPRs. */
} else {
def = branch->definitions[0];
}
Definition def_tmp_lo(def.physReg(), s1);
Operand op_tmp_lo(def.physReg(), s1);
Definition def_tmp_hi(def.physReg().advance(4), s1);
Operand op_tmp_hi(def.physReg().advance(4), s1);
/* If we didn't find a suitable insertion point, split the existing code. */
if (insert_at == 0) {
/* Find the last block that is still within reach. */
unsigned insertion_block_idx = 0;
while (ctx.program->blocks[insertion_block_idx + 1].offset < upper_end)
insertion_block_idx++;
size_t conditional_br_imm = 0;
if (branch->opcode != aco_opcode::s_branch) {
/* for conditional branches, skip the long jump if the condition is false */
aco_opcode inv;
switch (branch->opcode) {
case aco_opcode::s_cbranch_scc0: inv = aco_opcode::s_cbranch_scc1; break;
case aco_opcode::s_cbranch_scc1: inv = aco_opcode::s_cbranch_scc0; break;
case aco_opcode::s_cbranch_vccz: inv = aco_opcode::s_cbranch_vccnz; break;
case aco_opcode::s_cbranch_vccnz: inv = aco_opcode::s_cbranch_vccz; break;
case aco_opcode::s_cbranch_execz: inv = aco_opcode::s_cbranch_execnz; break;
case aco_opcode::s_cbranch_execnz: inv = aco_opcode::s_cbranch_execz; break;
default: unreachable("Unhandled long jump.");
insert_at = ctx.program->blocks[insertion_block_idx].offset;
auto it = ctx.program->blocks[insertion_block_idx].instructions.begin();
int skip = 0;
if (insert_at < upper_start) {
/* Ensure some forward progress by splitting the block if necessary. */
while (skip-- > 0 || insert_at < upper_start) {
Instruction* instr = (it++)->get();
if (instr->isSOPP()) {
if (instr->opcode == aco_opcode::s_clause)
skip = instr->salu().imm + 1;
else if (instr->opcode == aco_opcode::s_delay_alu)
skip = ((instr->salu().imm >> 4) & 0x7) + 1;
else if (instr->opcode == aco_opcode::s_branch)
skip = 1;
insert_at++;
continue;
}
emit_instruction(ctx, code, instr);
assert(out[insert_at] == code[0]);
insert_at += code.size();
code.clear();
}
/* If the insertion point is in the middle of the block, insert the branch instructions
* into that block instead. */
bld.reset(&ctx.program->blocks[insertion_block_idx].instructions, it);
} else {
bld.reset(&ctx.program->blocks[insertion_block_idx - 1].instructions);
}
aco_ptr<Instruction> instr;
instr.reset(bld.sopp(inv, 0));
emit_sopp_instruction(ctx, out, instr.get(), true);
conditional_br_imm = out.size() - 1;
/* Since we insert a branch into existing code, mitigate LdsBranchVmemWARHazard on GFX10. */
if (ctx.program->gfx_level == GFX10) {
emit_sopk_instruction(
ctx, code, bld.sopk(aco_opcode::s_waitcnt_vscnt, Operand(sgpr_null, s1), 0).instr);
}
/* For the existing code, create a short jump over the new branch. */
branch_instr = bld.sopp(aco_opcode::s_branch, 1).instr;
emit_sopp_instruction(ctx, code, branch_instr, true);
}
const unsigned block_offset = insert_at + code.size();
if (ctx.gfx_level == GFX10) /* VMEMtoScalarWriteHazard needs vm_vsrc=0 wait */
emit(bld.sopp(aco_opcode::s_waitcnt_depctr, 0xffe3));
branch_instr = bld.sopp(aco_opcode::s_branch, target);
emit_sopp_instruction(ctx, code, branch_instr, true);
insert_code(ctx, out, insert_at, code.size(), code.data());
/* create the new PC and stash SCC in the LSB */
uint32_t getpc_loc, add_literal_loc;
emit(bld.sop1(aco_opcode::s_getpc_b64, def), &getpc_loc);
if (ctx.gfx_level >= GFX12)
emit(bld.sop1(aco_opcode::s_sext_i32_i16, def_tmp_hi, op_tmp_hi));
emit(bld.sop2(aco_opcode::s_addc_u32, def_tmp_lo, op_tmp_lo, Operand::literal32(0)),
&add_literal_loc);
branch->pass_flags = getpc_loc | (add_literal_loc << 16);
/* s_addc_u32 for high 32 bits not needed because the program is in a 32-bit VA range */
/* restore SCC and clear the LSB of the new PC */
emit(bld.sopc(aco_opcode::s_bitcmp1_b32, Definition(scc, s1), op_tmp_lo, Operand::zero()));
emit(bld.sop1(aco_opcode::s_bitset0_b32, def_tmp_lo, Operand::zero()));
/* create the s_setpc_b64 to jump */
emit(bld.sop1(aco_opcode::s_setpc_b64, Operand(def.physReg(), s2)));
if (branch->opcode != aco_opcode::s_branch) {
uint32_t imm = out.size() - conditional_br_imm - 1;
assert(imm != 0x3f || ctx.gfx_level != GFX10);
out[conditional_br_imm] |= imm;
}
new_block->offset = block_offset;
ctx.branches.push_back({block_offset, target});
assert(out[ctx.branches.back().pos] == code.back());
}
void
@@ -1615,30 +1636,16 @@ fix_branches(asm_context& ctx, std::vector<uint32_t>& out)
if (ctx.gfx_level == GFX10)
fix_branches_gfx10(ctx, out);
for (std::pair<int, SALU_instruction*>& branch : ctx.branches) {
int offset = (int)ctx.program->blocks[branch.second->imm].offset - branch.first - 1;
if ((offset < INT16_MIN || offset > INT16_MAX) && !branch.second->pass_flags) {
std::vector<uint32_t> long_jump;
bool backwards =
ctx.program->blocks[branch.second->imm].offset < (unsigned)branch.first;
emit_long_jump(ctx, branch.second, backwards, long_jump);
out[branch.first] = long_jump[0];
insert_code(ctx, out, branch.first + 1, long_jump.size() - 1, long_jump.data() + 1);
for (branch_info& branch : ctx.branches) {
int offset = (int)ctx.program->blocks[branch.target].offset - branch.pos - 1;
if (offset >= INT16_MIN && offset <= INT16_MAX) {
out[branch.pos] &= 0xffff0000u;
out[branch.pos] |= (uint16_t)offset;
} else {
chain_branches(ctx, out, branch);
repeat = true;
break;
}
if (branch.second->pass_flags) {
uint32_t after_getpc = branch.first + (branch.second->pass_flags & 0xffff);
uint32_t add_literal = branch.first + (branch.second->pass_flags >> 16) - 1;
offset = (int)ctx.program->blocks[branch.second->imm].offset - after_getpc;
out[add_literal] = offset * 4;
} else {
out[branch.first] &= 0xffff0000u;
out[branch.first] |= (uint16_t)offset;
}
}
} while (repeat);
}
+38 -71
View File
@@ -60,21 +60,21 @@ BEGIN_TEST(assembler.long_jump.unconditional_forwards)
return;
//!BB0:
//! s_waitcnt_depctr 0xffe3 ; bfa3ffe3
//! s_getpc_b64 s[0:1] ; be801f00
//! s_addc_u32 s0, s0, 0x20014 ; 8200ff00 00020014
//! s_bitcmp1_b32 s0, 0 ; bf0d8000
//! s_bitset0_b32 s0, 0 ; be801b80
//! s_setpc_b64 s[0:1] ; be802000
//! s_branch 16369 ; bf823ff1
bld.sopp(aco_opcode::s_branch, Definition(PhysReg(0), s2), 2);
bld.reset(program->create_and_insert_block());
//! s_nop 0 ; bf800000
//!(then repeated 32767 times)
//!(then repeated 16366 times)
for (unsigned i = 0; i < INT16_MAX + 1; i++)
bld.sopp(aco_opcode::s_nop, 0);
//! s_waitcnt_vscnt null, 0x0 ; bbfd0000
//! s_branch 1 ; bf820001
//! s_branch BB2 ; bf824011
//! s_nop 0 ; bf800000
//! (then repeated 16400 times)
//! BB2:
//! s_endpgm ; bf810000
bld.reset(program->create_and_insert_block());
@@ -91,26 +91,20 @@ BEGIN_TEST(assembler.long_jump.conditional_forwards)
continue;
//! BB0:
//! s_cbranch_scc1 BB1 ; $_
//~gfx10! s_waitcnt_depctr 0xffe3 ; $_
//! s_getpc_b64 s[0:1] ; $_
//~gfx12! s_wait_alu 0xfffe ; $_
//~gfx12! s_sext_i32_i16 s1, s1 ; $_
//~gfx12! s_wait_alu 0xfffe ; $_
//~gfx10! s_addc_u32 s0, s0, 0x20014 ; $_ $_
//~gfx12! s_add_co_ci_u32 s0, s0, 0x20028 ; $_ $_
//~gfx12! s_wait_alu 0xfffe ; $_
//! s_bitcmp1_b32 s0, 0 ; $_
//! s_bitset0_b32 s0, 0 ; $_
//~gfx12! s_wait_alu 0xfffe ; $_
//! s_setpc_b64 s[0:1] ; $_
//~gfx10! s_cbranch_scc0 16369 ; bf843ff1
//~gfx12! s_cbranch_scc0 16368 ; bfa13ff0
bld.sopp(aco_opcode::s_cbranch_scc0, Definition(PhysReg(0), s2), 2);
bld.reset(program->create_and_insert_block());
//! BB1:
//! s_nop 0 ; bf800000
//!(then repeated 32767 times)
//! s_nop 0 ; bf800000
//!(then repeated 16366 times)
//~gfx10! s_waitcnt_vscnt null, 0x0 ; bbfd0000
//! s_branch 1 ; $_
//! s_branch BB2 ; $_
//! s_nop 0 ; bf800000
//!(then repeated 16400 times)
for (unsigned i = 0; i < INT16_MAX + 1; i++)
bld.sopp(aco_opcode::s_nop, 0);
@@ -132,16 +126,16 @@ BEGIN_TEST(assembler.long_jump.unconditional_backwards)
//!BB0:
//! s_nop 0 ; bf800000
//!(then repeated 32767 times)
//!(then repeated 16367 times)
for (unsigned i = 0; i < INT16_MAX + 1; i++)
bld.sopp(aco_opcode::s_nop, 0);
//! s_waitcnt_depctr 0xffe3 ; bfa3ffe3
//! s_getpc_b64 s[0:1] ; be801f00
//! s_addc_u32 s0, s0, 0xfffdfff8 ; 8200ff00 fffdfff8
//! s_bitcmp1_b32 s0, 0 ; bf0d8000
//! s_bitset0_b32 s0, 0 ; be801b80
//! s_setpc_b64 s[0:1] ; be802000
//! s_waitcnt_vscnt null, 0x0 ; bbfd0000
//! s_branch 1 ; bf820001
//! s_branch BB0 ; bf82c00d
//! s_nop 0 ; bf800000
//! (then repeated 16399 times)
//! s_branch 49134 ; bf82bfee
bld.sopp(aco_opcode::s_branch, Definition(PhysReg(0), s2), 0);
//! BB1:
@@ -160,17 +154,16 @@ BEGIN_TEST(assembler.long_jump.conditional_backwards)
//!BB0:
//! s_nop 0 ; bf800000
//!(then repeated 32767 times)
//!(then repeated 16367 times)
for (unsigned i = 0; i < INT16_MAX + 1; i++)
bld.sopp(aco_opcode::s_nop, 0);
//! s_cbranch_execz BB1 ; bf880007
//! s_waitcnt_depctr 0xffe3 ; bfa3ffe3
//! s_getpc_b64 s[0:1] ; be801f00
//! s_addc_u32 s0, s0, 0xfffdfff4 ; 8200ff00 fffdfff4
//! s_bitcmp1_b32 s0, 0 ; bf0d8000
//! s_bitset0_b32 s0, 0 ; be801b80
//! s_setpc_b64 s[0:1] ; be802000
//! s_waitcnt_vscnt null, 0x0 ; bbfd0000
//! s_branch 1 ; bf820001
//! s_branch BB0 ; bf82c00d
//! s_nop 0 ; bf800000
//!(then repeated 16399 times)
//! s_cbranch_execnz 49134 ; bf89bfee
bld.sopp(aco_opcode::s_cbranch_execnz, Definition(PhysReg(0), s2), 0);
//! BB1:
@@ -183,36 +176,11 @@ BEGIN_TEST(assembler.long_jump.conditional_backwards)
finish_assembler_test();
END_TEST
BEGIN_TEST(assembler.long_jump._3f)
if (!setup_cs(NULL, (amd_gfx_level)GFX10))
return;
//! BB0:
//! s_branch BB1 ; bf820040
//! s_nop 0 ; bf800000
bld.sopp(aco_opcode::s_branch, Definition(PhysReg(0), s2), 1);
for (unsigned i = 0; i < 0x3f - 7; i++) // an unconditional long jump is 7 dwords
bld.vop1(aco_opcode::v_nop);
bld.sopp(aco_opcode::s_branch, Definition(PhysReg(0), s2), 2);
bld.reset(program->create_and_insert_block());
for (unsigned i = 0; i < INT16_MAX + 1; i++)
bld.vop1(aco_opcode::v_nop);
bld.reset(program->create_and_insert_block());
program->blocks[1].linear_preds.push_back(0u);
program->blocks[2].linear_preds.push_back(0u);
program->blocks[2].linear_preds.push_back(1u);
finish_assembler_test();
END_TEST
BEGIN_TEST(assembler.long_jump.constaddr)
if (!setup_cs(NULL, (amd_gfx_level)GFX10))
return;
//>> s_getpc_b64 s[0:1] ; be801f00
//>> s_branch 16369 ; bf823ff1
bld.sopp(aco_opcode::s_branch, Definition(PhysReg(0), s2), 2);
bld.reset(program->create_and_insert_block());
@@ -239,20 +207,19 @@ BEGIN_TEST(assembler.long_jump.discard_early_exit)
return;
//! BB0:
//! s_cbranch_scc1 BB1 ; bf850007
//! s_waitcnt_depctr 0xffe3 ; bfa3ffe3
//! s_getpc_b64 s[0:1] ; be801f00
//! s_addc_u32 s0, s0, 0x20014 ; 8200ff00 00020014
//! s_bitcmp1_b32 s0, 0 ; bf0d8000
//! s_bitset0_b32 s0, 0 ; be801b80
//! s_setpc_b64 s[0:1] ; be802000
//! s_cbranch_scc0 16369 ; bf843ff1
bld.sopp(aco_opcode::s_cbranch_scc0, 2);
bld.reset(program->create_and_insert_block());
//! BB1:
//! s_nop 1 ; bf800001
//!(then repeated 32766 times)
//! (then repeated 16366 times)
//! s_waitcnt_vscnt null, 0x0 ; bbfd0000
//! s_branch 1 ; bf820001
//! s_branch BB2 ; bf824011
//! s_nop 1 ; bf800001
//! (then repeated 16399 times)
//! s_endpgm ; bf810000
for (unsigned i = 0; i < INT16_MAX; i++)
bld.sopp(aco_opcode::s_nop, 1);