summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/amd/compiler/README87
-rw-r--r--src/amd/compiler/aco_assembler.cpp497
-rw-r--r--src/amd/compiler/aco_builder_h.py400
-rw-r--r--src/amd/compiler/aco_dead_code_analysis.cpp102
-rw-r--r--src/amd/compiler/aco_dominance.cpp93
-rw-r--r--src/amd/compiler/aco_insert_NOPs.cpp282
-rw-r--r--src/amd/compiler/aco_insert_exec_mask.cpp1078
-rw-r--r--src/amd/compiler/aco_insert_waitcnt.cpp697
-rw-r--r--src/amd/compiler/aco_instruction_selection.cpp7621
-rw-r--r--src/amd/compiler/aco_instruction_selection_setup.cpp1366
-rw-r--r--src/amd/compiler/aco_interface.cpp166
-rw-r--r--src/amd/compiler/aco_interface.h45
-rw-r--r--src/amd/compiler/aco_ir.h1169
-rw-r--r--src/amd/compiler/aco_live_var_analysis.cpp243
-rw-r--r--src/amd/compiler/aco_lower_bool_phis.cpp241
-rw-r--r--src/amd/compiler/aco_lower_to_hw_instr.cpp765
-rw-r--r--src/amd/compiler/aco_opcodes.py1552
-rw-r--r--src/amd/compiler/aco_opcodes_cpp.py74
-rw-r--r--src/amd/compiler/aco_opcodes_h.py47
-rw-r--r--src/amd/compiler/aco_opt_value_numbering.cpp327
-rw-r--r--src/amd/compiler/aco_optimizer.cpp2401
-rw-r--r--src/amd/compiler/aco_print_asm.cpp104
-rw-r--r--src/amd/compiler/aco_print_ir.cpp575
-rw-r--r--src/amd/compiler/aco_reduce_assign.cpp164
-rw-r--r--src/amd/compiler/aco_register_allocation.cpp1924
-rw-r--r--src/amd/compiler/aco_scheduler.cpp835
-rw-r--r--src/amd/compiler/aco_spill.cpp1630
-rw-r--r--src/amd/compiler/aco_ssa_elimination.cpp291
-rw-r--r--src/amd/compiler/aco_util.h233
-rw-r--r--src/amd/compiler/aco_validate.cpp460
-rw-r--r--src/amd/compiler/meson.build103
31 files changed, 25572 insertions, 0 deletions
diff --git a/src/amd/compiler/README b/src/amd/compiler/README
new file mode 100644
index 00000000000..87d63c07024
--- /dev/null
+++ b/src/amd/compiler/README
@@ -0,0 +1,87 @@
+# Unofficial GCN/RDNA ISA reference errata
+
+## v_sad_u32
+
+The Vega ISA reference writes it's behaviour as:
+```
+D.u = abs(S0.i - S1.i) + S2.u.
+```
+This is incorrect. The actual behaviour is what is written in the GCN3 reference
+guide:
+```
+ABS_DIFF (A,B) = (A>B) ? (A-B) : (B-A)
+D.u = ABS_DIFF (S0.u,S1.u) + S2.u
+```
+The instruction doesn't subtract the S0 and S1 and use the absolute value (the
+_signed_ distance), it uses the _unsigned_ distance between the operands. So
+`v_sad_u32(-5, 0, 0)` would return `4294967291` (`-5` interpreted as unsigned),
+not `5`.
+
+## s_bfe_*
+
+Both the Vega and GCN3 ISA references write that these instructions don't write
+SCC. They do.
+
+## v_bcnt_u32_b32
+
+The Vega ISA reference writes it's behaviour as:
+```
+D.u = 0;
+for i in 0 ... 31 do
+D.u += (S0.u[i] == 1 ? 1 : 0);
+endfor.
+```
+This is incorrect. The actual behaviour (and number of operands) is what
+is written in the GCN3 reference guide:
+```
+D.u = CountOneBits(S0.u) + S1.u.
+```
+
+## SMEM stores
+
+The Vega ISA references doesn't say this (or doesn't make it clear), but
+the offset for SMEM stores must be in m0 if IMM == 0.
+
+The RDNA ISA doesn't mention SMEM stores at all, but they seem to be supported
+by the chip and are present in LLVM. AMD devs however highly recommend avoiding
+these instructions.
+
+## SMEM atomics
+
+RDNA ISA: same as the SMEM stores, the ISA pretends they don't exist, but they
+are there in LLVM.
+
+## VMEM stores
+
+All reference guides say (under "Vector Memory Instruction Data Dependencies"):
+> When a VM instruction is issued, the address is immediately read out of VGPRs
+> and sent to the texture cache. Any texture or buffer resources and samplers
+> are also sent immediately. However, write-data is not immediately sent to the
+> texture cache.
+Reading that, one might think that waitcnts need to be added when writing to
+the registers used for a VMEM store's data. Experimentation has shown that this
+does not seem to be the case on GFX8 and GFX9 (GFX6 and GFX7 are untested). It
+also seems unlikely, since NOPs are apparently needed in a subset of these
+situations.
+
+## MIMG opcodes on GFX8/GCN3
+
+The `image_atomic_{swap,cmpswap,add,sub}` opcodes in the GCN3 ISA reference
+guide are incorrect. The Vega ISA reference guide has the correct ones.
+
+## Legacy instructions
+
+Some instructions have a `_LEGACY` variant which implements "DX9 rules", in which
+the zero "wins" in multiplications, ie. `0.0*x` is always `0.0`. The VEGA ISA
+mentions `V_MAC_LEGACY_F32` but this instruction is not really there on VEGA.
+
+# Hardware Bugs
+
+## SMEM corrupts VCCZ on SI/CI
+
+https://github.com/llvm/llvm-project/blob/acb089e12ae48b82c0b05c42326196a030df9b82/llvm/lib/Target/AMDGPU/SIInsertWaits.cpp#L580-L616
+After issuing a SMEM instructions, we need to wait for the SMEM instructions to
+finish and then write to vcc (for example, `s_mov_b64 vcc, vcc`) to correct vccz
+
+Currently, we don't do this.
+
diff --git a/src/amd/compiler/aco_assembler.cpp b/src/amd/compiler/aco_assembler.cpp
new file mode 100644
index 00000000000..a6bf2a3e0db
--- /dev/null
+++ b/src/amd/compiler/aco_assembler.cpp
@@ -0,0 +1,497 @@
+#include <map>
+
+#include "aco_ir.h"
+#include "common/sid.h"
+
+namespace aco {
+
+struct asm_context {
+ Program *program;
+ enum chip_class chip_class;
+ std::map<int, SOPP_instruction*> branches;
+ std::vector<unsigned> constaddrs;
+ const int16_t* opcode;
+ // TODO: keep track of branch instructions referring blocks
+ // and, when emitting the block, correct the offset in instr
+ asm_context(Program* program) : program(program), chip_class(program->chip_class) {
+ if (chip_class <= GFX9)
+ opcode = &instr_info.opcode_gfx9[0];
+ }
+};
+
+void emit_instruction(asm_context& ctx, std::vector<uint32_t>& out, Instruction* instr)
+{
+ uint32_t instr_offset = out.size() * 4u;
+
+ /* lower remaining pseudo-instructions */
+ if (instr->opcode == aco_opcode::p_constaddr) {
+ unsigned dest = instr->definitions[0].physReg();
+ unsigned offset = instr->operands[0].constantValue();
+
+ /* s_getpc_b64 dest[0:1] */
+ uint32_t encoding = (0b101111101 << 23);
+ uint32_t opcode = ctx.opcode[(int)aco_opcode::s_getpc_b64];
+ if (opcode >= 55 && ctx.chip_class <= GFX9) {
+ assert(ctx.chip_class == GFX9 && opcode < 60);
+ opcode = opcode - 4;
+ }
+ encoding |= dest << 16;
+ encoding |= opcode << 8;
+ out.push_back(encoding);
+
+ /* s_add_u32 dest[0], dest[0], ... */
+ encoding = (0b10 << 30);
+ encoding |= ctx.opcode[(int)aco_opcode::s_add_u32] << 23;
+ encoding |= dest << 16;
+ encoding |= dest;
+ encoding |= 255 << 8;
+ out.push_back(encoding);
+ ctx.constaddrs.push_back(out.size());
+ out.push_back(-(instr_offset + 4) + offset);
+
+ /* s_addc_u32 dest[1], dest[1], 0 */
+ encoding = (0b10 << 30);
+ encoding |= ctx.opcode[(int)aco_opcode::s_addc_u32] << 23;
+ encoding |= (dest + 1) << 16;
+ encoding |= dest + 1;
+ encoding |= 128 << 8;
+ out.push_back(encoding);
+ return;
+ }
+
+ uint32_t opcode = ctx.opcode[(int)instr->opcode];
+ if (opcode == (uint32_t)-1) {
+ fprintf(stderr, "Unsupported opcode: ");
+ aco_print_instr(instr, stderr);
+ abort();
+ }
+
+ switch (instr->format) {
+ case Format::SOP2: {
+ uint32_t encoding = (0b10 << 30);
+ encoding |= opcode << 23;
+ encoding |= !instr->definitions.empty() ? instr->definitions[0].physReg() << 16 : 0;
+ encoding |= instr->operands.size() >= 2 ? instr->operands[1].physReg() << 8 : 0;
+ encoding |= !instr->operands.empty() ? instr->operands[0].physReg() : 0;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::SOPK: {
+ uint32_t encoding = (0b1011 << 28);
+ encoding |= opcode << 23;
+ encoding |=
+ !instr->definitions.empty() && !(instr->definitions[0].physReg() == scc) ?
+ instr->definitions[0].physReg() << 16 :
+ !instr->operands.empty() && !(instr->operands[0].physReg() == scc) ?
+ instr->operands[0].physReg() << 16 : 0;
+ encoding |= static_cast<SOPK_instruction*>(instr)->imm;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::SOP1: {
+ uint32_t encoding = (0b101111101 << 23);
+ if (opcode >= 55 && ctx.chip_class <= GFX9) {
+ assert(ctx.chip_class == GFX9 && opcode < 60);
+ opcode = opcode - 4;
+ }
+ encoding |= !instr->definitions.empty() ? instr->definitions[0].physReg() << 16 : 0;
+ encoding |= opcode << 8;
+ encoding |= !instr->operands.empty() ? instr->operands[0].physReg() : 0;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::SOPC: {
+ uint32_t encoding = (0b101111110 << 23);
+ encoding |= opcode << 16;
+ encoding |= instr->operands.size() == 2 ? instr->operands[1].physReg() << 8 : 0;
+ encoding |= !instr->operands.empty() ? instr->operands[0].physReg() : 0;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::SOPP: {
+ SOPP_instruction* sopp = static_cast<SOPP_instruction*>(instr);
+ uint32_t encoding = (0b101111111 << 23);
+ encoding |= opcode << 16;
+ encoding |= (uint16_t) sopp->imm;
+ if (sopp->block != -1)
+ ctx.branches.insert({out.size(), sopp});
+ out.push_back(encoding);
+ break;
+ }
+ case Format::SMEM: {
+ SMEM_instruction* smem = static_cast<SMEM_instruction*>(instr);
+ uint32_t encoding = (0b110000 << 26);
+ encoding |= opcode << 18;
+ if (instr->operands.size() >= 2)
+ encoding |= instr->operands[1].isConstant() ? 1 << 17 : 0;
+ bool soe = instr->operands.size() >= (!instr->definitions.empty() ? 3 : 4);
+ assert(!soe || ctx.chip_class >= GFX9);
+ encoding |= soe ? 1 << 14 : 0;
+ encoding |= smem->glc ? 1 << 16 : 0;
+ if (!instr->definitions.empty() || instr->operands.size() >= 3)
+ encoding |= (!instr->definitions.empty() ? instr->definitions[0].physReg() : instr->operands[2].physReg().reg) << 6;
+ if (instr->operands.size() >= 1)
+ encoding |= instr->operands[0].physReg() >> 1;
+ out.push_back(encoding);
+ encoding = 0;
+ if (instr->operands.size() >= 2)
+ encoding |= instr->operands[1].isConstant() ? instr->operands[1].constantValue() : instr->operands[1].physReg().reg;
+ encoding |= soe ? instr->operands.back().physReg() << 25 : 0;
+ out.push_back(encoding);
+ return;
+ }
+ case Format::VOP2: {
+ uint32_t encoding = 0;
+ encoding |= opcode << 25;
+ encoding |= (0xFF & instr->definitions[0].physReg().reg) << 17;
+ encoding |= (0xFF & instr->operands[1].physReg().reg) << 9;
+ encoding |= instr->operands[0].physReg().reg;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::VOP1: {
+ uint32_t encoding = (0b0111111 << 25);
+ encoding |= (0xFF & instr->definitions[0].physReg().reg) << 17;
+ encoding |= opcode << 9;
+ encoding |= instr->operands[0].physReg().reg;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::VOPC: {
+ uint32_t encoding = (0b0111110 << 25);
+ encoding |= opcode << 17;
+ encoding |= (0xFF & instr->operands[1].physReg().reg) << 9;
+ encoding |= instr->operands[0].physReg().reg;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::VINTRP: {
+ Interp_instruction* interp = static_cast<Interp_instruction*>(instr);
+ uint32_t encoding = (0b110101 << 26);
+ encoding |= (0xFF & instr->definitions[0].physReg().reg) << 18;
+ encoding |= opcode << 16;
+ encoding |= interp->attribute << 10;
+ encoding |= interp->component << 8;
+ if (instr->opcode == aco_opcode::v_interp_mov_f32)
+ encoding |= (0x3 & instr->operands[0].constantValue());
+ else
+ encoding |= (0xFF & instr->operands[0].physReg().reg);
+ out.push_back(encoding);
+ break;
+ }
+ case Format::DS: {
+ DS_instruction* ds = static_cast<DS_instruction*>(instr);
+ uint32_t encoding = (0b110110 << 26);
+ encoding |= opcode << 17;
+ encoding |= (ds->gds ? 1 : 0) << 16;
+ encoding |= ((0xFF & ds->offset1) << 8);
+ encoding |= (0xFFFF & ds->offset0);
+ out.push_back(encoding);
+ encoding = 0;
+ unsigned reg = !instr->definitions.empty() ? instr->definitions[0].physReg() : 0;
+ encoding |= (0xFF & reg) << 24;
+ reg = instr->operands.size() >= 3 && !(instr->operands[2].physReg() == m0) ? instr->operands[2].physReg() : 0;
+ encoding |= (0xFF & reg) << 16;
+ reg = instr->operands.size() >= 2 && !(instr->operands[1].physReg() == m0) ? instr->operands[1].physReg() : 0;
+ encoding |= (0xFF & reg) << 8;
+ encoding |= (0xFF & instr->operands[0].physReg().reg);
+ out.push_back(encoding);
+ break;
+ }
+ case Format::MUBUF: {
+ MUBUF_instruction* mubuf = static_cast<MUBUF_instruction*>(instr);
+ uint32_t encoding = (0b111000 << 26);
+ encoding |= opcode << 18;
+ encoding |= (mubuf->slc ? 1 : 0) << 17;
+ encoding |= (mubuf->lds ? 1 : 0) << 16;
+ encoding |= (mubuf->glc ? 1 : 0) << 14;
+ encoding |= (mubuf->idxen ? 1 : 0) << 13;
+ encoding |= (mubuf->offen ? 1 : 0) << 12;
+ encoding |= 0x0FFF & mubuf->offset;
+ out.push_back(encoding);
+ encoding = 0;
+ encoding |= instr->operands[2].physReg() << 24;
+ encoding |= (mubuf->tfe ? 1 : 0) << 23;
+ encoding |= (instr->operands[1].physReg() >> 2) << 16;
+ unsigned reg = instr->operands.size() > 3 ? instr->operands[3].physReg() : instr->definitions[0].physReg().reg;
+ encoding |= (0xFF & reg) << 8;
+ encoding |= (0xFF & instr->operands[0].physReg().reg);
+ out.push_back(encoding);
+ break;
+ }
+ case Format::MTBUF: {
+ MTBUF_instruction* mtbuf = static_cast<MTBUF_instruction*>(instr);
+ uint32_t encoding = (0b111010 << 26);
+ encoding |= opcode << 15;
+ encoding |= (mtbuf->glc ? 1 : 0) << 14;
+ encoding |= (mtbuf->idxen ? 1 : 0) << 13;
+ encoding |= (mtbuf->offen ? 1 : 0) << 12;
+ encoding |= 0x0FFF & mtbuf->offset;
+ encoding |= (0xF & mtbuf->dfmt) << 19;
+ encoding |= (0x7 & mtbuf->nfmt) << 23;
+ out.push_back(encoding);
+ encoding = 0;
+ encoding |= instr->operands[2].physReg().reg << 24;
+ encoding |= (mtbuf->tfe ? 1 : 0) << 23;
+ encoding |= (mtbuf->slc ? 1 : 0) << 22;
+ encoding |= (instr->operands[1].physReg().reg >> 2) << 16;
+ unsigned reg = instr->operands.size() > 3 ? instr->operands[3].physReg().reg : instr->definitions[0].physReg().reg;
+ encoding |= (0xFF & reg) << 8;
+ encoding |= (0xFF & instr->operands[0].physReg().reg);
+ out.push_back(encoding);
+ break;
+ }
+ case Format::MIMG: {
+ MIMG_instruction* mimg = static_cast<MIMG_instruction*>(instr);
+ uint32_t encoding = (0b111100 << 26);
+ encoding |= mimg->slc ? 1 << 25 : 0;
+ encoding |= opcode << 18;
+ encoding |= mimg->lwe ? 1 << 17 : 0;
+ encoding |= mimg->tfe ? 1 << 16 : 0;
+ encoding |= mimg->r128 ? 1 << 15 : 0;
+ encoding |= mimg->da ? 1 << 14 : 0;
+ encoding |= mimg->glc ? 1 << 13 : 0;
+ encoding |= mimg->unrm ? 1 << 12 : 0;
+ encoding |= (0xF & mimg->dmask) << 8;
+ out.push_back(encoding);
+ encoding = (0xFF & instr->operands[0].physReg().reg); /* VADDR */
+ if (!instr->definitions.empty()) {
+ encoding |= (0xFF & instr->definitions[0].physReg().reg) << 8; /* VDATA */
+ } else if (instr->operands.size() == 4) {
+ encoding |= (0xFF & instr->operands[3].physReg().reg) << 8; /* VDATA */
+ }
+ encoding |= (0x1F & (instr->operands[1].physReg() >> 2)) << 16; /* T# (resource) */
+ if (instr->operands.size() > 2)
+ encoding |= (0x1F & (instr->operands[2].physReg() >> 2)) << 21; /* sampler */
+ // TODO VEGA: D16
+ out.push_back(encoding);
+ break;
+ }
+ case Format::FLAT:
+ case Format::SCRATCH:
+ case Format::GLOBAL: {
+ FLAT_instruction *flat = static_cast<FLAT_instruction*>(instr);
+ uint32_t encoding = (0b110111 << 26);
+ encoding |= opcode << 18;
+ encoding |= flat->offset & 0x1fff;
+ if (instr->format == Format::SCRATCH)
+ encoding |= 1 << 14;
+ else if (instr->format == Format::GLOBAL)
+ encoding |= 2 << 14;
+ encoding |= flat->lds ? 1 << 13 : 0;
+ encoding |= flat->glc ? 1 << 13 : 0;
+ encoding |= flat->slc ? 1 << 13 : 0;
+ out.push_back(encoding);
+ encoding = (0xFF & instr->operands[0].physReg().reg);
+ if (!instr->definitions.empty())
+ encoding |= (0xFF & instr->definitions[0].physReg().reg) << 24;
+ else
+ encoding |= (0xFF & instr->operands[2].physReg().reg) << 8;
+ if (!instr->operands[1].isUndefined()) {
+ assert(instr->operands[1].physReg() != 0x7f);
+ assert(instr->format != Format::FLAT);
+ encoding |= instr->operands[1].physReg() << 16;
+ } else if (instr->format != Format::FLAT) {
+ encoding |= 0x7F << 16;
+ }
+ encoding |= flat->nv ? 1 << 23 : 0;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::EXP: {
+ Export_instruction* exp = static_cast<Export_instruction*>(instr);
+ uint32_t encoding = (0b110001 << 26);
+ encoding |= exp->valid_mask ? 0b1 << 12 : 0;
+ encoding |= exp->done ? 0b1 << 11 : 0;
+ encoding |= exp->compressed ? 0b1 << 10 : 0;
+ encoding |= exp->dest << 4;
+ encoding |= exp->enabled_mask;
+ out.push_back(encoding);
+ encoding = 0xFF & exp->operands[0].physReg().reg;
+ encoding |= (0xFF & exp->operands[1].physReg().reg) << 8;
+ encoding |= (0xFF & exp->operands[2].physReg().reg) << 16;
+ encoding |= (0xFF & exp->operands[3].physReg().reg) << 24;
+ out.push_back(encoding);
+ break;
+ }
+ case Format::PSEUDO:
+ case Format::PSEUDO_BARRIER:
+ unreachable("Pseudo instructions should be lowered before assembly.");
+ default:
+ if ((uint16_t) instr->format & (uint16_t) Format::VOP3A) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr);
+
+ if ((uint16_t) instr->format & (uint16_t) Format::VOP2)
+ opcode = opcode + 0x100;
+ else if ((uint16_t) instr->format & (uint16_t) Format::VOP1)
+ opcode = opcode + 0x140;
+ else if ((uint16_t) instr->format & (uint16_t) Format::VOPC)
+ opcode = opcode + 0x0;
+ else if ((uint16_t) instr->format & (uint16_t) Format::VINTRP)
+ opcode = opcode + 0x270;
+
+ // TODO: op_sel
+ uint32_t encoding = (0b110100 << 26);
+ encoding |= opcode << 16;
+ encoding |= (vop3->clamp ? 1 : 0) << 15;
+ for (unsigned i = 0; i < 3; i++)
+ encoding |= vop3->abs[i] << (8+i);
+ if (instr->definitions.size() == 2)
+ encoding |= instr->definitions[1].physReg() << 8;
+ encoding |= (0xFF & instr->definitions[0].physReg().reg);
+ out.push_back(encoding);
+ encoding = 0;
+ if (instr->opcode == aco_opcode::v_interp_mov_f32) {
+ encoding = 0x3 & instr->operands[0].constantValue();
+ } else {
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ encoding |= instr->operands[i].physReg() << (i * 9);
+ }
+ encoding |= vop3->omod << 27;
+ for (unsigned i = 0; i < 3; i++)
+ encoding |= vop3->neg[i] << (29+i);
+ out.push_back(encoding);
+ return;
+
+ } else if (instr->isDPP()){
+ /* first emit the instruction without the DPP operand */
+ Operand dpp_op = instr->operands[0];
+ instr->operands[0] = Operand(PhysReg{250}, v1);
+ instr->format = (Format) ((uint32_t) instr->format & ~(1 << 14));
+ emit_instruction(ctx, out, instr);
+ DPP_instruction* dpp = static_cast<DPP_instruction*>(instr);
+ uint32_t encoding = (0xF & dpp->row_mask) << 28;
+ encoding |= (0xF & dpp->bank_mask) << 24;
+ encoding |= dpp->abs[1] << 23;
+ encoding |= dpp->neg[1] << 22;
+ encoding |= dpp->abs[0] << 21;
+ encoding |= dpp->neg[0] << 20;
+ encoding |= dpp->bound_ctrl << 19;
+ encoding |= dpp->dpp_ctrl << 8;
+ encoding |= (0xFF) & dpp_op.physReg().reg;
+ out.push_back(encoding);
+ return;
+ } else {
+ unreachable("unimplemented instruction format");
+ }
+ }
+
+ /* append literal dword */
+ for (const Operand& op : instr->operands) {
+ if (op.isLiteral()) {
+ out.push_back(op.constantValue());
+ break;
+ }
+ }
+}
+
+void emit_block(asm_context& ctx, std::vector<uint32_t>& out, Block& block)
+{
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+#if 0
+ int start_idx = out.size();
+ std::cerr << "Encoding:\t" << std::endl;
+ aco_print_instr(&*instr, stderr);
+ std::cerr << std::endl;
+#endif
+ emit_instruction(ctx, out, instr.get());
+#if 0
+ for (int i = start_idx; i < out.size(); i++)
+ std::cerr << "encoding: " << "0x" << std::setfill('0') << std::setw(8) << std::hex << out[i] << std::endl;
+#endif
+ }
+}
+
+void fix_exports(asm_context& ctx, std::vector<uint32_t>& out, Program* program)
+{
+ for (int idx = program->blocks.size() - 1; idx >= 0; idx--) {
+ Block& block = program->blocks[idx];
+ std::vector<aco_ptr<Instruction>>::reverse_iterator it = block.instructions.rbegin();
+ bool endBlock = false;
+ bool exported = false;
+ while ( it != block.instructions.rend())
+ {
+ if ((*it)->format == Format::EXP && endBlock) {
+ Export_instruction* exp = static_cast<Export_instruction*>((*it).get());
+ if (program->stage & hw_vs) {
+ if (exp->dest >= V_008DFC_SQ_EXP_POS && exp->dest <= (V_008DFC_SQ_EXP_POS + 3)) {
+ exp->done = true;
+ exported = true;
+ break;
+ }
+ } else {
+ exp->done = true;
+ exp->valid_mask = true;
+ exported = true;
+ break;
+ }
+ } else if ((*it)->definitions.size() && (*it)->definitions[0].physReg() == exec)
+ break;
+ else if ((*it)->opcode == aco_opcode::s_endpgm) {
+ if (endBlock)
+ break;
+ endBlock = true;
+ }
+ ++it;
+ }
+ if (!endBlock || exported)
+ continue;
+ /* we didn't find an Export instruction and have to insert a null export */
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ for (unsigned i = 0; i < 4; i++)
+ exp->operands[i] = Operand(v1);
+ exp->enabled_mask = 0;
+ exp->compressed = false;
+ exp->done = true;
+ exp->valid_mask = program->stage & hw_fs;
+ if (program->stage & hw_fs)
+ exp->dest = 9; /* NULL */
+ else
+ exp->dest = V_008DFC_SQ_EXP_POS;
+ /* insert the null export 1 instruction before endpgm */
+ block.instructions.insert(block.instructions.end() - 1, std::move(exp));
+ }
+}
+
+void fix_branches(asm_context& ctx, std::vector<uint32_t>& out)
+{
+ for (std::pair<int, SOPP_instruction*> branch : ctx.branches)
+ {
+ int offset = (int)ctx.program->blocks[branch.second->block].offset - branch.first - 1;
+ out[branch.first] |= (uint16_t) offset;
+ }
+}
+
+void fix_constaddrs(asm_context& ctx, std::vector<uint32_t>& out)
+{
+ for (unsigned addr : ctx.constaddrs)
+ out[addr] += out.size() * 4u;
+}
+
+unsigned emit_program(Program* program,
+ std::vector<uint32_t>& code)
+{
+ asm_context ctx(program);
+
+ if (program->stage & (hw_vs | hw_fs))
+ fix_exports(ctx, code, program);
+
+ for (Block& block : program->blocks) {
+ block.offset = code.size();
+ emit_block(ctx, code, block);
+ }
+
+ fix_branches(ctx, code);
+ fix_constaddrs(ctx, code);
+
+ unsigned constant_data_offset = code.size() * sizeof(uint32_t);
+ while (program->constant_data.size() % 4u)
+ program->constant_data.push_back(0);
+ /* Copy constant data */
+ code.insert(code.end(), (uint32_t*)program->constant_data.data(),
+ (uint32_t*)(program->constant_data.data() + program->constant_data.size()));
+
+ return constant_data_offset;
+}
+
+}
diff --git a/src/amd/compiler/aco_builder_h.py b/src/amd/compiler/aco_builder_h.py
new file mode 100644
index 00000000000..7be3a664c4a
--- /dev/null
+++ b/src/amd/compiler/aco_builder_h.py
@@ -0,0 +1,400 @@
+
+template = """\
+/*
+ * Copyright (c) 2019 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * This file was generated by aco_builder_h.py
+ */
+
+#ifndef _ACO_BUILDER_
+#define _ACO_BUILDER_
+
+#include "aco_ir.h"
+#include "util/u_math.h"
+#include "util/bitscan.h"
+
+namespace aco {
+enum dpp_ctrl {
+ _dpp_quad_perm = 0x000,
+ _dpp_row_sl = 0x100,
+ _dpp_row_sr = 0x110,
+ _dpp_row_rr = 0x120,
+ dpp_wf_sl1 = 0x130,
+ dpp_wf_rl1 = 0x134,
+ dpp_wf_sr1 = 0x138,
+ dpp_wf_rr1 = 0x13C,
+ dpp_row_mirror = 0x140,
+ dpp_row_half_mirror = 0x141,
+ dpp_row_bcast15 = 0x142,
+ dpp_row_bcast31 = 0x143
+};
+
+inline dpp_ctrl
+dpp_quad_perm(unsigned lane0, unsigned lane1, unsigned lane2, unsigned lane3)
+{
+ assert(lane0 < 4 && lane1 < 4 && lane2 < 4 && lane3 < 4);
+ return (dpp_ctrl)(lane0 | (lane1 << 2) | (lane2 << 4) | (lane3 << 6));
+}
+
+inline dpp_ctrl
+dpp_row_sl(unsigned amount)
+{
+ assert(amount > 0 && amount < 16);
+ return (dpp_ctrl)(((unsigned) _dpp_row_sl) | amount);
+}
+
+inline dpp_ctrl
+dpp_row_sr(unsigned amount)
+{
+ assert(amount > 0 && amount < 16);
+ return (dpp_ctrl)(((unsigned) _dpp_row_sr) | amount);
+}
+
+inline unsigned
+ds_pattern_bitmode(unsigned and_mask, unsigned or_mask, unsigned xor_mask)
+{
+ assert(and_mask < 32 && or_mask < 32 && xor_mask < 32);
+ return and_mask | (or_mask << 5) | (xor_mask << 10);
+}
+
+aco_ptr<Instruction> create_s_mov(Definition dst, Operand src);
+
+class Builder {
+public:
+ struct Result {
+ Instruction *instr;
+
+ Result(Instruction *instr) : instr(instr) {}
+
+ operator Instruction *() const {
+ return instr;
+ }
+
+ operator Temp() const {
+ return instr->definitions[0].getTemp();
+ }
+
+ operator Operand() const {
+ return Operand((Temp)*this);
+ }
+
+ Definition& def(unsigned index) const {
+ return instr->definitions[index];
+ }
+
+ aco_ptr<Instruction> get_ptr() const {
+ return aco_ptr<Instruction>(instr);
+ }
+ };
+
+ struct Op {
+ Operand op;
+ Op(Temp tmp) : op(tmp) {}
+ Op(Operand op_) : op(op_) {}
+ Op(Result res) : op((Temp)res) {}
+ };
+
+ Program *program;
+ bool use_iterator;
+ union {
+ bool forwards; //when use_iterator == true
+ bool start; //when use_iterator == false
+ };
+ std::vector<aco_ptr<Instruction>> *instructions;
+ std::vector<aco_ptr<Instruction>>::iterator it;
+
+ Builder(Program *pgm) : program(pgm), use_iterator(false), start(false), instructions(NULL) {}
+ Builder(Program *pgm, Block *block) : program(pgm), use_iterator(false), start(false), instructions(&block->instructions) {}
+ Builder(Program *pgm, std::vector<aco_ptr<Instruction>> *instrs) : program(pgm), use_iterator(false), start(false), instructions(instrs) {}
+
+ void moveEnd(Block *block) {
+ instructions = &block->instructions;
+ }
+
+ void reset() {
+ use_iterator = false;
+ start = false;
+ instructions = NULL;
+ }
+
+ void reset(Block *block) {
+ use_iterator = false;
+ start = false;
+ instructions = &block->instructions;
+ }
+
+ void reset(std::vector<aco_ptr<Instruction>> *instrs) {
+ use_iterator = false;
+ start = false;
+ instructions = instrs;
+ }
+
+ Result insert(aco_ptr<Instruction> instr) {
+ Instruction *instr_ptr = instr.get();
+ if (instructions) {
+ if (use_iterator) {
+ it = instructions->emplace(it, std::move(instr));
+ if (forwards)
+ it = std::next(it);
+ } else if (!start) {
+ instructions->emplace_back(std::move(instr));
+ } else {
+ instructions->emplace(instructions->begin(), std::move(instr));
+ }
+ }
+ return Result(instr_ptr);
+ }
+
+ Result insert(Instruction* instr) {
+ if (instructions) {
+ if (use_iterator) {
+ it = instructions->emplace(it, aco_ptr<Instruction>(instr));
+ if (forwards)
+ it = std::next(it);
+ } else if (!start) {
+ instructions->emplace_back(aco_ptr<Instruction>(instr));
+ } else {
+ instructions->emplace(instructions->begin(), aco_ptr<Instruction>(instr));
+ }
+ }
+ return Result(instr);
+ }
+
+ Temp tmp(RegClass rc) {
+ return (Temp){program->allocateId(), rc};
+ }
+
+ Temp tmp(RegType type, unsigned size) {
+ return (Temp){program->allocateId(), RegClass(type, size)};
+ }
+
+ Definition def(RegClass rc) {
+ return Definition((Temp){program->allocateId(), rc});
+ }
+
+ Definition def(RegType type, unsigned size) {
+ return Definition((Temp){program->allocateId(), RegClass(type, size)});
+ }
+
+ Definition def(RegClass rc, PhysReg reg) {
+ return Definition(program->allocateId(), reg, rc);
+ }
+
+% for fixed in ['m0', 'vcc', 'exec', 'scc']:
+ Operand ${fixed}(Temp tmp) {
+ Operand op(tmp);
+ op.setFixed(aco::${fixed});
+ return op;
+ }
+
+ Definition ${fixed}(Definition def) {
+ def.setFixed(aco::${fixed});
+ return def;
+ }
+
+ Definition hint_${fixed}(Definition def) {
+ def.setHint(aco::${fixed});
+ return def;
+ }
+
+% endfor
+ /* hand-written helpers */
+ Temp as_uniform(Op op)
+ {
+ assert(op.op.isTemp());
+ if (op.op.getTemp().type() == RegType::vgpr)
+ return pseudo(aco_opcode::p_as_uniform, def(RegType::sgpr, op.op.size()), op);
+ else
+ return op.op.getTemp();
+ }
+
+ Result v_mul_imm(Definition dst, Temp tmp, uint32_t imm, bool bits24=false)
+ {
+ assert(tmp.type() == RegType::vgpr);
+ if (imm == 0) {
+ return vop1(aco_opcode::v_mov_b32, dst, Operand(0u));
+ } else if (imm == 1) {
+ return copy(dst, Operand(tmp));
+ } else if (util_is_power_of_two_or_zero(imm)) {
+ return vop2(aco_opcode::v_lshlrev_b32, dst, Operand((uint32_t)ffs(imm) - 1u), tmp);
+ } else if (bits24) {
+ return vop2(aco_opcode::v_mul_u32_u24, dst, Operand(imm), tmp);
+ } else {
+ Temp imm_tmp = copy(def(v1), Operand(imm));
+ return vop3(aco_opcode::v_mul_lo_u32, dst, imm_tmp, tmp);
+ }
+ }
+
+ Result v_mul24_imm(Definition dst, Temp tmp, uint32_t imm)
+ {
+ return v_mul_imm(dst, tmp, imm, true);
+ }
+
+ Result copy(Definition dst, Op op_) {
+ Operand op = op_.op;
+ if (dst.regClass() == s1 && op.size() == 1 && op.isLiteral()) {
+ uint32_t imm = op.constantValue();
+ if (imm >= 0xffff8000 || imm <= 0x7fff) {
+ return sopk(aco_opcode::s_movk_i32, dst, imm & 0xFFFFu);
+ } else if (util_bitreverse(imm) <= 64 || util_bitreverse(imm) >= 0xFFFFFFF0) {
+ uint32_t rev = util_bitreverse(imm);
+ return dst.regClass() == v1 ?
+ vop1(aco_opcode::v_bfrev_b32, dst, Operand(rev)) :
+ sop1(aco_opcode::s_brev_b32, dst, Operand(rev));
+ } else if (imm != 0) {
+ unsigned start = (ffs(imm) - 1) & 0x1f;
+ unsigned size = util_bitcount(imm) & 0x1f;
+ if ((((1u << size) - 1u) << start) == imm)
+ return sop2(aco_opcode::s_bfm_b32, dst, Operand(size), Operand(start));
+ }
+ }
+
+ if (dst.regClass() == s2) {
+ return sop1(aco_opcode::s_mov_b64, dst, op);
+ } else if (op.size() > 1) {
+ return pseudo(aco_opcode::p_create_vector, dst, op);
+ } else if (dst.regClass() == v1 || dst.regClass() == v1.as_linear()) {
+ return vop1(aco_opcode::v_mov_b32, dst, op);
+ } else {
+ assert(dst.regClass() == s1);
+ return sop1(aco_opcode::s_mov_b32, dst, op);
+ }
+ }
+
+ Result vadd32(Definition dst, Op a, Op b, bool carry_out=false, Op carry_in=Op(Operand(s2))) {
+ if (!b.op.isTemp() || b.op.regClass().type() != RegType::vgpr)
+ std::swap(a, b);
+ assert(b.op.isTemp() && b.op.regClass().type() == RegType::vgpr);
+
+ if (!carry_in.op.isUndefined())
+ return vop2(aco_opcode::v_addc_co_u32, Definition(dst), hint_vcc(def(s2)), a, b, carry_in);
+ else if (program->chip_class < GFX9 || carry_out)
+ return vop2(aco_opcode::v_add_co_u32, Definition(dst), hint_vcc(def(s2)), a, b);
+ else
+ return vop2(aco_opcode::v_add_u32, Definition(dst), a, b);
+ }
+
+ Result vsub32(Definition dst, Op a, Op b, bool carry_out=false, Op borrow=Op(Operand(s2)))
+ {
+ if (!borrow.op.isUndefined() || program->chip_class < GFX9)
+ carry_out = true;
+
+ bool reverse = !b.op.isTemp() || b.op.regClass().type() != RegType::vgpr;
+ if (reverse)
+ std::swap(a, b);
+ assert(b.op.isTemp() && b.op.regClass().type() == RegType::vgpr);
+
+ aco_opcode op;
+ Temp carry;
+ if (carry_out) {
+ carry = tmp(s2);
+ if (borrow.op.isUndefined())
+ op = reverse ? aco_opcode::v_subrev_co_u32 : aco_opcode::v_sub_co_u32;
+ else
+ op = reverse ? aco_opcode::v_subbrev_co_u32 : aco_opcode::v_subb_co_u32;
+ } else {
+ op = reverse ? aco_opcode::v_subrev_u32 : aco_opcode::v_sub_u32;
+ }
+
+ int num_ops = borrow.op.isUndefined() ? 2 : 3;
+ int num_defs = carry_out ? 2 : 1;
+ aco_ptr<Instruction> sub{create_instruction<VOP2_instruction>(op, Format::VOP2, num_ops, num_defs)};
+ sub->operands[0] = a.op;
+ sub->operands[1] = b.op;
+ if (!borrow.op.isUndefined())
+ sub->operands[2] = borrow.op;
+ sub->definitions[0] = dst;
+ if (carry_out) {
+ sub->definitions[1] = Definition(carry);
+ sub->definitions[1].setHint(aco::vcc);
+ }
+ return insert(std::move(sub));
+ }
+<%
+import itertools
+formats = [("pseudo", [Format.PSEUDO], 'Pseudo_instruction', list(itertools.product(range(5), range(5))) + [(8, 1), (1, 8)]),
+ ("sop1", [Format.SOP1], 'SOP1_instruction', [(1, 1), (2, 1), (3, 2)]),
+ ("sop2", [Format.SOP2], 'SOP2_instruction', itertools.product([1, 2], [2, 3])),
+ ("sopk", [Format.SOPK], 'SOPK_instruction', itertools.product([0, 1, 2], [0, 1])),
+ ("sopp", [Format.SOPP], 'SOPP_instruction', [(0, 0), (0, 1)]),
+ ("sopc", [Format.SOPC], 'SOPC_instruction', [(1, 2)]),
+ ("smem", [Format.SMEM], 'SMEM_instruction', [(0, 4), (0, 3), (1, 0), (1, 3), (1, 2), (0, 0)]),
+ ("ds", [Format.DS], 'DS_instruction', [(1, 1), (1, 2), (0, 3), (0, 4)]),
+ ("mubuf", [Format.MUBUF], 'MUBUF_instruction', [(0, 4), (1, 3)]),
+ ("mimg", [Format.MIMG], 'MIMG_instruction', [(0, 4), (1, 3), (0, 3), (1, 2)]), #TODO(pendingchaos): less shapes?
+ ("exp", [Format.EXP], 'Export_instruction', [(0, 4)]),
+ ("branch", [Format.PSEUDO_BRANCH], 'Pseudo_branch_instruction', itertools.product([0], [0, 1])),
+ ("barrier", [Format.PSEUDO_BARRIER], 'Pseudo_barrier_instruction', [(0, 0)]),
+ ("reduction", [Format.PSEUDO_REDUCTION], 'Pseudo_reduction_instruction', [(3, 2)]),
+ ("vop1", [Format.VOP1], 'VOP1_instruction', [(1, 1), (2, 2)]),
+ ("vop2", [Format.VOP2], 'VOP2_instruction', itertools.product([1, 2], [2, 3])),
+ ("vopc", [Format.VOPC], 'VOPC_instruction', itertools.product([1, 2], [2])),
+ ("vop3", [Format.VOP3A], 'VOP3A_instruction', [(1, 3), (1, 2), (1, 1), (2, 2)]),
+ ("vintrp", [Format.VINTRP], 'Interp_instruction', [(1, 2), (1, 3)]),
+ ("vop1_dpp", [Format.VOP1, Format.DPP], 'DPP_instruction', [(1, 1)]),
+ ("vop2_dpp", [Format.VOP2, Format.DPP], 'DPP_instruction', itertools.product([1, 2], [2, 3])),
+ ("vopc_dpp", [Format.VOPC, Format.DPP], 'DPP_instruction', itertools.product([1, 2], [2])),
+ ("vop1_e64", [Format.VOP1, Format.VOP3A], 'VOP3A_instruction', itertools.product([1], [1])),
+ ("vop2_e64", [Format.VOP2, Format.VOP3A], 'VOP3A_instruction', itertools.product([1, 2], [2, 3])),
+ ("vopc_e64", [Format.VOPC, Format.VOP3A], 'VOP3A_instruction', itertools.product([1, 2], [2])),
+ ("flat", [Format.FLAT], 'FLAT_instruction', [(0, 3), (1, 2)]),
+ ("global", [Format.GLOBAL], 'FLAT_instruction', [(0, 3), (1, 2)])]
+%>\\
+% for name, formats, struct, shapes in formats:
+ % for num_definitions, num_operands in shapes:
+ <%
+ args = ['aco_opcode opcode']
+ for i in range(num_definitions):
+ args.append('Definition def%d' % i)
+ for i in range(num_operands):
+ args.append('Op op%d' % i)
+ for f in formats:
+ args += f.get_builder_field_decls()
+ %>\\
+
+ Result ${name}(${', '.join(args)})
+ {
+ ${struct} *instr = create_instruction<${struct}>(opcode, (Format)(${'|'.join('(int)Format::%s' % f.name for f in formats)}), ${num_operands}, ${num_definitions});
+ % for i in range(num_definitions):
+ instr->definitions[${i}] = def${i};
+ % endfor
+ % for i in range(num_operands):
+ instr->operands[${i}] = op${i}.op;
+ % endfor
+ % for f in formats:
+ % for dest, field_name in zip(f.get_builder_field_dests(), f.get_builder_field_names()):
+ instr->${dest} = ${field_name};
+ % endfor
+ % endfor
+ return insert(instr);
+ }
+ % endfor
+% endfor
+};
+
+}
+#endif /* _ACO_BUILDER_ */"""
+
+from aco_opcodes import opcodes, Format
+from mako.template import Template
+
+print(Template(template).render(opcodes=opcodes, Format=Format))
diff --git a/src/amd/compiler/aco_dead_code_analysis.cpp b/src/amd/compiler/aco_dead_code_analysis.cpp
new file mode 100644
index 00000000000..f56718f0479
--- /dev/null
+++ b/src/amd/compiler/aco_dead_code_analysis.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright © 2019 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+
+#include <algorithm>
+
+/*
+ * Implements an analysis pass to determine the number of uses
+ * for each SSA-definition.
+ */
+
+namespace aco {
+namespace {
+
+struct dce_ctx {
+ int current_block;
+ std::vector<uint16_t> uses;
+ std::vector<std::vector<bool>> live;
+
+ dce_ctx(Program* program) : current_block(program->blocks.size() - 1), uses(program->peekAllocationId())
+ {
+ live.reserve(program->blocks.size());
+ for (Block& block : program->blocks)
+ live.emplace_back(block.instructions.size());
+ }
+};
+
+void process_block(dce_ctx& ctx, Block& block)
+{
+ std::vector<bool>& live = ctx.live[block.index];
+ assert(live.size() == block.instructions.size());
+ bool process_predecessors = false;
+ for (int idx = block.instructions.size() - 1; idx >= 0; idx--) {
+ if (live[idx])
+ continue;
+
+ aco_ptr<Instruction>& instr = block.instructions[idx];
+ const bool is_live = instr->definitions.empty() ||
+ std::any_of(instr->definitions.begin(), instr->definitions.end(),
+ [&ctx] (const Definition& def) { return !def.isTemp() || ctx.uses[def.tempId()];});
+
+ if (is_live) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp()) {
+ if (ctx.uses[op.tempId()] == 0)
+ process_predecessors = true;
+ ctx.uses[op.tempId()]++;
+ }
+ }
+ live[idx] = true;
+ }
+ }
+
+ if (process_predecessors) {
+ for (unsigned pred_idx : block.linear_preds)
+ ctx.current_block = std::max(ctx.current_block, (int) pred_idx);
+ }
+}
+
+} /* end namespace */
+
+std::vector<uint16_t> dead_code_analysis(Program *program) {
+
+ dce_ctx ctx(program);
+
+ while (ctx.current_block >= 0) {
+ unsigned next_block = ctx.current_block--;
+ process_block(ctx, program->blocks[next_block]);
+ }
+
+ /* add one use to exec to prevent startpgm from being removed */
+ aco_ptr<Instruction>& startpgm = program->blocks[0].instructions[0];
+ assert(startpgm->opcode == aco_opcode::p_startpgm);
+ ctx.uses[startpgm->definitions.back().tempId()]++;
+
+ return ctx.uses;
+}
+
+}
+
diff --git a/src/amd/compiler/aco_dominance.cpp b/src/amd/compiler/aco_dominance.cpp
new file mode 100644
index 00000000000..de5549eec1a
--- /dev/null
+++ b/src/amd/compiler/aco_dominance.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schürmann ([email protected])
+ *
+ */
+
+#ifndef ACO_DOMINANCE_CPP
+#define ACO_DOMINANCE_CPP
+
+#include "aco_ir.h"
+
+/*
+ * Implements the algorithms for computing the dominator tree from
+ * "A Simple, Fast Dominance Algorithm" by Cooper, Harvey, and Kennedy.
+ *
+ * Different from the paper, our CFG allows to compute the dominator tree
+ * in a single pass as it is guaranteed that the dominating predecessors
+ * are processed before the current block.
+ */
+
+namespace aco {
+
+void dominator_tree(Program* program)
+{
+ program->blocks[0].logical_idom = 0;
+ program->blocks[0].linear_idom = 0;
+
+ for (unsigned i = 1; i < program->blocks.size(); i++) {
+ Block& block = program->blocks[i];
+ int new_logical_idom = -1;
+ int new_linear_idom = -1;
+ for (unsigned pred_idx : block.logical_preds) {
+ if ((int) program->blocks[pred_idx].logical_idom == -1)
+ continue;
+
+ if (new_logical_idom == -1) {
+ new_logical_idom = pred_idx;
+ continue;
+ }
+
+ while ((int) pred_idx != new_logical_idom) {
+ if ((int) pred_idx > new_logical_idom)
+ pred_idx = program->blocks[pred_idx].logical_idom;
+ if ((int) pred_idx < new_logical_idom)
+ new_logical_idom = program->blocks[new_logical_idom].logical_idom;
+ }
+ }
+
+ for (unsigned pred_idx : block.linear_preds) {
+ if ((int) program->blocks[pred_idx].linear_idom == -1)
+ continue;
+
+ if (new_linear_idom == -1) {
+ new_linear_idom = pred_idx;
+ continue;
+ }
+
+ while ((int) pred_idx != new_linear_idom) {
+ if ((int) pred_idx > new_linear_idom)
+ pred_idx = program->blocks[pred_idx].linear_idom;
+ if ((int) pred_idx < new_linear_idom)
+ new_linear_idom = program->blocks[new_linear_idom].linear_idom;
+ }
+ }
+
+ block.logical_idom = new_logical_idom;
+ block.linear_idom = new_linear_idom;
+ }
+}
+
+}
+#endif
diff --git a/src/amd/compiler/aco_insert_NOPs.cpp b/src/amd/compiler/aco_insert_NOPs.cpp
new file mode 100644
index 00000000000..fea1364072e
--- /dev/null
+++ b/src/amd/compiler/aco_insert_NOPs.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright © 2019 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+
+namespace aco {
+namespace {
+
+struct NOP_ctx {
+ /* just initialize these with something less than max NOPs */
+ int VALU_wrexec = -10;
+ int VALU_wrvcc = -10;
+ int VALU_wrsgpr = -10;
+ enum chip_class chip_class;
+ unsigned vcc_physical;
+ NOP_ctx(Program* program) : chip_class(program->chip_class) {
+ vcc_physical = program->config->num_sgprs - 2;
+ }
+};
+
+bool VALU_writes_sgpr(aco_ptr<Instruction>& instr)
+{
+ if ((uint32_t) instr->format & (uint32_t) Format::VOPC)
+ return true;
+ if (instr->isVOP3() && instr->definitions.size() == 2)
+ return true;
+ if (instr->opcode == aco_opcode::v_readfirstlane_b32 || instr->opcode == aco_opcode::v_readlane_b32)
+ return true;
+ return false;
+}
+
+bool regs_intersect(PhysReg a_reg, unsigned a_size, PhysReg b_reg, unsigned b_size)
+{
+ return a_reg > b_reg ?
+ (a_reg - b_reg < b_size) :
+ (b_reg - a_reg < a_size);
+}
+
+int handle_instruction(NOP_ctx& ctx, aco_ptr<Instruction>& instr,
+ std::vector<aco_ptr<Instruction>>& old_instructions,
+ std::vector<aco_ptr<Instruction>>& new_instructions)
+{
+ int new_idx = new_instructions.size();
+
+ // TODO: setreg / getreg / m0 writes
+ // TODO: try to schedule the NOP-causing instruction up to reduce the number of stall cycles
+
+ /* break off from prevous SMEM clause if needed */
+ if (instr->format == Format::SMEM && ctx.chip_class >= GFX8) {
+ const bool is_store = instr->definitions.empty();
+ for (int pred_idx = new_idx - 1; pred_idx >= 0; pred_idx--) {
+ aco_ptr<Instruction>& pred = new_instructions[pred_idx];
+ if (pred->format != Format::SMEM)
+ break;
+
+ /* Don't allow clauses with store instructions since the clause's
+ * instructions may use the same address. */
+ if (is_store || pred->definitions.empty())
+ return 1;
+
+ Definition& instr_def = instr->definitions[0];
+ Definition& pred_def = pred->definitions[0];
+
+ /* ISA reference doesn't say anything about this, but best to be safe */
+ if (regs_intersect(instr_def.physReg(), instr_def.size(), pred_def.physReg(), pred_def.size()))
+ return 1;
+
+ for (const Operand& op : pred->operands) {
+ if (op.isConstant() || !op.isFixed())
+ continue;
+ if (regs_intersect(instr_def.physReg(), instr_def.size(), op.physReg(), op.size()))
+ return 1;
+ }
+ for (const Operand& op : instr->operands) {
+ if (op.isConstant() || !op.isFixed())
+ continue;
+ if (regs_intersect(pred_def.physReg(), pred_def.size(), op.physReg(), op.size()))
+ return 1;
+ }
+ }
+ } else if (instr->isVALU() || instr->format == Format::VINTRP) {
+ int NOPs = 0;
+
+ if (instr->isDPP()) {
+ /* VALU does not forward EXEC to DPP. */
+ if (ctx.VALU_wrexec + 5 >= new_idx)
+ NOPs = 5 + ctx.VALU_wrexec - new_idx + 1;
+
+ /* VALU DPP reads VGPR written by VALU */
+ for (int pred_idx = new_idx - 1; pred_idx >= 0 && pred_idx >= new_idx - 2; pred_idx--) {
+ aco_ptr<Instruction>& pred = new_instructions[pred_idx];
+ if ((pred->isVALU() || pred->format == Format::VINTRP) &&
+ !pred->definitions.empty() &&
+ pred->definitions[0].physReg() == instr->operands[0].physReg()) {
+ NOPs = std::max(NOPs, 2 + pred_idx - new_idx + 1);
+ break;
+ }
+ }
+ }
+
+ /* SALU writes M0 */
+ if (instr->format == Format::VINTRP && new_idx > 0 && ctx.chip_class >= GFX9) {
+ aco_ptr<Instruction>& pred = new_instructions.back();
+ if (pred->isSALU() &&
+ !pred->definitions.empty() &&
+ pred->definitions[0].physReg() == m0)
+ NOPs = std::max(NOPs, 1);
+ }
+
+ for (const Operand& op : instr->operands) {
+ /* VALU which uses VCCZ */
+ if (op.physReg() == PhysReg{251} &&
+ ctx.VALU_wrvcc + 5 >= new_idx)
+ NOPs = std::max(NOPs, 5 + ctx.VALU_wrvcc - new_idx + 1);
+
+ /* VALU which uses EXECZ */
+ if (op.physReg() == PhysReg{252} &&
+ ctx.VALU_wrexec + 5 >= new_idx)
+ NOPs = std::max(NOPs, 5 + ctx.VALU_wrexec - new_idx + 1);
+
+ /* VALU which reads VCC as a constant */
+ if (ctx.VALU_wrvcc + 1 >= new_idx) {
+ for (unsigned k = 0; k < op.size(); k++) {
+ unsigned reg = op.physReg() + k;
+ if (reg == ctx.vcc_physical || reg == ctx.vcc_physical + 1)
+ NOPs = std::max(NOPs, 1);
+ }
+ }
+ }
+
+ switch (instr->opcode) {
+ case aco_opcode::v_readlane_b32:
+ case aco_opcode::v_writelane_b32: {
+ if (ctx.VALU_wrsgpr + 4 < new_idx)
+ break;
+ PhysReg reg = instr->operands[1].physReg();
+ for (int pred_idx = new_idx - 1; pred_idx >= 0 && pred_idx >= new_idx - 4; pred_idx--) {
+ aco_ptr<Instruction>& pred = new_instructions[pred_idx];
+ if (!pred->isVALU() || !VALU_writes_sgpr(pred))
+ continue;
+ for (const Definition& def : pred->definitions) {
+ if (def.physReg() == reg)
+ NOPs = std::max(NOPs, 4 + pred_idx - new_idx + 1);
+ }
+ }
+ break;
+ }
+ case aco_opcode::v_div_fmas_f32:
+ case aco_opcode::v_div_fmas_f64: {
+ if (ctx.VALU_wrvcc + 4 >= new_idx)
+ NOPs = std::max(NOPs, 4 + ctx.VALU_wrvcc - new_idx + 1);
+ break;
+ }
+ default:
+ break;
+ }
+
+ /* Write VGPRs holding writedata > 64 bit from MIMG/MUBUF instructions */
+ // FIXME: handle case if the last instruction of a block without branch is such store
+ // TODO: confirm that DS instructions cannot cause WAR hazards here
+ if (new_idx > 0) {
+ aco_ptr<Instruction>& pred = new_instructions.back();
+ if (pred->isVMEM() &&
+ pred->operands.size() == 4 &&
+ pred->operands[3].size() > 2 &&
+ pred->operands[1].size() != 8 &&
+ (pred->format != Format::MUBUF || pred->operands[2].physReg() >= 102)) {
+ /* Ops that use a 256-bit T# do not need a wait state.
+ * BUFFER_STORE_* operations that use an SGPR for "offset"
+ * do not require any wait states. */
+ PhysReg wrdata = pred->operands[3].physReg();
+ unsigned size = pred->operands[3].size();
+ assert(wrdata >= 256);
+ for (const Definition& def : instr->definitions) {
+ if (regs_intersect(def.physReg(), def.size(), wrdata, size))
+ NOPs = std::max(NOPs, 1);
+ }
+ }
+ }
+
+ if (VALU_writes_sgpr(instr)) {
+ for (const Definition& def : instr->definitions) {
+ if (def.physReg() == vcc)
+ ctx.VALU_wrvcc = NOPs ? new_idx : new_idx + 1;
+ else if (def.physReg() == exec)
+ ctx.VALU_wrexec = NOPs ? new_idx : new_idx + 1;
+ else if (def.physReg() <= 102)
+ ctx.VALU_wrsgpr = NOPs ? new_idx : new_idx + 1;
+ }
+ }
+ return NOPs;
+ } else if (instr->isVMEM() && ctx.VALU_wrsgpr + 5 >= new_idx) {
+ /* If the VALU writes the SGPR that is used by a VMEM, the user must add five wait states. */
+ for (int pred_idx = new_idx - 1; pred_idx >= 0 && pred_idx >= new_idx - 5; pred_idx--) {
+ aco_ptr<Instruction>& pred = new_instructions[pred_idx];
+ if (!(pred->isVALU() && VALU_writes_sgpr(pred)))
+ continue;
+
+ for (const Definition& def : pred->definitions) {
+ if (def.physReg() > 102)
+ continue;
+
+ if (instr->operands.size() > 1 &&
+ regs_intersect(instr->operands[1].physReg(), instr->operands[1].size(),
+ def.physReg(), def.size())) {
+ return 5 + pred_idx - new_idx + 1;
+ }
+
+ if (instr->operands.size() > 2 &&
+ regs_intersect(instr->operands[2].physReg(), instr->operands[2].size(),
+ def.physReg(), def.size())) {
+ return 5 + pred_idx - new_idx + 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+void handle_block(NOP_ctx& ctx, Block& block)
+{
+ std::vector<aco_ptr<Instruction>> instructions;
+ instructions.reserve(block.instructions.size());
+ for (unsigned i = 0; i < block.instructions.size(); i++) {
+ aco_ptr<Instruction>& instr = block.instructions[i];
+ unsigned NOPs = handle_instruction(ctx, instr, block.instructions, instructions);
+ if (NOPs) {
+ // TODO: try to move the instruction down
+ /* create NOP */
+ aco_ptr<SOPP_instruction> nop{create_instruction<SOPP_instruction>(aco_opcode::s_nop, Format::SOPP, 0, 0)};
+ nop->imm = NOPs - 1;
+ nop->block = -1;
+ instructions.emplace_back(std::move(nop));
+ }
+
+ instructions.emplace_back(std::move(instr));
+ }
+
+ ctx.VALU_wrvcc -= instructions.size();
+ ctx.VALU_wrexec -= instructions.size();
+ ctx.VALU_wrsgpr -= instructions.size();
+ block.instructions = std::move(instructions);
+}
+
+} /* end namespace */
+
+
+void insert_NOPs(Program* program)
+{
+ NOP_ctx ctx(program);
+ for (Block& block : program->blocks) {
+ if (block.instructions.empty())
+ continue;
+
+ handle_block(ctx, block);
+ }
+}
+
+}
diff --git a/src/amd/compiler/aco_insert_exec_mask.cpp b/src/amd/compiler/aco_insert_exec_mask.cpp
new file mode 100644
index 00000000000..7886a4c77e2
--- /dev/null
+++ b/src/amd/compiler/aco_insert_exec_mask.cpp
@@ -0,0 +1,1078 @@
+/*
+ * Copyright © 2019 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+#include "aco_builder.h"
+
+namespace aco {
+
+namespace {
+
+enum WQMState : uint8_t {
+ Unspecified = 0,
+ Exact = 1 << 0,
+ WQM = 1 << 1, /* with control flow applied */
+ Preserve_WQM = 1 << 2,
+ Exact_Branch = 1 << 3,
+};
+
+enum mask_type : uint8_t {
+ mask_type_global = 1 << 0,
+ mask_type_exact = 1 << 1,
+ mask_type_wqm = 1 << 2,
+ mask_type_loop = 1 << 3, /* active lanes of a loop */
+ mask_type_initial = 1 << 4, /* initially active lanes */
+};
+
+struct wqm_ctx {
+ Program* program;
+ /* state for WQM propagation */
+ std::set<unsigned> worklist;
+ std::vector<uint16_t> defined_in;
+ std::vector<bool> needs_wqm;
+ std::vector<bool> branch_wqm; /* true if the branch condition in this block should be in wqm */
+ bool loop;
+ bool wqm;
+ wqm_ctx(Program* program) : program(program),
+ defined_in(program->peekAllocationId(), 0xFFFF),
+ needs_wqm(program->peekAllocationId()),
+ branch_wqm(program->blocks.size()),
+ loop(false),
+ wqm(false)
+ {
+ for (unsigned i = 0; i < program->blocks.size(); i++)
+ worklist.insert(i);
+ }
+};
+
+struct loop_info {
+ Block* loop_header;
+ uint16_t num_exec_masks;
+ uint8_t needs;
+ bool has_divergent_break;
+ bool has_divergent_continue;
+ bool has_discard;
+ loop_info(Block* b, uint16_t num, uint8_t needs, bool breaks, bool cont, bool discard) :
+ loop_header(b), num_exec_masks(num), needs(needs), has_divergent_break(breaks),
+ has_divergent_continue(cont), has_discard(discard) {}
+};
+
+struct block_info {
+ std::vector<std::pair<Temp, uint8_t>> exec;
+ std::vector<WQMState> instr_needs;
+ uint8_t block_needs;
+ uint8_t ever_again_needs;
+ /* more... */
+};
+
+struct exec_ctx {
+ Program *program;
+ std::vector<block_info> info;
+ std::vector<loop_info> loop;
+ bool handle_wqm = false;
+ exec_ctx(Program *program) : program(program), info(program->blocks.size()) {}
+};
+
+bool pred_by_exec_mask(aco_ptr<Instruction>& instr) {
+ if (instr->format == Format::SMEM || instr->isSALU())
+ return false;
+ if (instr->format == Format::PSEUDO_BARRIER)
+ return false;
+
+ if (instr->format == Format::PSEUDO) {
+ switch (instr->opcode) {
+ case aco_opcode::p_create_vector:
+ return instr->definitions[0].getTemp().type() == RegType::vgpr;
+ case aco_opcode::p_extract_vector:
+ case aco_opcode::p_split_vector:
+ return instr->operands[0].getTemp().type() == RegType::vgpr;
+ case aco_opcode::p_spill:
+ case aco_opcode::p_reload:
+ return false;
+ default:
+ break;
+ }
+ }
+
+ if (instr->opcode == aco_opcode::v_readlane_b32 ||
+ instr->opcode == aco_opcode::v_writelane_b32)
+ return false;
+
+ return true;
+}
+
+bool needs_exact(aco_ptr<Instruction>& instr) {
+ if (instr->format == Format::MUBUF) {
+ MUBUF_instruction *mubuf = static_cast<MUBUF_instruction *>(instr.get());
+ return mubuf->disable_wqm;
+ } else if (instr->format == Format::MTBUF) {
+ MTBUF_instruction *mtbuf = static_cast<MTBUF_instruction *>(instr.get());
+ return mtbuf->disable_wqm;
+ } else if (instr->format == Format::MIMG) {
+ MIMG_instruction *mimg = static_cast<MIMG_instruction *>(instr.get());
+ return mimg->disable_wqm;
+ } else {
+ return instr->format == Format::EXP || instr->opcode == aco_opcode::p_fs_buffer_store_smem;
+ }
+}
+
+void set_needs_wqm(wqm_ctx &ctx, Temp tmp)
+{
+ if (!ctx.needs_wqm[tmp.id()]) {
+ ctx.needs_wqm[tmp.id()] = true;
+ if (ctx.defined_in[tmp.id()] != 0xFFFF)
+ ctx.worklist.insert(ctx.defined_in[tmp.id()]);
+ }
+}
+
+void mark_block_wqm(wqm_ctx &ctx, unsigned block_idx)
+{
+ if (ctx.branch_wqm[block_idx])
+ return;
+
+ ctx.branch_wqm[block_idx] = true;
+ Block& block = ctx.program->blocks[block_idx];
+ aco_ptr<Instruction>& branch = block.instructions.back();
+
+ if (branch->opcode != aco_opcode::p_branch) {
+ assert(!branch->operands.empty() && branch->operands[0].isTemp());
+ set_needs_wqm(ctx, branch->operands[0].getTemp());
+ }
+
+ /* TODO: this sets more branch conditions to WQM than it needs to
+ * it should be enough to stop at the "exec mask top level" */
+ if (block.kind & block_kind_top_level)
+ return;
+
+ for (unsigned pred_idx : block.logical_preds)
+ mark_block_wqm(ctx, pred_idx);
+}
+
+void get_block_needs(wqm_ctx &ctx, exec_ctx &exec_ctx, Block* block)
+{
+ block_info& info = exec_ctx.info[block->index];
+
+ std::vector<WQMState> instr_needs(block->instructions.size());
+
+ if (block->kind & block_kind_top_level) {
+ if (ctx.loop && ctx.wqm) {
+ /* mark all break conditions as WQM */
+ unsigned block_idx = block->index + 1;
+ while (!(ctx.program->blocks[block_idx].kind & block_kind_top_level)) {
+ if (ctx.program->blocks[block_idx].kind & block_kind_break)
+ mark_block_wqm(ctx, block_idx);
+ block_idx++;
+ }
+ } else if (ctx.loop && !ctx.wqm) {
+ /* Ensure a branch never results in an exec mask with only helper
+ * invocations (which can cause a loop to repeat infinitively if it's
+ * break branches are done in exact). */
+ unsigned block_idx = block->index;
+ do {
+ if ((ctx.program->blocks[block_idx].kind & block_kind_branch))
+ exec_ctx.info[block_idx].block_needs |= Exact_Branch;
+ block_idx++;
+ } while (!(ctx.program->blocks[block_idx].kind & block_kind_top_level));
+ }
+
+ ctx.loop = false;
+ ctx.wqm = false;
+ }
+
+ for (int i = block->instructions.size() - 1; i >= 0; --i)
+ {
+ aco_ptr<Instruction>& instr = block->instructions[i];
+
+ WQMState needs = needs_exact(instr) ? Exact : Unspecified;
+ bool propagate_wqm = instr->opcode == aco_opcode::p_wqm;
+ bool preserve_wqm = instr->opcode == aco_opcode::p_discard_if;
+ bool pred_by_exec = pred_by_exec_mask(instr);
+ for (const Definition& definition : instr->definitions) {
+ if (!definition.isTemp())
+ continue;
+ const unsigned def = definition.tempId();
+ ctx.defined_in[def] = block->index;
+ if (needs == Unspecified && ctx.needs_wqm[def]) {
+ needs = pred_by_exec ? WQM : Unspecified;
+ propagate_wqm = true;
+ }
+ }
+
+ if (propagate_wqm) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp()) {
+ set_needs_wqm(ctx, op.getTemp());
+ }
+ }
+ } else if (preserve_wqm && info.block_needs & WQM) {
+ needs = Preserve_WQM;
+ }
+
+ /* ensure the condition controlling the control flow for this phi is in WQM */
+ if (needs == WQM && instr->opcode == aco_opcode::p_phi) {
+ for (unsigned pred_idx : block->logical_preds)
+ mark_block_wqm(ctx, pred_idx);
+ }
+
+ instr_needs[i] = needs;
+ info.block_needs |= needs;
+ }
+
+ info.instr_needs = instr_needs;
+
+ /* for "if (<cond>) <wqm code>" or "while (<cond>) <wqm code>",
+ * <cond> should be computed in WQM */
+ if (info.block_needs & WQM && !(block->kind & block_kind_top_level)) {
+ for (unsigned pred_idx : block->logical_preds)
+ mark_block_wqm(ctx, pred_idx);
+ ctx.wqm = true;
+ }
+ if (block->kind & block_kind_loop_header)
+ ctx.loop = true;
+}
+
+void calculate_wqm_needs(exec_ctx& exec_ctx)
+{
+ wqm_ctx ctx(exec_ctx.program);
+
+ while (!ctx.worklist.empty()) {
+ unsigned block_index = *std::prev(ctx.worklist.end());
+ ctx.worklist.erase(std::prev(ctx.worklist.end()));
+
+ get_block_needs(ctx, exec_ctx, &exec_ctx.program->blocks[block_index]);
+ }
+
+ uint8_t ever_again_needs = 0;
+ for (int i = exec_ctx.program->blocks.size() - 1; i >= 0; i--) {
+ exec_ctx.info[i].ever_again_needs = ever_again_needs;
+ Block& block = exec_ctx.program->blocks[i];
+
+ if (block.kind & block_kind_needs_lowering)
+ exec_ctx.info[i].block_needs |= Exact;
+
+ /* if discard is used somewhere in nested CF, we need to preserve the WQM mask */
+ if ((block.kind & block_kind_discard ||
+ block.kind & block_kind_uses_discard_if) &&
+ ever_again_needs & WQM)
+ exec_ctx.info[i].block_needs |= Preserve_WQM;
+
+ ever_again_needs |= exec_ctx.info[i].block_needs & ~Exact_Branch;
+ if (block.kind & block_kind_discard ||
+ block.kind & block_kind_uses_discard_if)
+ ever_again_needs |= Exact;
+
+ /* don't propagate WQM preservation further than the next top_level block */
+ if (block.kind & block_kind_top_level)
+ ever_again_needs &= ~Preserve_WQM;
+ else
+ exec_ctx.info[i].block_needs &= ~Preserve_WQM;
+ }
+ exec_ctx.handle_wqm = true;
+}
+
+void transition_to_WQM(exec_ctx& ctx, Builder bld, unsigned idx)
+{
+ if (ctx.info[idx].exec.back().second & mask_type_wqm)
+ return;
+ if (ctx.info[idx].exec.back().second & mask_type_global) {
+ Temp exec_mask = ctx.info[idx].exec.back().first;
+ exec_mask = bld.sop1(aco_opcode::s_wqm_b64, bld.def(s2, exec), bld.def(s1, scc), exec_mask);
+ ctx.info[idx].exec.emplace_back(exec_mask, mask_type_global | mask_type_wqm);
+ return;
+ }
+ /* otherwise, the WQM mask should be one below the current mask */
+ ctx.info[idx].exec.pop_back();
+ assert(ctx.info[idx].exec.back().second & mask_type_wqm);
+ ctx.info[idx].exec.back().first = bld.pseudo(aco_opcode::p_parallelcopy, bld.def(s2, exec),
+ ctx.info[idx].exec.back().first);
+}
+
+void transition_to_Exact(exec_ctx& ctx, Builder bld, unsigned idx)
+{
+ if (ctx.info[idx].exec.back().second & mask_type_exact)
+ return;
+ if (ctx.info[idx].exec.back().second & mask_type_global) {
+ ctx.info[idx].exec.pop_back();
+ assert(ctx.info[idx].exec.back().second & mask_type_exact);
+ ctx.info[idx].exec.back().first = bld.pseudo(aco_opcode::p_parallelcopy, bld.def(s2, exec),
+ ctx.info[idx].exec.back().first);
+ return;
+ }
+ /* otherwise, we create an exact mask and push to the stack */
+ Temp wqm = ctx.info[idx].exec.back().first;
+ Temp exact = bld.tmp(s2);
+ wqm = bld.sop1(aco_opcode::s_and_saveexec_b64, bld.def(s2), bld.def(s1, scc),
+ bld.exec(Definition(exact)), ctx.info[idx].exec[0].first, bld.exec(wqm));
+ ctx.info[idx].exec.back().first = wqm;
+ ctx.info[idx].exec.emplace_back(exact, mask_type_exact);
+}
+
+unsigned add_coupling_code(exec_ctx& ctx, Block* block,
+ std::vector<aco_ptr<Instruction>>& instructions)
+{
+ unsigned idx = block->index;
+ Builder bld(ctx.program, &instructions);
+ std::vector<unsigned>& preds = block->linear_preds;
+
+ /* start block */
+ if (idx == 0) {
+ aco_ptr<Instruction>& startpgm = block->instructions[0];
+ assert(startpgm->opcode == aco_opcode::p_startpgm);
+ Temp exec_mask = startpgm->definitions.back().getTemp();
+ bld.insert(std::move(startpgm));
+
+ if (ctx.handle_wqm) {
+ ctx.info[0].exec.emplace_back(exec_mask, mask_type_global | mask_type_exact | mask_type_initial);
+ /* if this block only needs WQM, initialize already */
+ if (ctx.info[0].block_needs == WQM)
+ transition_to_WQM(ctx, bld, 0);
+ } else {
+ uint8_t mask = mask_type_global;
+ if (ctx.program->needs_wqm) {
+ exec_mask = bld.sop1(aco_opcode::s_wqm_b64, bld.def(s2, exec), bld.def(s1, scc), bld.exec(exec_mask));
+ mask |= mask_type_wqm;
+ } else {
+ mask |= mask_type_exact;
+ }
+ ctx.info[0].exec.emplace_back(exec_mask, mask);
+ }
+
+ return 1;
+ }
+
+ /* loop entry block */
+ if (block->kind & block_kind_loop_header) {
+ assert(preds[0] == idx - 1);
+ ctx.info[idx].exec = ctx.info[idx - 1].exec;
+ loop_info& info = ctx.loop.back();
+ while (ctx.info[idx].exec.size() > info.num_exec_masks)
+ ctx.info[idx].exec.pop_back();
+
+ /* create ssa names for outer exec masks */
+ if (info.has_discard) {
+ aco_ptr<Pseudo_instruction> phi;
+ for (int i = 0; i < info.num_exec_masks - 1; i++) {
+ phi.reset(create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, preds.size(), 1));
+ phi->definitions[0] = bld.def(s2);
+ phi->operands[0] = Operand(ctx.info[preds[0]].exec[i].first);
+ ctx.info[idx].exec[i].first = bld.insert(std::move(phi));
+ }
+ }
+
+ /* create ssa name for restore mask */
+ if (info.has_divergent_break) {
+ /* this phi might be trivial but ensures a parallelcopy on the loop header */
+ aco_ptr<Pseudo_instruction> phi{create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, preds.size(), 1)};
+ phi->definitions[0] = bld.def(s2);
+ phi->operands[0] = Operand(ctx.info[preds[0]].exec[info.num_exec_masks - 1].first);
+ ctx.info[idx].exec.back().first = bld.insert(std::move(phi));
+ }
+
+ /* create ssa name for loop active mask */
+ aco_ptr<Pseudo_instruction> phi{create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, preds.size(), 1)};
+ if (info.has_divergent_continue)
+ phi->definitions[0] = bld.def(s2);
+ else
+ phi->definitions[0] = bld.def(s2, exec);
+ phi->operands[0] = Operand(ctx.info[preds[0]].exec.back().first);
+ Temp loop_active = bld.insert(std::move(phi));
+
+ if (info.has_divergent_break) {
+ uint8_t mask_type = (ctx.info[idx].exec.back().second & (mask_type_wqm | mask_type_exact)) | mask_type_loop;
+ ctx.info[idx].exec.emplace_back(loop_active, mask_type);
+ } else {
+ ctx.info[idx].exec.back().first = loop_active;
+ ctx.info[idx].exec.back().second |= mask_type_loop;
+ }
+
+ /* create a parallelcopy to move the active mask to exec */
+ unsigned i = 0;
+ if (info.has_divergent_continue) {
+ while (block->instructions[i]->opcode != aco_opcode::p_logical_start) {
+ bld.insert(std::move(block->instructions[i]));
+ i++;
+ }
+ uint8_t mask_type = ctx.info[idx].exec.back().second & (mask_type_wqm | mask_type_exact);
+ ctx.info[idx].exec.emplace_back(bld.pseudo(aco_opcode::p_parallelcopy, bld.def(s2, exec),
+ ctx.info[idx].exec.back().first), mask_type);
+ }
+
+ return i;
+ }
+
+ /* loop exit block */
+ if (block->kind & block_kind_loop_exit) {
+ Block* header = ctx.loop.back().loop_header;
+ loop_info& info = ctx.loop.back();
+
+ for (ASSERTED unsigned pred : preds)
+ assert(ctx.info[pred].exec.size() >= info.num_exec_masks);
+
+ /* fill the loop header phis */
+ std::vector<unsigned>& header_preds = header->linear_preds;
+ int k = 0;
+ if (info.has_discard) {
+ while (k < info.num_exec_masks - 1) {
+ aco_ptr<Instruction>& phi = header->instructions[k];
+ assert(phi->opcode == aco_opcode::p_linear_phi);
+ for (unsigned i = 1; i < phi->operands.size(); i++)
+ phi->operands[i] = Operand(ctx.info[header_preds[i]].exec[k].first);
+ k++;
+ }
+ }
+ aco_ptr<Instruction>& phi = header->instructions[k++];
+ assert(phi->opcode == aco_opcode::p_linear_phi);
+ for (unsigned i = 1; i < phi->operands.size(); i++)
+ phi->operands[i] = Operand(ctx.info[header_preds[i]].exec[info.num_exec_masks - 1].first);
+
+ if (info.has_divergent_break) {
+ aco_ptr<Instruction>& phi = header->instructions[k];
+ assert(phi->opcode == aco_opcode::p_linear_phi);
+ for (unsigned i = 1; i < phi->operands.size(); i++)
+ phi->operands[i] = Operand(ctx.info[header_preds[i]].exec[info.num_exec_masks].first);
+ }
+
+ assert(!(block->kind & block_kind_top_level) || info.num_exec_masks <= 2);
+
+ /* create the loop exit phis if not trivial */
+ for (unsigned k = 0; k < info.num_exec_masks; k++) {
+ Temp same = ctx.info[preds[0]].exec[k].first;
+ uint8_t type = ctx.info[header_preds[0]].exec[k].second;
+ bool trivial = true;
+
+ for (unsigned i = 1; i < preds.size() && trivial; i++) {
+ if (ctx.info[preds[i]].exec[k].first != same)
+ trivial = false;
+ }
+
+ if (trivial) {
+ ctx.info[idx].exec.emplace_back(same, type);
+ } else {
+ /* create phi for loop footer */
+ aco_ptr<Pseudo_instruction> phi{create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, preds.size(), 1)};
+ phi->definitions[0] = bld.def(s2);
+ for (unsigned i = 0; i < phi->operands.size(); i++)
+ phi->operands[i] = Operand(ctx.info[preds[i]].exec[k].first);
+ ctx.info[idx].exec.emplace_back(bld.insert(std::move(phi)), type);
+ }
+ }
+ assert(ctx.info[idx].exec.size() == info.num_exec_masks);
+
+ /* create a parallelcopy to move the live mask to exec */
+ unsigned i = 0;
+ while (block->instructions[i]->opcode != aco_opcode::p_logical_start) {
+ bld.insert(std::move(block->instructions[i]));
+ i++;
+ }
+
+ if (ctx.handle_wqm) {
+ if (block->kind & block_kind_top_level && ctx.info[idx].exec.size() == 2) {
+ if ((ctx.info[idx].block_needs | ctx.info[idx].ever_again_needs) == 0 ||
+ (ctx.info[idx].block_needs | ctx.info[idx].ever_again_needs) == Exact) {
+ ctx.info[idx].exec.back().second |= mask_type_global;
+ transition_to_Exact(ctx, bld, idx);
+ ctx.handle_wqm = false;
+ }
+ }
+ if (ctx.info[idx].block_needs == WQM)
+ transition_to_WQM(ctx, bld, idx);
+ else if (ctx.info[idx].block_needs == Exact)
+ transition_to_Exact(ctx, bld, idx);
+ }
+
+ ctx.info[idx].exec.back().first = bld.pseudo(aco_opcode::p_parallelcopy, bld.def(s2, exec),
+ ctx.info[idx].exec.back().first);
+
+ ctx.loop.pop_back();
+ return i;
+ }
+
+ if (preds.size() == 1) {
+ ctx.info[idx].exec = ctx.info[preds[0]].exec;
+ } else {
+ assert(preds.size() == 2);
+ /* if one of the predecessors ends in exact mask, we pop it from stack */
+ unsigned num_exec_masks = std::min(ctx.info[preds[0]].exec.size(),
+ ctx.info[preds[1]].exec.size());
+ if (block->kind & block_kind_top_level && !(block->kind & block_kind_merge))
+ num_exec_masks = std::min(num_exec_masks, 2u);
+
+ /* create phis for diverged exec masks */
+ for (unsigned i = 0; i < num_exec_masks; i++) {
+ bool in_exec = i == num_exec_masks - 1 && !(block->kind & block_kind_merge);
+ if (!in_exec && ctx.info[preds[0]].exec[i].first == ctx.info[preds[1]].exec[i].first) {
+ assert(ctx.info[preds[0]].exec[i].second == ctx.info[preds[1]].exec[i].second);
+ ctx.info[idx].exec.emplace_back(ctx.info[preds[0]].exec[i]);
+ continue;
+ }
+
+ Temp phi = bld.pseudo(aco_opcode::p_linear_phi, in_exec ? bld.def(s2, exec) : bld.def(s2),
+ ctx.info[preds[0]].exec[i].first,
+ ctx.info[preds[1]].exec[i].first);
+ uint8_t mask_type = ctx.info[preds[0]].exec[i].second & ctx.info[preds[1]].exec[i].second;
+ ctx.info[idx].exec.emplace_back(phi, mask_type);
+ }
+ }
+
+ unsigned i = 0;
+ while (block->instructions[i]->opcode == aco_opcode::p_phi ||
+ block->instructions[i]->opcode == aco_opcode::p_linear_phi) {
+ bld.insert(std::move(block->instructions[i]));
+ i++;
+ }
+
+ if (block->kind & block_kind_merge)
+ ctx.info[idx].exec.pop_back();
+
+ if (block->kind & block_kind_top_level && ctx.info[idx].exec.size() == 3) {
+ assert(ctx.info[idx].exec.back().second == mask_type_exact);
+ assert(block->kind & block_kind_merge);
+ ctx.info[idx].exec.pop_back();
+ }
+
+ /* try to satisfy the block's needs */
+ if (ctx.handle_wqm) {
+ if (block->kind & block_kind_top_level && ctx.info[idx].exec.size() == 2) {
+ if ((ctx.info[idx].block_needs | ctx.info[idx].ever_again_needs) == 0 ||
+ (ctx.info[idx].block_needs | ctx.info[idx].ever_again_needs) == Exact) {
+ ctx.info[idx].exec.back().second |= mask_type_global;
+ transition_to_Exact(ctx, bld, idx);
+ ctx.handle_wqm = false;
+ }
+ }
+ if (ctx.info[idx].block_needs == WQM)
+ transition_to_WQM(ctx, bld, idx);
+ else if (ctx.info[idx].block_needs == Exact)
+ transition_to_Exact(ctx, bld, idx);
+ }
+
+ if (block->kind & block_kind_merge) {
+ Temp restore = ctx.info[idx].exec.back().first;
+ ctx.info[idx].exec.back().first = bld.pseudo(aco_opcode::p_parallelcopy, bld.def(s2, exec), restore);
+ }
+
+ return i;
+}
+
+void lower_fs_buffer_store_smem(Builder& bld, bool need_check, aco_ptr<Instruction>& instr, Temp cur_exec)
+{
+ Operand offset = instr->operands[1];
+ if (need_check) {
+ /* if exec is zero, then use UINT32_MAX as an offset and make this store a no-op */
+ Temp nonempty = bld.sopc(aco_opcode::s_cmp_lg_u64, bld.def(s1, scc), cur_exec, Operand(0u));
+
+ if (offset.isLiteral())
+ offset = bld.sop1(aco_opcode::s_mov_b32, bld.def(s1), offset);
+
+ offset = bld.sop2(aco_opcode::s_cselect_b32, bld.hint_m0(bld.def(s1)),
+ offset, Operand(UINT32_MAX), bld.scc(nonempty));
+ } else if (offset.isConstant() && offset.constantValue() > 0xFFFFF) {
+ offset = bld.sop1(aco_opcode::s_mov_b32, bld.hint_m0(bld.def(s1)), offset);
+ }
+ if (!offset.isConstant())
+ offset.setFixed(m0);
+
+ switch (instr->operands[2].size()) {
+ case 1:
+ instr->opcode = aco_opcode::s_buffer_store_dword;
+ break;
+ case 2:
+ instr->opcode = aco_opcode::s_buffer_store_dwordx2;
+ break;
+ case 4:
+ instr->opcode = aco_opcode::s_buffer_store_dwordx4;
+ break;
+ default:
+ unreachable("Invalid SMEM buffer store size");
+ }
+ instr->operands[1] = offset;
+ /* as_uniform() needs to be done here so it's done in exact mode and helper
+ * lanes don't contribute. */
+ instr->operands[2] = Operand(bld.as_uniform(instr->operands[2]));
+}
+
+void process_instructions(exec_ctx& ctx, Block* block,
+ std::vector<aco_ptr<Instruction>>& instructions,
+ unsigned idx)
+{
+ WQMState state;
+ if (ctx.info[block->index].exec.back().second & mask_type_wqm)
+ state = WQM;
+ else {
+ assert(!ctx.handle_wqm || ctx.info[block->index].exec.back().second & mask_type_exact);
+ state = Exact;
+ }
+
+ /* if the block doesn't need both, WQM and Exact, we can skip processing the instructions */
+ bool process = (ctx.handle_wqm &&
+ (ctx.info[block->index].block_needs & state) !=
+ (ctx.info[block->index].block_needs & (WQM | Exact))) ||
+ block->kind & block_kind_uses_discard_if ||
+ block->kind & block_kind_needs_lowering;
+ if (!process) {
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(block->instructions.begin(), idx);
+ instructions.insert(instructions.end(),
+ std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(it),
+ std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(block->instructions.end()));
+ return;
+ }
+
+ Builder bld(ctx.program, &instructions);
+
+ for (; idx < block->instructions.size(); idx++) {
+ aco_ptr<Instruction> instr = std::move(block->instructions[idx]);
+
+ WQMState needs = ctx.handle_wqm ? ctx.info[block->index].instr_needs[idx] : Unspecified;
+
+ if (instr->opcode == aco_opcode::p_discard_if) {
+ if (ctx.info[block->index].block_needs & Preserve_WQM) {
+ assert(block->kind & block_kind_top_level);
+ transition_to_WQM(ctx, bld, block->index);
+ ctx.info[block->index].exec.back().second &= ~mask_type_global;
+ }
+ unsigned num = ctx.info[block->index].exec.size();
+ assert(num);
+ Operand cond = instr->operands[0];
+ instr.reset(create_instruction<Pseudo_instruction>(aco_opcode::p_discard_if, Format::PSEUDO, num + 1, num + 1));
+ for (unsigned i = 0; i < num; i++) {
+ instr->operands[i] = Operand(ctx.info[block->index].exec[i].first);
+ if (i == num - 1)
+ instr->operands[i].setFixed(exec);
+ Temp new_mask = bld.tmp(s2);
+ instr->definitions[i] = Definition(new_mask);
+ ctx.info[block->index].exec[i].first = new_mask;
+ }
+ assert((ctx.info[block->index].exec[0].second & mask_type_wqm) == 0);
+ instr->definitions[num - 1].setFixed(exec);
+ instr->operands[num] = cond;
+ instr->definitions[num] = bld.def(s1, scc);
+
+ } else if (needs == WQM && state != WQM) {
+ transition_to_WQM(ctx, bld, block->index);
+ state = WQM;
+ } else if (needs == Exact && state != Exact) {
+ transition_to_Exact(ctx, bld, block->index);
+ state = Exact;
+ }
+
+ if (instr->opcode == aco_opcode::p_is_helper || instr->opcode == aco_opcode::p_load_helper) {
+ Definition dst = instr->definitions[0];
+ if (state == Exact) {
+ instr.reset(create_instruction<SOP1_instruction>(aco_opcode::s_mov_b64, Format::SOP1, 1, 1));
+ instr->operands[0] = Operand(0u);
+ instr->definitions[0] = dst;
+ } else {
+ std::pair<Temp, uint8_t>& exact_mask = ctx.info[block->index].exec[0];
+ if (instr->opcode == aco_opcode::p_load_helper &&
+ !(ctx.info[block->index].exec[0].second & mask_type_initial)) {
+ /* find last initial exact mask */
+ for (int i = block->index; i >= 0; i--) {
+ if (ctx.program->blocks[i].kind & block_kind_top_level &&
+ ctx.info[i].exec[0].second & mask_type_initial) {
+ exact_mask = ctx.info[i].exec[0];
+ break;
+ }
+ }
+ }
+
+ assert(instr->opcode == aco_opcode::p_is_helper || exact_mask.second & mask_type_initial);
+ assert(exact_mask.second & mask_type_exact);
+
+ instr.reset(create_instruction<SOP2_instruction>(aco_opcode::s_andn2_b64, Format::SOP2, 2, 2));
+ instr->operands[0] = Operand(ctx.info[block->index].exec.back().first); /* current exec */
+ instr->operands[1] = Operand(exact_mask.first);
+ instr->definitions[0] = dst;
+ instr->definitions[1] = bld.def(s1, scc);
+ }
+ } else if (instr->opcode == aco_opcode::p_demote_to_helper) {
+ /* turn demote into discard_if with only exact masks */
+ assert((ctx.info[block->index].exec[0].second & (mask_type_exact | mask_type_global)) == (mask_type_exact | mask_type_global));
+ ctx.info[block->index].exec[0].second &= ~mask_type_initial;
+
+ int num = 0;
+ Temp cond;
+ if (instr->operands.empty()) {
+ /* transition to exact and set exec to zero */
+ Temp old_exec = ctx.info[block->index].exec.back().first;
+ Temp new_exec = bld.tmp(s2);
+ cond = bld.sop1(aco_opcode::s_and_saveexec_b64, bld.def(s2), bld.def(s1, scc),
+ bld.exec(Definition(new_exec)), Operand(0u), bld.exec(old_exec));
+ if (ctx.info[block->index].exec.back().second & mask_type_exact) {
+ ctx.info[block->index].exec.back().first = new_exec;
+ } else {
+ ctx.info[block->index].exec.back().first = cond;
+ ctx.info[block->index].exec.emplace_back(new_exec, mask_type_exact);
+ }
+ } else {
+ /* demote_if: transition to exact */
+ transition_to_Exact(ctx, bld, block->index);
+ assert(instr->operands[0].isTemp());
+ cond = instr->operands[0].getTemp();
+ num = 1;
+ }
+
+ for (unsigned i = 0; i < ctx.info[block->index].exec.size() - 1; i++)
+ num += ctx.info[block->index].exec[i].second & mask_type_exact ? 1 : 0;
+ instr.reset(create_instruction<Instruction>(aco_opcode::p_discard_if, Format::PSEUDO, num + 1, num + 1));
+ int k = 0;
+ for (unsigned i = 0; k < num; i++) {
+ if (ctx.info[block->index].exec[i].second & mask_type_exact) {
+ instr->operands[k] = Operand(ctx.info[block->index].exec[i].first);
+ Temp new_mask = bld.tmp(s2);
+ instr->definitions[k] = Definition(new_mask);
+ if (i == ctx.info[block->index].exec.size() - 1)
+ instr->definitions[k].setFixed(exec);
+ k++;
+ ctx.info[block->index].exec[i].first = new_mask;
+ }
+ }
+ assert(k == num);
+ instr->definitions[num] = bld.def(s1, scc);
+ instr->operands[num] = Operand(cond);
+ state = Exact;
+
+ } else if (instr->opcode == aco_opcode::p_fs_buffer_store_smem) {
+ bool need_check = ctx.info[block->index].exec.size() != 1 &&
+ !(ctx.info[block->index].exec[ctx.info[block->index].exec.size() - 2].second & Exact);
+ lower_fs_buffer_store_smem(bld, need_check, instr, ctx.info[block->index].exec.back().first);
+ }
+
+ bld.insert(std::move(instr));
+ }
+}
+
+void add_branch_code(exec_ctx& ctx, Block* block)
+{
+ unsigned idx = block->index;
+ Builder bld(ctx.program, block);
+
+ if (idx == ctx.program->blocks.size() - 1)
+ return;
+
+ /* try to disable wqm handling */
+ if (ctx.handle_wqm && block->kind & block_kind_top_level) {
+ if (ctx.info[idx].exec.size() == 3) {
+ assert(ctx.info[idx].exec[1].second == mask_type_wqm);
+ ctx.info[idx].exec.pop_back();
+ }
+ assert(ctx.info[idx].exec.size() <= 2);
+
+ if (ctx.info[idx].ever_again_needs == 0 ||
+ ctx.info[idx].ever_again_needs == Exact) {
+ /* transition to Exact */
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.pop_back();
+ ctx.info[idx].exec.back().second |= mask_type_global;
+ transition_to_Exact(ctx, bld, idx);
+ bld.insert(std::move(branch));
+ ctx.handle_wqm = false;
+
+ } else if (ctx.info[idx].block_needs & Preserve_WQM) {
+ /* transition to WQM and remove global flag */
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.pop_back();
+ transition_to_WQM(ctx, bld, idx);
+ ctx.info[idx].exec.back().second &= ~mask_type_global;
+ bld.insert(std::move(branch));
+ }
+ }
+
+ if (block->kind & block_kind_loop_preheader) {
+ /* collect information about the succeeding loop */
+ bool has_divergent_break = false;
+ bool has_divergent_continue = false;
+ bool has_discard = false;
+ uint8_t needs = 0;
+ unsigned loop_nest_depth = ctx.program->blocks[idx + 1].loop_nest_depth;
+
+ for (unsigned i = idx + 1; ctx.program->blocks[i].loop_nest_depth >= loop_nest_depth; i++) {
+ Block& loop_block = ctx.program->blocks[i];
+ needs |= ctx.info[i].block_needs;
+
+ if (loop_block.kind & block_kind_uses_discard_if ||
+ loop_block.kind & block_kind_discard)
+ has_discard = true;
+ if (loop_block.loop_nest_depth != loop_nest_depth)
+ continue;
+
+ if (loop_block.kind & block_kind_uniform)
+ continue;
+ else if (loop_block.kind & block_kind_break)
+ has_divergent_break = true;
+ else if (loop_block.kind & block_kind_continue)
+ has_divergent_continue = true;
+ }
+
+ if (ctx.handle_wqm) {
+ if (needs & WQM) {
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.pop_back();
+ transition_to_WQM(ctx, bld, idx);
+ bld.insert(std::move(branch));
+ } else {
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.pop_back();
+ transition_to_Exact(ctx, bld, idx);
+ bld.insert(std::move(branch));
+ }
+ }
+
+ unsigned num_exec_masks = ctx.info[idx].exec.size();
+ if (block->kind & block_kind_top_level)
+ num_exec_masks = std::min(num_exec_masks, 2u);
+
+ ctx.loop.emplace_back(&ctx.program->blocks[block->linear_succs[0]],
+ num_exec_masks,
+ needs,
+ has_divergent_break,
+ has_divergent_continue,
+ has_discard);
+ }
+
+ if (block->kind & block_kind_discard) {
+
+ assert(block->instructions.back()->format == Format::PSEUDO_BRANCH);
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.pop_back();
+
+ /* create a discard_if() instruction with the exec mask as condition */
+ unsigned num = 0;
+ if (ctx.loop.size()) {
+ /* if we're in a loop, only discard from the outer exec masks */
+ num = ctx.loop.back().num_exec_masks;
+ } else {
+ num = ctx.info[idx].exec.size() - 1;
+ }
+
+ Temp old_exec = ctx.info[idx].exec.back().first;
+ Temp new_exec = bld.tmp(s2);
+ Temp cond = bld.sop1(aco_opcode::s_and_saveexec_b64, bld.def(s2), bld.def(s1, scc),
+ bld.exec(Definition(new_exec)), Operand(0u), bld.exec(old_exec));
+ ctx.info[idx].exec.back().first = new_exec;
+
+ aco_ptr<Pseudo_instruction> discard{create_instruction<Pseudo_instruction>(aco_opcode::p_discard_if, Format::PSEUDO, num + 1, num + 1)};
+ for (unsigned i = 0; i < num; i++) {
+ discard->operands[i] = Operand(ctx.info[block->index].exec[i].first);
+ Temp new_mask = bld.tmp(s2);
+ discard->definitions[i] = Definition(new_mask);
+ ctx.info[block->index].exec[i].first = new_mask;
+ }
+ assert(!ctx.handle_wqm || (ctx.info[block->index].exec[0].second & mask_type_wqm) == 0);
+ discard->operands[num] = Operand(cond);
+ discard->definitions[num] = bld.def(s1, scc);
+
+ bld.insert(std::move(discard));
+ if ((block->kind & (block_kind_break | block_kind_uniform)) == block_kind_break)
+ ctx.info[idx].exec.back().first = cond;
+ bld.insert(std::move(branch));
+ /* no return here as it can be followed by a divergent break */
+ }
+
+ if (block->kind & block_kind_continue_or_break) {
+ assert(block->instructions.back()->opcode == aco_opcode::p_branch);
+ block->instructions.pop_back();
+
+ /* because of how linear_succs is created, this needs to be swapped */
+ std::swap(block->linear_succs[0], block->linear_succs[1]);
+
+ assert(ctx.program->blocks[block->linear_succs[1]].kind & block_kind_loop_header);
+ assert(ctx.program->blocks[ctx.program->blocks[block->linear_succs[0]].linear_succs[0]].kind & block_kind_loop_exit);
+
+ if (ctx.info[idx].exec.back().second & mask_type_loop) {
+ bld.branch(aco_opcode::p_cbranch_nz, bld.exec(ctx.info[idx].exec.back().first), block->linear_succs[1], block->linear_succs[0]);
+ } else {
+ Temp cond = Temp();
+ for (int exec_idx = ctx.info[idx].exec.size() - 1; exec_idx >= 0; exec_idx--) {
+ if (ctx.info[idx].exec[exec_idx].second & mask_type_loop) {
+ cond = bld.sopc(aco_opcode::s_cmp_lg_u64, bld.def(s1, scc), ctx.info[idx].exec[exec_idx].first, Operand(0u));
+ break;
+ }
+ }
+ assert(cond != Temp());
+
+ bld.branch(aco_opcode::p_cbranch_nz, bld.scc(cond), block->linear_succs[1], block->linear_succs[0]);
+ }
+ return;
+ }
+
+ if (block->kind & block_kind_uniform) {
+ Pseudo_branch_instruction* branch = static_cast<Pseudo_branch_instruction*>(block->instructions.back().get());
+ if (branch->opcode == aco_opcode::p_branch) {
+ branch->target[0] = block->linear_succs[0];
+ } else {
+ branch->target[0] = block->linear_succs[1];
+ branch->target[1] = block->linear_succs[0];
+ }
+ return;
+ }
+
+ if (block->kind & block_kind_branch) {
+
+ if (ctx.handle_wqm &&
+ ctx.info[idx].exec.size() >= 2 &&
+ ctx.info[idx].exec.back().second == mask_type_exact &&
+ !(ctx.info[idx].block_needs & Exact_Branch) &&
+ ctx.info[idx].exec[ctx.info[idx].exec.size() - 2].second & mask_type_wqm) {
+ /* return to wqm before branching */
+ ctx.info[idx].exec.pop_back();
+ }
+
+ // orig = s_and_saveexec_b64
+ assert(block->linear_succs.size() == 2);
+ assert(block->instructions.back()->opcode == aco_opcode::p_cbranch_z);
+ Temp cond = block->instructions.back()->operands[0].getTemp();
+ block->instructions.pop_back();
+
+ if (ctx.info[idx].block_needs & Exact_Branch)
+ transition_to_Exact(ctx, bld, idx);
+
+ Temp current_exec = ctx.info[idx].exec.back().first;
+ uint8_t mask_type = ctx.info[idx].exec.back().second & (mask_type_wqm | mask_type_exact);
+
+ Temp then_mask = bld.tmp(s2);
+ Temp old_exec = bld.sop1(aco_opcode::s_and_saveexec_b64, bld.def(s2), bld.def(s1, scc),
+ bld.exec(Definition(then_mask)), cond, bld.exec(current_exec));
+
+ ctx.info[idx].exec.back().first = old_exec;
+
+ /* add next current exec to the stack */
+ ctx.info[idx].exec.emplace_back(then_mask, mask_type);
+
+ bld.branch(aco_opcode::p_cbranch_z, bld.exec(then_mask), block->linear_succs[1], block->linear_succs[0]);
+ return;
+ }
+
+ if (block->kind & block_kind_invert) {
+ // exec = s_andn2_b64 (original_exec, exec)
+ assert(block->instructions.back()->opcode == aco_opcode::p_cbranch_nz);
+ block->instructions.pop_back();
+ Temp then_mask = ctx.info[idx].exec.back().first;
+ uint8_t mask_type = ctx.info[idx].exec.back().second;
+ ctx.info[idx].exec.pop_back();
+ Temp orig_exec = ctx.info[idx].exec.back().first;
+ Temp else_mask = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2, exec),
+ bld.def(s1, scc), orig_exec, bld.exec(then_mask));
+
+ /* add next current exec to the stack */
+ ctx.info[idx].exec.emplace_back(else_mask, mask_type);
+
+ bld.branch(aco_opcode::p_cbranch_z, bld.exec(else_mask), block->linear_succs[1], block->linear_succs[0]);
+ return;
+ }
+
+ if (block->kind & block_kind_break) {
+ // loop_mask = s_andn2_b64 (loop_mask, exec)
+ assert(block->instructions.back()->opcode == aco_opcode::p_branch);
+ block->instructions.pop_back();
+
+ Temp current_exec = ctx.info[idx].exec.back().first;
+ Temp cond = Temp();
+ for (int exec_idx = ctx.info[idx].exec.size() - 2; exec_idx >= 0; exec_idx--) {
+ cond = bld.tmp(s1);
+ Temp exec_mask = ctx.info[idx].exec[exec_idx].first;
+ exec_mask = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.scc(Definition(cond)),
+ exec_mask, current_exec);
+ ctx.info[idx].exec[exec_idx].first = exec_mask;
+ if (ctx.info[idx].exec[exec_idx].second & mask_type_loop)
+ break;
+ }
+
+ /* check if the successor is the merge block, otherwise set exec to 0 */
+ // TODO: this could be done better by directly branching to the merge block
+ unsigned succ_idx = ctx.program->blocks[block->linear_succs[1]].linear_succs[0];
+ Block& succ = ctx.program->blocks[succ_idx];
+ if (!(succ.kind & block_kind_invert || succ.kind & block_kind_merge)) {
+ ctx.info[idx].exec.back().first = bld.sop1(aco_opcode::s_mov_b64, bld.def(s2, exec), Operand(0u));
+ }
+
+ bld.branch(aco_opcode::p_cbranch_nz, bld.scc(cond), block->linear_succs[1], block->linear_succs[0]);
+ return;
+ }
+
+ if (block->kind & block_kind_continue) {
+ assert(block->instructions.back()->opcode == aco_opcode::p_branch);
+ block->instructions.pop_back();
+
+ Temp current_exec = ctx.info[idx].exec.back().first;
+ Temp cond = Temp();
+ for (int exec_idx = ctx.info[idx].exec.size() - 2; exec_idx >= 0; exec_idx--) {
+ if (ctx.info[idx].exec[exec_idx].second & mask_type_loop)
+ break;
+ cond = bld.tmp(s1);
+ Temp exec_mask = ctx.info[idx].exec[exec_idx].first;
+ exec_mask = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.scc(Definition(cond)),
+ exec_mask, bld.exec(current_exec));
+ ctx.info[idx].exec[exec_idx].first = exec_mask;
+ }
+ assert(cond != Temp());
+
+ /* check if the successor is the merge block, otherwise set exec to 0 */
+ // TODO: this could be done better by directly branching to the merge block
+ unsigned succ_idx = ctx.program->blocks[block->linear_succs[1]].linear_succs[0];
+ Block& succ = ctx.program->blocks[succ_idx];
+ if (!(succ.kind & block_kind_invert || succ.kind & block_kind_merge)) {
+ ctx.info[idx].exec.back().first = bld.sop1(aco_opcode::s_mov_b64, bld.def(s2, exec), Operand(0u));
+ }
+
+ bld.branch(aco_opcode::p_cbranch_nz, bld.scc(cond), block->linear_succs[1], block->linear_succs[0]);
+ return;
+ }
+}
+
+void process_block(exec_ctx& ctx, Block* block)
+{
+ std::vector<aco_ptr<Instruction>> instructions;
+ instructions.reserve(block->instructions.size());
+
+ unsigned idx = add_coupling_code(ctx, block, instructions);
+
+ assert(block->index != ctx.program->blocks.size() - 1 ||
+ ctx.info[block->index].exec.size() <= 2);
+
+ process_instructions(ctx, block, instructions, idx);
+
+ block->instructions = std::move(instructions);
+
+ add_branch_code(ctx, block);
+
+ block->live_out_exec = ctx.info[block->index].exec.back().first;
+}
+
+} /* end namespace */
+
+
+void insert_exec_mask(Program *program)
+{
+ exec_ctx ctx(program);
+
+ if (program->needs_wqm && program->needs_exact)
+ calculate_wqm_needs(ctx);
+
+ for (Block& block : program->blocks)
+ process_block(ctx, &block);
+
+}
+
+}
+
diff --git a/src/amd/compiler/aco_insert_waitcnt.cpp b/src/amd/compiler/aco_insert_waitcnt.cpp
new file mode 100644
index 00000000000..d19fdadadea
--- /dev/null
+++ b/src/amd/compiler/aco_insert_waitcnt.cpp
@@ -0,0 +1,697 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <algorithm>
+#include <map>
+
+#include "aco_ir.h"
+#include "vulkan/radv_shader.h"
+
+namespace aco {
+
+namespace {
+
+/**
+ * The general idea of this pass is:
+ * The CFG is traversed in reverse postorder (forward).
+ * Per BB one wait_ctx is maintained.
+ * The in-context is the joined out-contexts of the predecessors.
+ * The context contains a map: gpr -> wait_entry
+ * consisting of the information about the cnt values to be waited for.
+ * Note: After merge-nodes, it might occur that for the same register
+ * multiple cnt values are to be waited for.
+ *
+ * The values are updated according to the encountered instructions:
+ * - additional events increment the counter of waits of the same type
+ * - or erase gprs with counters higher than to be waited for.
+ */
+
+// TODO: do a more clever insertion of wait_cnt (lgkm_cnt) when there is a load followed by a use of a previous load
+
+/* Instructions of the same event will finish in-order except for smem
+ * and maybe flat. Instructions of different events may not finish in-order. */
+enum wait_event : uint16_t {
+ event_smem = 1 << 0,
+ event_lds = 1 << 1,
+ event_gds = 1 << 2,
+ event_vmem = 1 << 3,
+ event_vmem_store = 1 << 4, /* GFX10+ */
+ event_flat = 1 << 5,
+ event_exp_pos = 1 << 6,
+ event_exp_param = 1 << 7,
+ event_exp_mrt_null = 1 << 8,
+ event_gds_gpr_lock = 1 << 9,
+ event_vmem_gpr_lock = 1 << 10,
+};
+
+enum counter_type : uint8_t {
+ counter_exp = 1 << 0,
+ counter_lgkm = 1 << 1,
+ counter_vm = 1 << 2,
+ counter_vs = 1 << 3,
+};
+
+static const uint16_t exp_events = event_exp_pos | event_exp_param | event_exp_mrt_null | event_gds_gpr_lock | event_vmem_gpr_lock;
+static const uint16_t lgkm_events = event_smem | event_lds | event_gds | event_flat;
+static const uint16_t vm_events = event_vmem | event_flat;
+static const uint16_t vs_events = event_vmem_store;
+
+uint8_t get_counters_for_event(wait_event ev)
+{
+ switch (ev) {
+ case event_smem:
+ case event_lds:
+ case event_gds:
+ return counter_lgkm;
+ case event_vmem:
+ return counter_vm;
+ case event_vmem_store:
+ return counter_vs;
+ case event_flat:
+ return counter_vm | counter_lgkm;
+ case event_exp_pos:
+ case event_exp_param:
+ case event_exp_mrt_null:
+ case event_gds_gpr_lock:
+ case event_vmem_gpr_lock:
+ return counter_exp;
+ default:
+ return 0;
+ }
+}
+
+struct wait_imm {
+ static const uint8_t unset_counter = 0xff;
+
+ uint8_t vm;
+ uint8_t exp;
+ uint8_t lgkm;
+ uint8_t vs;
+
+ wait_imm() :
+ vm(unset_counter), exp(unset_counter), lgkm(unset_counter), vs(unset_counter) {}
+ wait_imm(uint16_t vm_, uint16_t exp_, uint16_t lgkm_, uint16_t vs_) :
+ vm(vm_), exp(exp_), lgkm(lgkm_), vs(vs_) {}
+
+ uint16_t pack(enum chip_class chip) const
+ {
+ uint16_t imm = 0;
+ assert(exp == unset_counter || exp <= 0x7);
+ switch (chip) {
+ case GFX10:
+ assert(lgkm == unset_counter || lgkm <= 0x3f);
+ assert(vm == unset_counter || vm <= 0x3f);
+ imm = ((vm & 0x30) << 10) | ((lgkm & 0x3f) << 8) | ((exp & 0x7) << 4) | (vm & 0xf);
+ break;
+ case GFX9:
+ assert(lgkm == unset_counter || lgkm <= 0xf);
+ assert(vm == unset_counter || vm <= 0x3f);
+ imm = ((vm & 0x30) << 10) | ((lgkm & 0xf) << 8) | ((exp & 0x7) << 4) | (vm & 0xf);
+ break;
+ default:
+ assert(lgkm == unset_counter || lgkm <= 0xf);
+ assert(vm == unset_counter || vm <= 0xf);
+ imm = ((lgkm & 0xf) << 8) | ((exp & 0x7) << 4) | (vm & 0xf);
+ break;
+ }
+ if (chip < GFX9 && vm == wait_imm::unset_counter)
+ imm |= 0xc000; /* should have no effect on pre-GFX9 and now we won't have to worry about the architecture when interpreting the immediate */
+ if (chip < GFX10 && lgkm == wait_imm::unset_counter)
+ imm |= 0x3000; /* should have no effect on pre-GFX10 and now we won't have to worry about the architecture when interpreting the immediate */
+ return imm;
+ }
+
+ void combine(const wait_imm& other)
+ {
+ vm = std::min(vm, other.vm);
+ exp = std::min(exp, other.exp);
+ lgkm = std::min(lgkm, other.lgkm);
+ vs = std::min(vs, other.vs);
+ }
+
+ bool empty() const
+ {
+ return vm == unset_counter && exp == unset_counter &&
+ lgkm == unset_counter && vs == unset_counter;
+ }
+};
+
+struct wait_entry {
+ wait_imm imm;
+ uint16_t events; /* use wait_event notion */
+ uint8_t counters; /* use counter_type notion */
+ bool wait_on_read:1;
+ bool logical:1;
+
+ wait_entry(wait_event event, wait_imm imm, bool logical, bool wait_on_read)
+ : imm(imm), events(event), counters(get_counters_for_event(event)),
+ wait_on_read(wait_on_read), logical(logical) {}
+
+ void join(const wait_entry& other)
+ {
+ events |= other.events;
+ counters |= other.counters;
+ imm.combine(other.imm);
+ wait_on_read = wait_on_read || other.wait_on_read;
+ assert(logical == other.logical);
+ }
+
+ void remove_counter(counter_type counter)
+ {
+ counters &= ~counter;
+
+ if (counter == counter_lgkm) {
+ imm.lgkm = wait_imm::unset_counter;
+ events &= ~(event_smem | event_lds | event_gds);
+ }
+
+ if (counter == counter_vm) {
+ imm.vm = wait_imm::unset_counter;
+ events &= ~event_vmem;
+ }
+
+ if (counter == counter_exp) {
+ imm.exp = wait_imm::unset_counter;
+ events &= ~(event_exp_pos | event_exp_param | event_exp_mrt_null | event_gds_gpr_lock | event_vmem_gpr_lock);
+ }
+
+ if (counter == counter_vs) {
+ imm.vs = wait_imm::unset_counter;
+ events &= ~event_vmem_store;
+ }
+
+ if (!(counters & counter_lgkm) && !(counters & counter_vm))
+ events &= ~event_flat;
+ }
+};
+
+struct wait_ctx {
+ Program *program;
+ enum chip_class chip_class;
+ uint16_t max_vm_cnt;
+ uint16_t max_exp_cnt;
+ uint16_t max_lgkm_cnt;
+ uint16_t max_vs_cnt;
+ uint16_t unordered_events = event_smem | event_flat;
+
+ uint8_t vm_cnt = 0;
+ uint8_t exp_cnt = 0;
+ uint8_t lgkm_cnt = 0;
+ uint8_t vs_cnt = 0;
+ bool pending_flat_lgkm = false;
+ bool pending_flat_vm = false;
+
+ wait_imm barrier_imm[barrier_count];
+
+ std::map<PhysReg,wait_entry> gpr_map;
+
+ wait_ctx() {}
+ wait_ctx(Program *program_)
+ : program(program_),
+ chip_class(program_->chip_class),
+ max_vm_cnt(program_->chip_class >= GFX9 ? 62 : 14),
+ max_exp_cnt(6),
+ max_lgkm_cnt(program_->chip_class >= GFX10 ? 62 : 14),
+ max_vs_cnt(program_->chip_class >= GFX10 ? 62 : 0),
+ unordered_events(event_smem | (program_->chip_class < GFX10 ? event_flat : 0)) {}
+
+ void join(const wait_ctx* other, bool logical)
+ {
+ exp_cnt = std::max(exp_cnt, other->exp_cnt);
+ vm_cnt = std::max(vm_cnt, other->vm_cnt);
+ lgkm_cnt = std::max(lgkm_cnt, other->lgkm_cnt);
+ vs_cnt = std::max(vs_cnt, other->vs_cnt);
+ pending_flat_lgkm |= other->pending_flat_lgkm;
+ pending_flat_vm |= other->pending_flat_vm;
+
+ for (std::pair<PhysReg,wait_entry> entry : other->gpr_map)
+ {
+ std::map<PhysReg,wait_entry>::iterator it = gpr_map.find(entry.first);
+ if (entry.second.logical != logical)
+ continue;
+
+ if (it != gpr_map.end())
+ it->second.join(entry.second);
+ else
+ gpr_map.insert(entry);
+ }
+
+ for (unsigned i = 0; i < barrier_count; i++)
+ barrier_imm[i].combine(other->barrier_imm[i]);
+ }
+};
+
+wait_imm check_instr(Instruction* instr, wait_ctx& ctx)
+{
+ wait_imm wait;
+
+ for (const Operand op : instr->operands) {
+ if (op.isConstant() || op.isUndefined())
+ continue;
+
+ /* check consecutively read gprs */
+ for (unsigned j = 0; j < op.size(); j++) {
+ PhysReg reg{op.physReg() + j};
+ std::map<PhysReg,wait_entry>::iterator it = ctx.gpr_map.find(reg);
+ if (it == ctx.gpr_map.end() || !it->second.wait_on_read)
+ continue;
+
+ wait.combine(it->second.imm);
+ }
+ }
+
+ for (const Definition& def : instr->definitions) {
+ /* check consecutively written gprs */
+ for (unsigned j = 0; j < def.getTemp().size(); j++)
+ {
+ PhysReg reg{def.physReg() + j};
+
+ std::map<PhysReg,wait_entry>::iterator it = ctx.gpr_map.find(reg);
+ if (it == ctx.gpr_map.end())
+ continue;
+
+ /* Vector Memory reads and writes return in the order they were issued */
+ if (instr->isVMEM() && ((it->second.events & vm_events) == event_vmem)) {
+ it->second.remove_counter(counter_vm);
+ if (!it->second.counters)
+ it = ctx.gpr_map.erase(it);
+ continue;
+ }
+
+ /* LDS reads and writes return in the order they were issued. same for GDS */
+ if (instr->format == Format::DS) {
+ bool gds = static_cast<DS_instruction*>(instr)->gds;
+ if ((it->second.events & lgkm_events) == (gds ? event_gds : event_lds)) {
+ it->second.remove_counter(counter_lgkm);
+ if (!it->second.counters)
+ it = ctx.gpr_map.erase(it);
+ continue;
+ }
+ }
+
+ wait.combine(it->second.imm);
+ }
+ }
+
+ return wait;
+}
+
+wait_imm kill(Instruction* instr, wait_ctx& ctx)
+{
+ wait_imm imm;
+ if (ctx.exp_cnt || ctx.vm_cnt || ctx.lgkm_cnt)
+ imm.combine(check_instr(instr, ctx));
+
+ if (instr->format == Format::PSEUDO_BARRIER) {
+ unsigned* bsize = ctx.program->info->cs.block_size;
+ unsigned workgroup_size = bsize[0] * bsize[1] * bsize[2];
+ switch (instr->opcode) {
+ case aco_opcode::p_memory_barrier_all:
+ for (unsigned i = 0; i < barrier_count; i++) {
+ if ((1 << i) == barrier_shared && workgroup_size <= 64)
+ continue;
+ imm.combine(ctx.barrier_imm[i]);
+ }
+ break;
+ case aco_opcode::p_memory_barrier_atomic:
+ imm.combine(ctx.barrier_imm[ffs(barrier_atomic) - 1]);
+ break;
+ /* see comment in aco_scheduler.cpp's can_move_instr() on why these barriers are merged */
+ case aco_opcode::p_memory_barrier_buffer:
+ case aco_opcode::p_memory_barrier_image:
+ imm.combine(ctx.barrier_imm[ffs(barrier_buffer) - 1]);
+ imm.combine(ctx.barrier_imm[ffs(barrier_image) - 1]);
+ break;
+ case aco_opcode::p_memory_barrier_shared:
+ if (workgroup_size > 64)
+ imm.combine(ctx.barrier_imm[ffs(barrier_shared) - 1]);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+
+ if (!imm.empty()) {
+ if (ctx.pending_flat_vm && imm.vm != wait_imm::unset_counter)
+ imm.vm = 0;
+ if (ctx.pending_flat_lgkm && imm.lgkm != wait_imm::unset_counter)
+ imm.lgkm = 0;
+
+ /* reset counters */
+ ctx.exp_cnt = std::min(ctx.exp_cnt, imm.exp);
+ ctx.vm_cnt = std::min(ctx.vm_cnt, imm.vm);
+ ctx.lgkm_cnt = std::min(ctx.lgkm_cnt, imm.lgkm);
+ ctx.vs_cnt = std::min(ctx.vs_cnt, imm.vs);
+
+ /* update barrier wait imms */
+ for (unsigned i = 0; i < barrier_count; i++) {
+ wait_imm& bar = ctx.barrier_imm[i];
+ if (bar.exp != wait_imm::unset_counter && imm.exp <= bar.exp)
+ bar.exp = wait_imm::unset_counter;
+ if (bar.vm != wait_imm::unset_counter && imm.vm <= bar.vm)
+ bar.vm = wait_imm::unset_counter;
+ if (bar.lgkm != wait_imm::unset_counter && imm.lgkm <= bar.lgkm)
+ bar.lgkm = wait_imm::unset_counter;
+ if (bar.vs != wait_imm::unset_counter && imm.vs <= bar.vs)
+ bar.vs = wait_imm::unset_counter;
+ }
+
+ /* remove all vgprs with higher counter from map */
+ std::map<PhysReg,wait_entry>::iterator it = ctx.gpr_map.begin();
+ while (it != ctx.gpr_map.end())
+ {
+ if (imm.exp != wait_imm::unset_counter && imm.exp <= it->second.imm.exp)
+ it->second.remove_counter(counter_exp);
+ if (imm.vm != wait_imm::unset_counter && imm.vm <= it->second.imm.vm)
+ it->second.remove_counter(counter_vm);
+ if (imm.lgkm != wait_imm::unset_counter && imm.lgkm <= it->second.imm.lgkm)
+ it->second.remove_counter(counter_lgkm);
+ if (imm.lgkm != wait_imm::unset_counter && imm.vs <= it->second.imm.vs)
+ it->second.remove_counter(counter_vs);
+ if (!it->second.counters)
+ it = ctx.gpr_map.erase(it);
+ else
+ it++;
+ }
+ }
+
+ if (imm.vm == 0)
+ ctx.pending_flat_vm = false;
+ if (imm.lgkm == 0)
+ ctx.pending_flat_lgkm = false;
+
+ return imm;
+}
+
+void update_barrier_imm(wait_ctx& ctx, uint8_t counters, barrier_interaction barrier)
+{
+ unsigned barrier_index = ffs(barrier) - 1;
+ for (unsigned i = 0; i < barrier_count; i++) {
+ wait_imm& bar = ctx.barrier_imm[i];
+ if (i == barrier_index) {
+ if (counters & counter_lgkm)
+ bar.lgkm = 0;
+ if (counters & counter_vm)
+ bar.vm = 0;
+ if (counters & counter_exp)
+ bar.exp = 0;
+ if (counters & counter_vs)
+ bar.vs = 0;
+ } else {
+ if (counters & counter_lgkm && bar.lgkm != wait_imm::unset_counter && bar.lgkm < ctx.max_lgkm_cnt)
+ bar.lgkm++;
+ if (counters & counter_vm && bar.vm != wait_imm::unset_counter && bar.vm < ctx.max_vm_cnt)
+ bar.vm++;
+ if (counters & counter_exp && bar.exp != wait_imm::unset_counter && bar.exp < ctx.max_exp_cnt)
+ bar.exp++;
+ if (counters & counter_vs && bar.vs != wait_imm::unset_counter && bar.vs < ctx.max_vs_cnt)
+ bar.vs++;
+ }
+ }
+}
+
+void update_counters(wait_ctx& ctx, wait_event event, barrier_interaction barrier=barrier_none)
+{
+ uint8_t counters = get_counters_for_event(event);
+
+ if (counters & counter_lgkm && ctx.lgkm_cnt <= ctx.max_lgkm_cnt)
+ ctx.lgkm_cnt++;
+ if (counters & counter_vm && ctx.vm_cnt <= ctx.max_vm_cnt)
+ ctx.vm_cnt++;
+ if (counters & counter_exp && ctx.exp_cnt <= ctx.max_exp_cnt)
+ ctx.exp_cnt++;
+ if (counters & counter_vs && ctx.vs_cnt <= ctx.max_vs_cnt)
+ ctx.vs_cnt++;
+
+ update_barrier_imm(ctx, counters, barrier);
+
+ if (ctx.unordered_events & event)
+ return;
+
+ if (ctx.pending_flat_lgkm)
+ counters &= ~counter_lgkm;
+ if (ctx.pending_flat_vm)
+ counters &= ~counter_vm;
+
+ for (std::pair<const PhysReg,wait_entry>& e : ctx.gpr_map) {
+ wait_entry& entry = e.second;
+
+ if (entry.events & ctx.unordered_events)
+ continue;
+
+ assert(entry.events);
+
+ if ((counters & counter_exp) && (entry.events & exp_events) == event && entry.imm.exp < ctx.max_exp_cnt)
+ entry.imm.exp++;
+ if ((counters & counter_lgkm) && (entry.events & lgkm_events) == event && entry.imm.lgkm < ctx.max_lgkm_cnt)
+ entry.imm.lgkm++;
+ if ((counters & counter_vm) && (entry.events & vm_events) == event && entry.imm.vm < ctx.max_vm_cnt)
+ entry.imm.vm++;
+ if ((counters & counter_vs) && (entry.events & vs_events) == event && entry.imm.vs < ctx.max_vs_cnt)
+ entry.imm.vs++;
+ }
+}
+
+void update_counters_for_flat_load(wait_ctx& ctx, barrier_interaction barrier=barrier_none)
+{
+ assert(ctx.chip_class < GFX10);
+
+ if (ctx.lgkm_cnt <= ctx.max_lgkm_cnt)
+ ctx.lgkm_cnt++;
+ if (ctx.lgkm_cnt <= ctx.max_vm_cnt)
+ ctx.vm_cnt++;
+
+ update_barrier_imm(ctx, counter_vm | counter_lgkm, barrier);
+
+ for (std::pair<PhysReg,wait_entry> e : ctx.gpr_map)
+ {
+ if (e.second.counters & counter_vm)
+ e.second.imm.vm = 0;
+ if (e.second.counters & counter_lgkm)
+ e.second.imm.lgkm = 0;
+ }
+ ctx.pending_flat_lgkm = true;
+ ctx.pending_flat_vm = true;
+}
+
+void insert_wait_entry(wait_ctx& ctx, PhysReg reg, RegClass rc, wait_event event, bool wait_on_read)
+{
+ uint16_t counters = get_counters_for_event(event);
+ wait_imm imm;
+ if (counters & counter_lgkm)
+ imm.lgkm = 0;
+ if (counters & counter_vm)
+ imm.vm = 0;
+ if (counters & counter_exp)
+ imm.exp = 0;
+ if (counters & counter_vs)
+ imm.vs = 0;
+
+ wait_entry new_entry(event, imm, !rc.is_linear(), wait_on_read);
+
+ for (unsigned i = 0; i < rc.size(); i++) {
+ auto it = ctx.gpr_map.emplace(PhysReg{reg.reg+i}, new_entry);
+ if (!it.second)
+ it.first->second.join(new_entry);
+ }
+}
+
+void insert_wait_entry(wait_ctx& ctx, Operand op, wait_event event)
+{
+ if (!op.isConstant() && !op.isUndefined())
+ insert_wait_entry(ctx, op.physReg(), op.regClass(), event, false);
+}
+
+void insert_wait_entry(wait_ctx& ctx, Definition def, wait_event event)
+{
+ insert_wait_entry(ctx, def.physReg(), def.regClass(), event, true);
+}
+
+void gen(Instruction* instr, wait_ctx& ctx)
+{
+ switch (instr->format) {
+ case Format::EXP: {
+ Export_instruction* exp_instr = static_cast<Export_instruction*>(instr);
+
+ wait_event ev;
+ if (exp_instr->dest <= 9)
+ ev = event_exp_mrt_null;
+ else if (exp_instr->dest <= 15)
+ ev = event_exp_pos;
+ else
+ ev = event_exp_param;
+ update_counters(ctx, ev);
+
+ /* insert new entries for exported vgprs */
+ for (unsigned i = 0; i < 4; i++)
+ {
+ if (exp_instr->enabled_mask & (1 << i)) {
+ unsigned idx = exp_instr->compressed ? i >> 1 : i;
+ assert(idx < exp_instr->operands.size());
+ insert_wait_entry(ctx, exp_instr->operands[idx], ev);
+
+ }
+ }
+ insert_wait_entry(ctx, exec, s2, ev, false);
+ break;
+ }
+ case Format::FLAT: {
+ if (ctx.chip_class < GFX10 && !instr->definitions.empty())
+ update_counters_for_flat_load(ctx, barrier_buffer);
+ else
+ update_counters(ctx, event_flat, barrier_buffer);
+
+ if (!instr->definitions.empty())
+ insert_wait_entry(ctx, instr->definitions[0], event_flat);
+ break;
+ }
+ case Format::SMEM: {
+ update_counters(ctx, event_smem, static_cast<SMEM_instruction*>(instr)->barrier);
+
+ if (!instr->definitions.empty())
+ insert_wait_entry(ctx, instr->definitions[0], event_smem);
+ break;
+ }
+ case Format::DS: {
+ bool gds = static_cast<DS_instruction*>(instr)->gds;
+ update_counters(ctx, gds ? event_gds : event_lds, gds ? barrier_none : barrier_shared);
+ if (gds)
+ update_counters(ctx, event_gds_gpr_lock);
+
+ if (!instr->definitions.empty())
+ insert_wait_entry(ctx, instr->definitions[0], gds ? event_gds : event_lds);
+
+ if (gds) {
+ for (const Operand& op : instr->operands)
+ insert_wait_entry(ctx, op, event_gds_gpr_lock);
+ insert_wait_entry(ctx, exec, s2, event_gds_gpr_lock, false);
+ }
+ break;
+ }
+ case Format::MUBUF:
+ case Format::MTBUF:
+ case Format::MIMG:
+ case Format::GLOBAL: {
+ wait_event ev = !instr->definitions.empty() || ctx.chip_class < GFX10 ? event_vmem : event_vmem_store;
+ update_counters(ctx, ev, get_barrier_interaction(instr));
+
+ if (!instr->definitions.empty())
+ insert_wait_entry(ctx, instr->definitions[0], ev);
+
+ if (instr->operands.size() == 4 && ctx.chip_class == GFX6) {
+ ctx.exp_cnt++;
+ update_counters(ctx, event_vmem_gpr_lock);
+ insert_wait_entry(ctx, instr->operands[3], event_vmem_gpr_lock);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void emit_waitcnt(wait_ctx& ctx, std::vector<aco_ptr<Instruction>>& instructions, wait_imm imm)
+{
+ if (imm.vs != wait_imm::unset_counter) {
+ assert(ctx.chip_class >= GFX10);
+ SOPK_instruction* waitcnt_vs = create_instruction<SOPK_instruction>(aco_opcode::s_waitcnt_vscnt, Format::SOPK, 0, 0);
+ waitcnt_vs->imm = imm.vs;
+ instructions.emplace_back(waitcnt_vs);
+ imm.vs = wait_imm::unset_counter;
+ }
+ if (!imm.empty()) {
+ SOPP_instruction* waitcnt = create_instruction<SOPP_instruction>(aco_opcode::s_waitcnt, Format::SOPP, 0, 0);
+ waitcnt->imm = imm.pack(ctx.chip_class);
+ waitcnt->block = -1;
+ instructions.emplace_back(waitcnt);
+ }
+}
+
+void handle_block(Program *program, Block& block, wait_ctx& ctx)
+{
+ std::vector<aco_ptr<Instruction>> new_instructions;
+
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ wait_imm imm = kill(instr.get(), ctx);
+
+ if (!imm.empty())
+ emit_waitcnt(ctx, new_instructions, imm);
+
+ gen(instr.get(), ctx);
+
+ if (instr->format != Format::PSEUDO_BARRIER)
+ new_instructions.emplace_back(std::move(instr));
+ }
+
+ /* check if this block is at the end of a loop */
+ for (unsigned succ_idx : block.linear_succs) {
+ /* eliminate any remaining counters */
+ if (succ_idx <= block.index && (ctx.vm_cnt || ctx.exp_cnt || ctx.lgkm_cnt || ctx.vs_cnt)) {
+ // TODO: we could do better if we only wait if the regs between the block and other predecessors differ
+
+ aco_ptr<Instruction> branch = std::move(new_instructions.back());
+ new_instructions.pop_back();
+
+ wait_imm imm(ctx.vm_cnt ? 0 : wait_imm::unset_counter,
+ ctx.exp_cnt ? 0 : wait_imm::unset_counter,
+ ctx.lgkm_cnt ? 0 : wait_imm::unset_counter,
+ ctx.vs_cnt ? 0 : wait_imm::unset_counter);
+ emit_waitcnt(ctx, new_instructions, imm);
+
+ new_instructions.push_back(std::move(branch));
+
+ ctx = wait_ctx(program);
+ break;
+ }
+ }
+ block.instructions.swap(new_instructions);
+}
+
+} /* end namespace */
+
+void insert_wait_states(Program* program)
+{
+ wait_ctx out_ctx[program->blocks.size()]; /* per BB ctx */
+ for (unsigned i = 0; i < program->blocks.size(); i++)
+ out_ctx[i] = wait_ctx(program);
+
+ for (unsigned i = 0; i < program->blocks.size(); i++) {
+ Block& current = program->blocks[i];
+ wait_ctx& in = out_ctx[current.index];
+
+ for (unsigned b : current.linear_preds)
+ in.join(&out_ctx[b], false);
+ for (unsigned b : current.logical_preds)
+ in.join(&out_ctx[b], true);
+
+ if (current.instructions.empty())
+ continue;
+
+ handle_block(program, current, in);
+ }
+}
+
+}
+
diff --git a/src/amd/compiler/aco_instruction_selection.cpp b/src/amd/compiler/aco_instruction_selection.cpp
new file mode 100644
index 00000000000..d52043f3c0d
--- /dev/null
+++ b/src/amd/compiler/aco_instruction_selection.cpp
@@ -0,0 +1,7621 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <algorithm>
+#include <map>
+
+#include "aco_ir.h"
+#include "aco_builder.h"
+#include "aco_interface.h"
+#include "aco_instruction_selection_setup.cpp"
+#include "util/fast_idiv_by_const.h"
+
+namespace aco {
+namespace {
+
+class loop_info_RAII {
+ isel_context* ctx;
+ unsigned header_idx_old;
+ Block* exit_old;
+ bool divergent_cont_old;
+ bool divergent_branch_old;
+ bool divergent_if_old;
+
+public:
+ loop_info_RAII(isel_context* ctx, unsigned loop_header_idx, Block* loop_exit)
+ : ctx(ctx),
+ header_idx_old(ctx->cf_info.parent_loop.header_idx), exit_old(ctx->cf_info.parent_loop.exit),
+ divergent_cont_old(ctx->cf_info.parent_loop.has_divergent_continue),
+ divergent_branch_old(ctx->cf_info.parent_loop.has_divergent_branch),
+ divergent_if_old(ctx->cf_info.parent_if.is_divergent)
+ {
+ ctx->cf_info.parent_loop.header_idx = loop_header_idx;
+ ctx->cf_info.parent_loop.exit = loop_exit;
+ ctx->cf_info.parent_loop.has_divergent_continue = false;
+ ctx->cf_info.parent_loop.has_divergent_branch = false;
+ ctx->cf_info.parent_if.is_divergent = false;
+ ctx->cf_info.loop_nest_depth = ctx->cf_info.loop_nest_depth + 1;
+ }
+
+ ~loop_info_RAII()
+ {
+ ctx->cf_info.parent_loop.header_idx = header_idx_old;
+ ctx->cf_info.parent_loop.exit = exit_old;
+ ctx->cf_info.parent_loop.has_divergent_continue = divergent_cont_old;
+ ctx->cf_info.parent_loop.has_divergent_branch = divergent_branch_old;
+ ctx->cf_info.parent_if.is_divergent = divergent_if_old;
+ ctx->cf_info.loop_nest_depth = ctx->cf_info.loop_nest_depth - 1;
+ if (!ctx->cf_info.loop_nest_depth && !ctx->cf_info.parent_if.is_divergent)
+ ctx->cf_info.exec_potentially_empty = false;
+ }
+};
+
+struct if_context {
+ Temp cond;
+
+ bool divergent_old;
+ bool exec_potentially_empty_old;
+
+ unsigned BB_if_idx;
+ unsigned invert_idx;
+ bool then_branch_divergent;
+ Block BB_invert;
+ Block BB_endif;
+};
+
+static void visit_cf_list(struct isel_context *ctx,
+ struct exec_list *list);
+
+static void add_logical_edge(unsigned pred_idx, Block *succ)
+{
+ succ->logical_preds.emplace_back(pred_idx);
+}
+
+
+static void add_linear_edge(unsigned pred_idx, Block *succ)
+{
+ succ->linear_preds.emplace_back(pred_idx);
+}
+
+static void add_edge(unsigned pred_idx, Block *succ)
+{
+ add_logical_edge(pred_idx, succ);
+ add_linear_edge(pred_idx, succ);
+}
+
+static void append_logical_start(Block *b)
+{
+ Builder(NULL, b).pseudo(aco_opcode::p_logical_start);
+}
+
+static void append_logical_end(Block *b)
+{
+ Builder(NULL, b).pseudo(aco_opcode::p_logical_end);
+}
+
+Temp get_ssa_temp(struct isel_context *ctx, nir_ssa_def *def)
+{
+ assert(ctx->allocated[def->index].id());
+ return ctx->allocated[def->index];
+}
+
+Temp emit_wqm(isel_context *ctx, Temp src, Temp dst=Temp(0, s1), bool program_needs_wqm = false)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ if (!dst.id())
+ dst = bld.tmp(src.regClass());
+
+ if (ctx->stage != fragment_fs) {
+ if (!dst.id())
+ return src;
+
+ if (src.type() == RegType::vgpr || src.size() > 1)
+ bld.copy(Definition(dst), src);
+ else
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), src);
+ return dst;
+ }
+
+ bld.pseudo(aco_opcode::p_wqm, Definition(dst), src);
+ ctx->program->needs_wqm |= program_needs_wqm;
+ return dst;
+}
+
+Temp as_vgpr(isel_context *ctx, Temp val)
+{
+ if (val.type() == RegType::sgpr) {
+ Builder bld(ctx->program, ctx->block);
+ return bld.copy(bld.def(RegType::vgpr, val.size()), val);
+ }
+ assert(val.type() == RegType::vgpr);
+ return val;
+}
+
+//assumes a != 0xffffffff
+void emit_v_div_u32(isel_context *ctx, Temp dst, Temp a, uint32_t b)
+{
+ assert(b != 0);
+ Builder bld(ctx->program, ctx->block);
+
+ if (util_is_power_of_two_or_zero(b)) {
+ bld.vop2(aco_opcode::v_lshrrev_b32, Definition(dst), Operand((uint32_t)util_logbase2(b)), a);
+ return;
+ }
+
+ util_fast_udiv_info info = util_compute_fast_udiv_info(b, 32, 32);
+
+ assert(info.multiplier <= 0xffffffff);
+
+ bool pre_shift = info.pre_shift != 0;
+ bool increment = info.increment != 0;
+ bool multiply = true;
+ bool post_shift = info.post_shift != 0;
+
+ if (!pre_shift && !increment && !multiply && !post_shift) {
+ bld.vop1(aco_opcode::v_mov_b32, Definition(dst), a);
+ return;
+ }
+
+ Temp pre_shift_dst = a;
+ if (pre_shift) {
+ pre_shift_dst = (increment || multiply || post_shift) ? bld.tmp(v1) : dst;
+ bld.vop2(aco_opcode::v_lshrrev_b32, Definition(pre_shift_dst), Operand((uint32_t)info.pre_shift), a);
+ }
+
+ Temp increment_dst = pre_shift_dst;
+ if (increment) {
+ increment_dst = (post_shift || multiply) ? bld.tmp(v1) : dst;
+ bld.vadd32(Definition(increment_dst), Operand((uint32_t) info.increment), pre_shift_dst);
+ }
+
+ Temp multiply_dst = increment_dst;
+ if (multiply) {
+ multiply_dst = post_shift ? bld.tmp(v1) : dst;
+ bld.vop3(aco_opcode::v_mul_hi_u32, Definition(multiply_dst), increment_dst,
+ bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand((uint32_t)info.multiplier)));
+ }
+
+ if (post_shift) {
+ bld.vop2(aco_opcode::v_lshrrev_b32, Definition(dst), Operand((uint32_t)info.post_shift), multiply_dst);
+ }
+}
+
+void emit_extract_vector(isel_context* ctx, Temp src, uint32_t idx, Temp dst)
+{
+ Builder bld(ctx->program, ctx->block);
+ bld.pseudo(aco_opcode::p_extract_vector, Definition(dst), src, Operand(idx));
+}
+
+
+Temp emit_extract_vector(isel_context* ctx, Temp src, uint32_t idx, RegClass dst_rc)
+{
+ /* no need to extract the whole vector */
+ if (src.regClass() == dst_rc) {
+ assert(idx == 0);
+ return src;
+ }
+ assert(src.size() > idx);
+ Builder bld(ctx->program, ctx->block);
+ auto it = ctx->allocated_vec.find(src.id());
+ /* the size check needs to be early because elements other than 0 may be garbage */
+ if (it != ctx->allocated_vec.end() && it->second[0].size() == dst_rc.size()) {
+ if (it->second[idx].regClass() == dst_rc) {
+ return it->second[idx];
+ } else {
+ assert(dst_rc.size() == it->second[idx].regClass().size());
+ assert(dst_rc.type() == RegType::vgpr && it->second[idx].type() == RegType::sgpr);
+ return bld.copy(bld.def(dst_rc), it->second[idx]);
+ }
+ }
+
+ if (src.size() == dst_rc.size()) {
+ assert(idx == 0);
+ return bld.copy(bld.def(dst_rc), src);
+ } else {
+ Temp dst = bld.tmp(dst_rc);
+ emit_extract_vector(ctx, src, idx, dst);
+ return dst;
+ }
+}
+
+void emit_split_vector(isel_context* ctx, Temp vec_src, unsigned num_components)
+{
+ if (num_components == 1)
+ return;
+ if (ctx->allocated_vec.find(vec_src.id()) != ctx->allocated_vec.end())
+ return;
+ aco_ptr<Pseudo_instruction> split{create_instruction<Pseudo_instruction>(aco_opcode::p_split_vector, Format::PSEUDO, 1, num_components)};
+ split->operands[0] = Operand(vec_src);
+ std::array<Temp,4> elems;
+ for (unsigned i = 0; i < num_components; i++) {
+ elems[i] = {ctx->program->allocateId(), RegClass(vec_src.type(), vec_src.size() / num_components)};
+ split->definitions[i] = Definition(elems[i]);
+ }
+ ctx->block->instructions.emplace_back(std::move(split));
+ ctx->allocated_vec.emplace(vec_src.id(), elems);
+}
+
+/* This vector expansion uses a mask to determine which elements in the new vector
+ * come from the original vector. The other elements are undefined. */
+void expand_vector(isel_context* ctx, Temp vec_src, Temp dst, unsigned num_components, unsigned mask)
+{
+ emit_split_vector(ctx, vec_src, util_bitcount(mask));
+
+ if (vec_src == dst)
+ return;
+
+ Builder bld(ctx->program, ctx->block);
+ if (num_components == 1) {
+ if (dst.type() == RegType::sgpr)
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), vec_src);
+ else
+ bld.copy(Definition(dst), vec_src);
+ return;
+ }
+
+ unsigned component_size = dst.size() / num_components;
+ std::array<Temp,4> elems;
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, num_components, 1)};
+ vec->definitions[0] = Definition(dst);
+ unsigned k = 0;
+ for (unsigned i = 0; i < num_components; i++) {
+ if (mask & (1 << i)) {
+ Temp src = emit_extract_vector(ctx, vec_src, k++, RegClass(vec_src.type(), component_size));
+ if (dst.type() == RegType::sgpr)
+ src = bld.as_uniform(src);
+ vec->operands[i] = Operand(src);
+ } else {
+ vec->operands[i] = Operand(0u);
+ }
+ elems[i] = vec->operands[i].getTemp();
+ }
+ ctx->block->instructions.emplace_back(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), elems);
+}
+
+Temp as_divergent_bool(isel_context *ctx, Temp val, bool vcc_hint)
+{
+ if (val.regClass() == s2) {
+ return val;
+ } else {
+ assert(val.regClass() == s1);
+ Builder bld(ctx->program, ctx->block);
+ Definition& def = bld.sop2(aco_opcode::s_cselect_b64, bld.def(s2),
+ Operand((uint32_t) -1), Operand(0u), bld.scc(val)).def(0);
+ if (vcc_hint)
+ def.setHint(vcc);
+ return def.getTemp();
+ }
+}
+
+Temp as_uniform_bool(isel_context *ctx, Temp val)
+{
+ if (val.regClass() == s1) {
+ return val;
+ } else {
+ assert(val.regClass() == s2);
+ Builder bld(ctx->program, ctx->block);
+ return bld.sopc(aco_opcode::s_cmp_lg_u64, bld.def(s1, scc), Operand(0u), Operand(val));
+ }
+}
+
+Temp get_alu_src(struct isel_context *ctx, nir_alu_src src, unsigned size=1)
+{
+ if (src.src.ssa->num_components == 1 && src.swizzle[0] == 0 && size == 1)
+ return get_ssa_temp(ctx, src.src.ssa);
+
+ if (src.src.ssa->num_components == size) {
+ bool identity_swizzle = true;
+ for (unsigned i = 0; identity_swizzle && i < size; i++) {
+ if (src.swizzle[i] != i)
+ identity_swizzle = false;
+ }
+ if (identity_swizzle)
+ return get_ssa_temp(ctx, src.src.ssa);
+ }
+
+ Temp vec = get_ssa_temp(ctx, src.src.ssa);
+ unsigned elem_size = vec.size() / src.src.ssa->num_components;
+ assert(elem_size > 0); /* TODO: 8 and 16-bit vectors not supported */
+ assert(vec.size() % elem_size == 0);
+
+ RegClass elem_rc = RegClass(vec.type(), elem_size);
+ if (size == 1) {
+ return emit_extract_vector(ctx, vec, src.swizzle[0], elem_rc);
+ } else {
+ assert(size <= 4);
+ std::array<Temp,4> elems;
+ aco_ptr<Pseudo_instruction> vec_instr{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, size, 1)};
+ for (unsigned i = 0; i < size; ++i) {
+ elems[i] = emit_extract_vector(ctx, vec, src.swizzle[i], elem_rc);
+ vec_instr->operands[i] = Operand{elems[i]};
+ }
+ Temp dst{ctx->program->allocateId(), RegClass(vec.type(), elem_size * size)};
+ vec_instr->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec_instr));
+ ctx->allocated_vec.emplace(dst.id(), elems);
+ return dst;
+ }
+}
+
+Temp convert_pointer_to_64_bit(isel_context *ctx, Temp ptr)
+{
+ if (ptr.size() == 2)
+ return ptr;
+ Builder bld(ctx->program, ctx->block);
+ return bld.pseudo(aco_opcode::p_create_vector, bld.def(s2),
+ ptr, Operand((unsigned)ctx->options->address32_hi));
+}
+
+void emit_sop2_instruction(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst, bool writes_scc)
+{
+ aco_ptr<SOP2_instruction> sop2{create_instruction<SOP2_instruction>(op, Format::SOP2, 2, writes_scc ? 2 : 1)};
+ sop2->operands[0] = Operand(get_alu_src(ctx, instr->src[0]));
+ sop2->operands[1] = Operand(get_alu_src(ctx, instr->src[1]));
+ sop2->definitions[0] = Definition(dst);
+ if (writes_scc)
+ sop2->definitions[1] = Definition(ctx->program->allocateId(), scc, s1);
+ ctx->block->instructions.emplace_back(std::move(sop2));
+}
+
+void emit_vop2_instruction(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst, bool commutative, bool swap_srcs=false)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp src0 = get_alu_src(ctx, instr->src[swap_srcs ? 1 : 0]);
+ Temp src1 = get_alu_src(ctx, instr->src[swap_srcs ? 0 : 1]);
+ if (src1.type() == RegType::sgpr) {
+ if (commutative && src0.type() == RegType::vgpr) {
+ Temp t = src0;
+ src0 = src1;
+ src1 = t;
+ } else if (src0.type() == RegType::vgpr &&
+ op != aco_opcode::v_madmk_f32 &&
+ op != aco_opcode::v_madak_f32 &&
+ op != aco_opcode::v_madmk_f16 &&
+ op != aco_opcode::v_madak_f16) {
+ /* If the instruction is not commutative, we emit a VOP3A instruction */
+ bld.vop2_e64(op, Definition(dst), src0, src1);
+ return;
+ } else {
+ src1 = bld.copy(bld.def(RegType::vgpr, src1.size()), src1); //TODO: as_vgpr
+ }
+ }
+ bld.vop2(op, Definition(dst), src0, src1);
+}
+
+void emit_vop3a_instruction(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst)
+{
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ Temp src2 = get_alu_src(ctx, instr->src[2]);
+
+ /* ensure that the instruction has at most 1 sgpr operand
+ * The optimizer will inline constants for us */
+ if (src0.type() == RegType::sgpr && src1.type() == RegType::sgpr)
+ src0 = as_vgpr(ctx, src0);
+ if (src1.type() == RegType::sgpr && src2.type() == RegType::sgpr)
+ src1 = as_vgpr(ctx, src1);
+ if (src2.type() == RegType::sgpr && src0.type() == RegType::sgpr)
+ src2 = as_vgpr(ctx, src2);
+
+ Builder bld(ctx->program, ctx->block);
+ bld.vop3(op, Definition(dst), src0, src1, src2);
+}
+
+void emit_vop1_instruction(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst)
+{
+ Builder bld(ctx->program, ctx->block);
+ bld.vop1(op, Definition(dst), get_alu_src(ctx, instr->src[0]));
+}
+
+void emit_vopc_instruction(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst)
+{
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ aco_ptr<Instruction> vopc;
+ if (src1.type() == RegType::sgpr) {
+ if (src0.type() == RegType::vgpr) {
+ /* to swap the operands, we might also have to change the opcode */
+ switch (op) {
+ case aco_opcode::v_cmp_lt_f32:
+ op = aco_opcode::v_cmp_gt_f32;
+ break;
+ case aco_opcode::v_cmp_ge_f32:
+ op = aco_opcode::v_cmp_le_f32;
+ break;
+ case aco_opcode::v_cmp_lt_i32:
+ op = aco_opcode::v_cmp_gt_i32;
+ break;
+ case aco_opcode::v_cmp_ge_i32:
+ op = aco_opcode::v_cmp_le_i32;
+ break;
+ case aco_opcode::v_cmp_lt_u32:
+ op = aco_opcode::v_cmp_gt_u32;
+ break;
+ case aco_opcode::v_cmp_ge_u32:
+ op = aco_opcode::v_cmp_le_u32;
+ break;
+ case aco_opcode::v_cmp_lt_f64:
+ op = aco_opcode::v_cmp_gt_f64;
+ break;
+ case aco_opcode::v_cmp_ge_f64:
+ op = aco_opcode::v_cmp_le_f64;
+ break;
+ case aco_opcode::v_cmp_lt_i64:
+ op = aco_opcode::v_cmp_gt_i64;
+ break;
+ case aco_opcode::v_cmp_ge_i64:
+ op = aco_opcode::v_cmp_le_i64;
+ break;
+ case aco_opcode::v_cmp_lt_u64:
+ op = aco_opcode::v_cmp_gt_u64;
+ break;
+ case aco_opcode::v_cmp_ge_u64:
+ op = aco_opcode::v_cmp_le_u64;
+ break;
+ default: /* eq and ne are commutative */
+ break;
+ }
+ Temp t = src0;
+ src0 = src1;
+ src1 = t;
+ } else {
+ src1 = as_vgpr(ctx, src1);
+ }
+ }
+ Builder bld(ctx->program, ctx->block);
+ bld.vopc(op, Definition(dst), src0, src1).def(0).setHint(vcc);
+}
+
+void emit_comparison(isel_context *ctx, nir_alu_instr *instr, aco_opcode op, Temp dst)
+{
+ if (dst.regClass() == s2) {
+ emit_vopc_instruction(ctx, instr, op, dst);
+ if (!ctx->divergent_vals[instr->dest.dest.ssa.index])
+ emit_split_vector(ctx, dst, 2);
+ } else if (dst.regClass() == s1) {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ assert(src0.type() == RegType::sgpr && src1.type() == RegType::sgpr);
+
+ Builder bld(ctx->program, ctx->block);
+ bld.sopc(op, bld.scc(Definition(dst)), src0, src1);
+
+ } else {
+ assert(false);
+ }
+}
+
+void emit_boolean_logic(isel_context *ctx, nir_alu_instr *instr, aco_opcode op32, aco_opcode op64, Temp dst)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == s2) {
+ bld.sop2(op64, Definition(dst), bld.def(s1, scc),
+ as_divergent_bool(ctx, src0, false), as_divergent_bool(ctx, src1, false));
+ } else {
+ assert(dst.regClass() == s1);
+ bld.sop2(op32, bld.def(s1), bld.scc(Definition(dst)),
+ as_uniform_bool(ctx, src0), as_uniform_bool(ctx, src1));
+ }
+}
+
+
+void emit_bcsel(isel_context *ctx, nir_alu_instr *instr, Temp dst)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp cond = get_alu_src(ctx, instr->src[0]);
+ Temp then = get_alu_src(ctx, instr->src[1]);
+ Temp els = get_alu_src(ctx, instr->src[2]);
+
+ if (dst.type() == RegType::vgpr) {
+ cond = as_divergent_bool(ctx, cond, true);
+
+ aco_ptr<Instruction> bcsel;
+ if (dst.size() == 1) {
+ then = as_vgpr(ctx, then);
+ els = as_vgpr(ctx, els);
+
+ bld.vop2(aco_opcode::v_cndmask_b32, Definition(dst), els, then, cond);
+ } else if (dst.size() == 2) {
+ Temp then_lo = bld.tmp(v1), then_hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(then_lo), Definition(then_hi), then);
+ Temp else_lo = bld.tmp(v1), else_hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(else_lo), Definition(else_hi), els);
+
+ Temp dst0 = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), else_lo, then_lo, cond);
+ Temp dst1 = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), else_hi, then_hi, cond);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), dst0, dst1);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ return;
+ }
+
+ if (instr->dest.dest.ssa.bit_size != 1) { /* uniform condition and values in sgpr */
+ if (dst.regClass() == s1 || dst.regClass() == s2) {
+ assert((then.regClass() == s1 || then.regClass() == s2) && els.regClass() == then.regClass());
+ aco_opcode op = dst.regClass() == s1 ? aco_opcode::s_cselect_b32 : aco_opcode::s_cselect_b64;
+ bld.sop2(op, Definition(dst), then, els, bld.scc(as_uniform_bool(ctx, cond)));
+ } else {
+ fprintf(stderr, "Unimplemented uniform bcsel bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ return;
+ }
+
+ /* boolean bcsel */
+ assert(instr->dest.dest.ssa.bit_size == 1);
+
+ if (dst.regClass() == s1)
+ cond = as_uniform_bool(ctx, cond);
+
+ if (cond.regClass() == s1) { /* uniform selection */
+ aco_opcode op;
+ if (dst.regClass() == s2) {
+ op = aco_opcode::s_cselect_b64;
+ then = as_divergent_bool(ctx, then, false);
+ els = as_divergent_bool(ctx, els, false);
+ } else {
+ assert(dst.regClass() == s1);
+ op = aco_opcode::s_cselect_b32;
+ then = as_uniform_bool(ctx, then);
+ els = as_uniform_bool(ctx, els);
+ }
+ bld.sop2(op, Definition(dst), then, els, bld.scc(cond));
+ return;
+ }
+
+ /* divergent boolean bcsel
+ * this implements bcsel on bools: dst = s0 ? s1 : s2
+ * are going to be: dst = (s0 & s1) | (~s0 & s2) */
+ assert (dst.regClass() == s2);
+ then = as_divergent_bool(ctx, then, false);
+ els = as_divergent_bool(ctx, els, false);
+
+ if (cond.id() != then.id())
+ then = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), cond, then);
+
+ if (cond.id() == els.id())
+ bld.sop1(aco_opcode::s_mov_b64, Definition(dst), then);
+ else
+ bld.sop2(aco_opcode::s_or_b64, Definition(dst), bld.def(s1, scc), then,
+ bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.def(s1, scc), els, cond));
+}
+
+void visit_alu_instr(isel_context *ctx, nir_alu_instr *instr)
+{
+ if (!instr->dest.dest.is_ssa) {
+ fprintf(stderr, "nir alu dst not in ssa: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ abort();
+ }
+ Builder bld(ctx->program, ctx->block);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.dest.ssa);
+ switch(instr->op) {
+ case nir_op_vec2:
+ case nir_op_vec3:
+ case nir_op_vec4: {
+ std::array<Temp,4> elems;
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, instr->dest.dest.ssa.num_components, 1)};
+ for (unsigned i = 0; i < instr->dest.dest.ssa.num_components; ++i) {
+ elems[i] = get_alu_src(ctx, instr->src[i]);
+ vec->operands[i] = Operand{elems[i]};
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), elems);
+ break;
+ }
+ case nir_op_mov: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ aco_ptr<Instruction> mov;
+ if (dst.type() == RegType::sgpr) {
+ if (src.type() == RegType::vgpr)
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), src);
+ else if (src.regClass() == s1)
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), src);
+ else if (src.regClass() == s2)
+ bld.sop1(aco_opcode::s_mov_b64, Definition(dst), src);
+ else
+ unreachable("wrong src register class for nir_op_imov");
+ } else if (dst.regClass() == v1) {
+ bld.vop1(aco_opcode::v_mov_b32, Definition(dst), src);
+ } else if (dst.regClass() == v2) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src);
+ } else {
+ nir_print_instr(&instr->instr, stderr);
+ unreachable("Should have been lowered to scalar.");
+ }
+ break;
+ }
+ case nir_op_inot: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ /* uniform booleans */
+ if (instr->dest.dest.ssa.bit_size == 1 && dst.regClass() == s1) {
+ if (src.regClass() == s1) {
+ /* in this case, src is either 1 or 0 */
+ bld.sop2(aco_opcode::s_xor_b32, bld.def(s1), bld.scc(Definition(dst)), Operand(1u), src);
+ } else {
+ /* src is either exec_mask or 0 */
+ assert(src.regClass() == s2);
+ bld.sopc(aco_opcode::s_cmp_eq_u64, bld.scc(Definition(dst)), Operand(0u), src);
+ }
+ } else if (dst.regClass() == v1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_not_b32, dst);
+ } else if (dst.type() == RegType::sgpr) {
+ aco_opcode opcode = dst.size() == 1 ? aco_opcode::s_not_b32 : aco_opcode::s_not_b64;
+ bld.sop1(opcode, Definition(dst), bld.def(s1, scc), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ineg: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == v1) {
+ bld.vsub32(Definition(dst), Operand(0u), Operand(src));
+ } else if (dst.regClass() == s1) {
+ bld.sop2(aco_opcode::s_mul_i32, Definition(dst), Operand((uint32_t) -1), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_iabs: {
+ if (dst.regClass() == s1) {
+ bld.sop1(aco_opcode::s_abs_i32, Definition(dst), bld.def(s1, scc), get_alu_src(ctx, instr->src[0]));
+ } else if (dst.regClass() == v1) {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ bld.vop2(aco_opcode::v_max_i32, Definition(dst), src, bld.vsub32(bld.def(v1), Operand(0u), src));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_isign: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == s1) {
+ Temp tmp = bld.sop2(aco_opcode::s_ashr_i32, bld.def(s1), bld.def(s1, scc), src, Operand(31u));
+ Temp gtz = bld.sopc(aco_opcode::s_cmp_gt_i32, bld.def(s1, scc), src, Operand(0u));
+ bld.sop2(aco_opcode::s_add_i32, Definition(dst), bld.def(s1, scc), gtz, tmp);
+ } else if (dst.regClass() == s2) {
+ Temp neg = bld.sop2(aco_opcode::s_ashr_i64, bld.def(s2), bld.def(s1, scc), src, Operand(63u));
+ Temp neqz = bld.sopc(aco_opcode::s_cmp_lg_u64, bld.def(s1, scc), src, Operand(0u));
+ bld.sop2(aco_opcode::s_or_b64, Definition(dst), bld.def(s1, scc), neg, neqz);
+ } else if (dst.regClass() == v1) {
+ Temp tmp = bld.vop2(aco_opcode::v_ashrrev_i32, bld.def(v1), Operand(31u), src);
+ Temp gtz = bld.vopc(aco_opcode::v_cmp_ge_i32, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ bld.vop2(aco_opcode::v_cndmask_b32, Definition(dst), Operand(1u), tmp, gtz);
+ } else if (dst.regClass() == v2) {
+ Temp upper = emit_extract_vector(ctx, src, 1, v1);
+ Temp neg = bld.vop2(aco_opcode::v_ashrrev_i32, bld.def(v1), Operand(31u), upper);
+ Temp gtz = bld.vopc(aco_opcode::v_cmp_ge_i64, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ Temp lower = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(1u), neg, gtz);
+ upper = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0u), neg, gtz);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imax: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_max_i32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_max_i32, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umax: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_max_u32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_max_u32, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imin: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_min_i32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_min_i32, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umin: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_min_u32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_min_u32, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ior: {
+ if (instr->dest.dest.ssa.bit_size == 1) {
+ emit_boolean_logic(ctx, instr, aco_opcode::s_or_b32, aco_opcode::s_or_b64, dst);
+ } else if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_or_b32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_or_b32, dst, true);
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_or_b64, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_iand: {
+ if (instr->dest.dest.ssa.bit_size == 1) {
+ emit_boolean_logic(ctx, instr, aco_opcode::s_and_b32, aco_opcode::s_and_b64, dst);
+ } else if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_and_b32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_and_b32, dst, true);
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_and_b64, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ixor: {
+ if (instr->dest.dest.ssa.bit_size == 1) {
+ emit_boolean_logic(ctx, instr, aco_opcode::s_xor_b32, aco_opcode::s_xor_b64, dst);
+ } else if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_xor_b32, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_xor_b32, dst, true);
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_xor_b64, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ushr: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_lshrrev_b32, dst, false, true);
+ } else if (dst.regClass() == v2) {
+ bld.vop3(aco_opcode::v_lshrrev_b64, Definition(dst),
+ get_alu_src(ctx, instr->src[1]), get_alu_src(ctx, instr->src[0]));
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_lshr_b64, dst, true);
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_lshr_b32, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ishl: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_lshlrev_b32, dst, false, true);
+ } else if (dst.regClass() == v2) {
+ bld.vop3(aco_opcode::v_lshlrev_b64, Definition(dst),
+ get_alu_src(ctx, instr->src[1]), get_alu_src(ctx, instr->src[0]));
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_lshl_b32, dst, true);
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_lshl_b64, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ishr: {
+ if (dst.regClass() == v1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_ashrrev_i32, dst, false, true);
+ } else if (dst.regClass() == v2) {
+ bld.vop3(aco_opcode::v_ashrrev_i64, Definition(dst),
+ get_alu_src(ctx, instr->src[1]), get_alu_src(ctx, instr->src[0]));
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_ashr_i32, dst, true);
+ } else if (dst.regClass() == s2) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_ashr_i64, dst, true);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_find_lsb: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (src.regClass() == s1) {
+ bld.sop1(aco_opcode::s_ff1_i32_b32, Definition(dst), src);
+ } else if (src.regClass() == v1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_ffbl_b32, dst);
+ } else if (src.regClass() == s2) {
+ bld.sop1(aco_opcode::s_ff1_i32_b64, Definition(dst), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ufind_msb:
+ case nir_op_ifind_msb: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (src.regClass() == s1 || src.regClass() == s2) {
+ aco_opcode op = src.regClass() == s2 ?
+ (instr->op == nir_op_ufind_msb ? aco_opcode::s_flbit_i32_b64 : aco_opcode::s_flbit_i32_i64) :
+ (instr->op == nir_op_ufind_msb ? aco_opcode::s_flbit_i32_b32 : aco_opcode::s_flbit_i32);
+ Temp msb_rev = bld.sop1(op, bld.def(s1), src);
+
+ Builder::Result sub = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc),
+ Operand(src.size() * 32u - 1u), msb_rev);
+ Temp msb = sub.def(0).getTemp();
+ Temp carry = sub.def(1).getTemp();
+
+ bld.sop2(aco_opcode::s_cselect_b32, Definition(dst), Operand((uint32_t)-1), msb, carry);
+ } else if (src.regClass() == v1) {
+ aco_opcode op = instr->op == nir_op_ufind_msb ? aco_opcode::v_ffbh_u32 : aco_opcode::v_ffbh_i32;
+ Temp msb_rev = bld.tmp(v1);
+ emit_vop1_instruction(ctx, instr, op, msb_rev);
+ Temp msb = bld.tmp(v1);
+ Temp carry = bld.vsub32(Definition(msb), Operand(31u), Operand(msb_rev), true).def(1).getTemp();
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), msb, Operand((uint32_t)-1), carry);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_bitfield_reverse: {
+ if (dst.regClass() == s1) {
+ bld.sop1(aco_opcode::s_brev_b32, Definition(dst), get_alu_src(ctx, instr->src[0]));
+ } else if (dst.regClass() == v1) {
+ bld.vop1(aco_opcode::v_bfrev_b32, Definition(dst), get_alu_src(ctx, instr->src[0]));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_iadd: {
+ if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_add_u32, dst, true);
+ break;
+ }
+
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == v1) {
+ bld.vadd32(Definition(dst), Operand(src0), Operand(src1));
+ break;
+ }
+
+ assert(src0.size() == 2 && src1.size() == 2);
+ Temp src00 = bld.tmp(src0.type(), 1);
+ Temp src01 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src00), Definition(src01), src0);
+ Temp src10 = bld.tmp(src1.type(), 1);
+ Temp src11 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src10), Definition(src11), src1);
+
+ if (dst.regClass() == s2) {
+ Temp carry = bld.tmp(s1);
+ Temp dst0 = bld.sop2(aco_opcode::s_add_u32, bld.def(s1), bld.scc(Definition(carry)), src00, src10);
+ Temp dst1 = bld.sop2(aco_opcode::s_addc_u32, bld.def(s1), bld.def(s1, scc), src01, src11, bld.scc(carry));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), dst0, dst1);
+ } else if (dst.regClass() == v2) {
+ Temp dst0 = bld.tmp(v1);
+ Temp carry = bld.vadd32(Definition(dst0), src00, src10, true).def(1).getTemp();
+ Temp dst1 = bld.vadd32(bld.def(v1), src01, src11, false, carry);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), dst0, dst1);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_uadd_sat: {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == s1) {
+ Temp tmp = bld.tmp(s1), carry = bld.tmp(s1);
+ bld.sop2(aco_opcode::s_add_u32, Definition(tmp), bld.scc(Definition(carry)),
+ src0, src1);
+ bld.sop2(aco_opcode::s_cselect_b32, Definition(dst), Operand((uint32_t) -1), tmp, bld.scc(carry));
+ } else if (dst.regClass() == v1) {
+ if (ctx->options->chip_class >= GFX9) {
+ aco_ptr<VOP3A_instruction> add{create_instruction<VOP3A_instruction>(aco_opcode::v_add_u32, asVOP3(Format::VOP2), 2, 1)};
+ add->operands[0] = Operand(src0);
+ add->operands[1] = Operand(src1);
+ add->definitions[0] = Definition(dst);
+ add->clamp = 1;
+ ctx->block->instructions.emplace_back(std::move(add));
+ } else {
+ if (src1.regClass() != v1)
+ std::swap(src0, src1);
+ assert(src1.regClass() == v1);
+ Temp tmp = bld.tmp(v1);
+ Temp carry = bld.vadd32(Definition(tmp), src0, src1, true).def(1).getTemp();
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), tmp, Operand((uint32_t) -1), carry);
+ }
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_uadd_carry: {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == s1) {
+ bld.sop2(aco_opcode::s_add_u32, bld.def(s1), bld.scc(Definition(dst)), src0, src1);
+ break;
+ }
+ if (dst.regClass() == v1) {
+ Temp carry = bld.vadd32(bld.def(v1), src0, src1, true).def(1).getTemp();
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), Operand(1u), carry);
+ break;
+ }
+
+ Temp src00 = bld.tmp(src0.type(), 1);
+ Temp src01 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src00), Definition(src01), src0);
+ Temp src10 = bld.tmp(src1.type(), 1);
+ Temp src11 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src10), Definition(src11), src1);
+ if (dst.regClass() == s2) {
+ Temp carry = bld.tmp(s1);
+ bld.sop2(aco_opcode::s_add_u32, bld.def(s1), bld.scc(Definition(carry)), src00, src10);
+ carry = bld.sop2(aco_opcode::s_addc_u32, bld.def(s1), bld.scc(bld.def(s1)), src01, src11, bld.scc(carry)).def(1).getTemp();
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), carry, Operand(0u));
+ } else if (dst.regClass() == v2) {
+ Temp carry = bld.vadd32(bld.def(v1), src00, src10, true).def(1).getTemp();
+ carry = bld.vadd32(bld.def(v1), src01, src11, true, carry).def(1).getTemp();
+ carry = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0u), Operand(1u), carry);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), carry, Operand(0u));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_isub: {
+ if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_sub_i32, dst, true);
+ break;
+ }
+
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == v1) {
+ bld.vsub32(Definition(dst), src0, src1);
+ break;
+ }
+
+ Temp src00 = bld.tmp(src0.type(), 1);
+ Temp src01 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src00), Definition(src01), src0);
+ Temp src10 = bld.tmp(src1.type(), 1);
+ Temp src11 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src10), Definition(src11), src1);
+ if (dst.regClass() == s2) {
+ Temp carry = bld.tmp(s1);
+ Temp dst0 = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.scc(Definition(carry)), src00, src10);
+ Temp dst1 = bld.sop2(aco_opcode::s_subb_u32, bld.def(s1), bld.def(s1, scc), src01, src11, carry);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), dst0, dst1);
+ } else if (dst.regClass() == v2) {
+ Temp lower = bld.tmp(v1);
+ Temp borrow = bld.vsub32(Definition(lower), src00, src10, true).def(1).getTemp();
+ Temp upper = bld.vsub32(bld.def(v1), src01, src11, false, borrow);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_usub_borrow: {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.regClass() == s1) {
+ bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.scc(Definition(dst)), src0, src1);
+ break;
+ } else if (dst.regClass() == v1) {
+ Temp borrow = bld.vsub32(bld.def(v1), src0, src1, true).def(1).getTemp();
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), Operand(1u), borrow);
+ break;
+ }
+
+ Temp src00 = bld.tmp(src0.type(), 1);
+ Temp src01 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src00), Definition(src01), src0);
+ Temp src10 = bld.tmp(src1.type(), 1);
+ Temp src11 = bld.tmp(dst.type(), 1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src10), Definition(src11), src1);
+ if (dst.regClass() == s2) {
+ Temp borrow = bld.tmp(s1);
+ bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.scc(Definition(borrow)), src00, src10);
+ borrow = bld.sop2(aco_opcode::s_subb_u32, bld.def(s1), bld.scc(bld.def(s1)), src01, src11, bld.scc(borrow)).def(1).getTemp();
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), borrow, Operand(0u));
+ } else if (dst.regClass() == v2) {
+ Temp borrow = bld.vsub32(bld.def(v1), src00, src10, true).def(1).getTemp();
+ borrow = bld.vsub32(bld.def(v1), src01, src11, true, Operand(borrow)).def(1).getTemp();
+ borrow = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0u), Operand(1u), borrow);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), borrow, Operand(0u));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imul: {
+ if (dst.regClass() == v1) {
+ bld.vop3(aco_opcode::v_mul_lo_u32, Definition(dst),
+ get_alu_src(ctx, instr->src[0]), get_alu_src(ctx, instr->src[1]));
+ } else if (dst.regClass() == s1) {
+ emit_sop2_instruction(ctx, instr, aco_opcode::s_mul_i32, dst, false);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umul_high: {
+ if (dst.regClass() == v1) {
+ bld.vop3(aco_opcode::v_mul_hi_u32, Definition(dst), get_alu_src(ctx, instr->src[0]), get_alu_src(ctx, instr->src[1]));
+ } else if (dst.regClass() == s1 && ctx->options->chip_class >= GFX9) {
+ bld.sop2(aco_opcode::s_mul_hi_u32, Definition(dst), get_alu_src(ctx, instr->src[0]), get_alu_src(ctx, instr->src[1]));
+ } else if (dst.regClass() == s1) {
+ Temp tmp = bld.vop3(aco_opcode::v_mul_hi_u32, bld.def(v1), get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), tmp);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imul_high: {
+ if (dst.regClass() == v1) {
+ bld.vop3(aco_opcode::v_mul_hi_i32, Definition(dst), get_alu_src(ctx, instr->src[0]), get_alu_src(ctx, instr->src[1]));
+ } else if (dst.regClass() == s1 && ctx->options->chip_class >= GFX9) {
+ bld.sop2(aco_opcode::s_mul_hi_i32, Definition(dst), get_alu_src(ctx, instr->src[0]), get_alu_src(ctx, instr->src[1]));
+ } else if (dst.regClass() == s1) {
+ Temp tmp = bld.vop3(aco_opcode::v_mul_hi_i32, bld.def(v1), get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), tmp);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmul: {
+ if (dst.size() == 1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_mul_f32, dst, true);
+ } else if (dst.size() == 2) {
+ bld.vop3(aco_opcode::v_mul_f64, Definition(dst), get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fadd: {
+ if (dst.size() == 1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_add_f32, dst, true);
+ } else if (dst.size() == 2) {
+ bld.vop3(aco_opcode::v_add_f64, Definition(dst), get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fsub: {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ if (dst.size() == 1) {
+ if (src1.type() == RegType::vgpr || src0.type() != RegType::vgpr)
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_sub_f32, dst, false);
+ else
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_subrev_f32, dst, true);
+ } else if (dst.size() == 2) {
+ Instruction* add = bld.vop3(aco_opcode::v_add_f64, Definition(dst),
+ get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ VOP3A_instruction* sub = static_cast<VOP3A_instruction*>(add);
+ sub->neg[1] = true;
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmod:
+ case nir_op_frem: {
+ if (dst.size() == 1) {
+ Temp rcp = bld.vop1(aco_opcode::v_rcp_f32, bld.def(v1), get_alu_src(ctx, instr->src[1]));
+ Temp mul = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), get_alu_src(ctx, instr->src[0]), rcp);
+
+ aco_opcode op = instr->op == nir_op_fmod ? aco_opcode::v_floor_f32 : aco_opcode::v_trunc_f32;
+ Temp floor = bld.vop1(op, bld.def(v1), mul);
+
+ mul = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), get_alu_src(ctx, instr->src[1]), floor);
+ bld.vop2(aco_opcode::v_sub_f32, Definition(dst), get_alu_src(ctx, instr->src[0]), mul);
+ } else if (dst.size() == 2) {
+ Temp rcp = bld.vop1(aco_opcode::v_rcp_f64, bld.def(v2), get_alu_src(ctx, instr->src[1]));
+ Temp mul = bld.vop3(aco_opcode::v_mul_f64, bld.def(v2), get_alu_src(ctx, instr->src[0]), rcp);
+
+ aco_opcode op = instr->op == nir_op_fmod ? aco_opcode::v_floor_f64 : aco_opcode::v_trunc_f64;
+ Temp floor = bld.vop1(op, bld.def(v1), mul);
+
+ mul = bld.vop3(aco_opcode::v_mul_f64, bld.def(v2), get_alu_src(ctx, instr->src[1]), floor);
+ Instruction* add = bld.vop3(aco_opcode::v_add_f64, Definition(dst), get_alu_src(ctx, instr->src[0]), mul);
+ VOP3A_instruction* sub = static_cast<VOP3A_instruction*>(add);
+ sub->neg[1] = true;
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmax: {
+ if (dst.size() == 1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_max_f32, dst, true);
+ } else if (dst.size() == 2) {
+ bld.vop3(aco_opcode::v_max_f64, Definition(dst),
+ get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmin: {
+ if (dst.size() == 1) {
+ emit_vop2_instruction(ctx, instr, aco_opcode::v_min_f32, dst, true);
+ } else if (dst.size() == 2) {
+ bld.vop3(aco_opcode::v_min_f64, Definition(dst),
+ get_alu_src(ctx, instr->src[0]),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[1])));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmax3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_max3_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmin3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_min3_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fmed3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_med3_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umax3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_max3_u32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umin3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_min3_u32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_umed3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_med3_u32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imax3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_max3_i32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imin3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_min3_i32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_imed3: {
+ if (dst.size() == 1) {
+ emit_vop3a_instruction(ctx, instr, aco_opcode::v_med3_i32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_cube_face_coord: {
+ Temp in = get_alu_src(ctx, instr->src[0], 3);
+ Temp src[3] = { emit_extract_vector(ctx, in, 0, v1),
+ emit_extract_vector(ctx, in, 1, v1),
+ emit_extract_vector(ctx, in, 2, v1) };
+ Temp ma = bld.vop3(aco_opcode::v_cubema_f32, bld.def(v1), src[0], src[1], src[2]);
+ ma = bld.vop1(aco_opcode::v_rcp_f32, bld.def(v1), ma);
+ Temp sc = bld.vop3(aco_opcode::v_cubesc_f32, bld.def(v1), src[0], src[1], src[2]);
+ Temp tc = bld.vop3(aco_opcode::v_cubetc_f32, bld.def(v1), src[0], src[1], src[2]);
+ sc = bld.vop2(aco_opcode::v_madak_f32, bld.def(v1), sc, ma, Operand(0x3f000000u/*0.5*/));
+ tc = bld.vop2(aco_opcode::v_madak_f32, bld.def(v1), tc, ma, Operand(0x3f000000u/*0.5*/));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), sc, tc);
+ break;
+ }
+ case nir_op_cube_face_index: {
+ Temp in = get_alu_src(ctx, instr->src[0], 3);
+ Temp src[3] = { emit_extract_vector(ctx, in, 0, v1),
+ emit_extract_vector(ctx, in, 1, v1),
+ emit_extract_vector(ctx, in, 2, v1) };
+ bld.vop3(aco_opcode::v_cubeid_f32, Definition(dst), src[0], src[1], src[2]);
+ break;
+ }
+ case nir_op_bcsel: {
+ emit_bcsel(ctx, instr, dst);
+ break;
+ }
+ case nir_op_frsq: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rsq_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rsq_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fneg: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.size() == 1) {
+ bld.vop2(aco_opcode::v_xor_b32, Definition(dst), Operand(0x80000000u), as_vgpr(ctx, src));
+ } else if (dst.size() == 2) {
+ Temp upper = bld.tmp(v1), lower = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), src);
+ upper = bld.vop2(aco_opcode::v_xor_b32, bld.def(v1), Operand(0x80000000u), upper);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fabs: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.size() == 1) {
+ bld.vop2(aco_opcode::v_and_b32, Definition(dst), Operand(0x7FFFFFFFu), as_vgpr(ctx, src));
+ } else if (dst.size() == 2) {
+ Temp upper = bld.tmp(v1), lower = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), src);
+ upper = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(0x7FFFFFFFu), upper);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fsat: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.size() == 1) {
+ bld.vop3(aco_opcode::v_med3_f32, Definition(dst), Operand(0u), Operand(0x3f800000u), src);
+ } else if (dst.size() == 2) {
+ Instruction* add = bld.vop3(aco_opcode::v_add_f64, Definition(dst), src, Operand(0u));
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(add);
+ vop3->clamp = true;
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_flog2: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_log_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_frcp: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rcp_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rcp_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fexp2: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_exp_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fsqrt: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_sqrt_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_sqrt_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ffract: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_fract_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_fract_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ffloor: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_floor_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_floor_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fceil: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_ceil_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_ceil_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ftrunc: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_trunc_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_trunc_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fround_even: {
+ if (dst.size() == 1) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rndne_f32, dst);
+ } else if (dst.size() == 2) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_rndne_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fsin:
+ case nir_op_fcos: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ aco_ptr<Instruction> norm;
+ if (dst.size() == 1) {
+ Temp tmp;
+ Operand half_pi(0x3e22f983u);
+ if (src.type() == RegType::sgpr)
+ tmp = bld.vop2_e64(aco_opcode::v_mul_f32, bld.def(v1), half_pi, src);
+ else
+ tmp = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), half_pi, src);
+
+ /* before GFX9, v_sin_f32 and v_cos_f32 had a valid input domain of [-256, +256] */
+ if (ctx->options->chip_class < GFX9)
+ tmp = bld.vop1(aco_opcode::v_fract_f32, bld.def(v1), tmp);
+
+ aco_opcode opcode = instr->op == nir_op_fsin ? aco_opcode::v_sin_f32 : aco_opcode::v_cos_f32;
+ bld.vop1(opcode, Definition(dst), tmp);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ldexp: {
+ if (dst.size() == 1) {
+ bld.vop3(aco_opcode::v_ldexp_f32, Definition(dst),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[0])),
+ get_alu_src(ctx, instr->src[1]));
+ } else if (dst.size() == 2) {
+ bld.vop3(aco_opcode::v_ldexp_f64, Definition(dst),
+ as_vgpr(ctx, get_alu_src(ctx, instr->src[0])),
+ get_alu_src(ctx, instr->src[1]));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_frexp_sig: {
+ if (dst.size() == 1) {
+ bld.vop1(aco_opcode::v_frexp_mant_f32, Definition(dst),
+ get_alu_src(ctx, instr->src[0]));
+ } else if (dst.size() == 2) {
+ bld.vop1(aco_opcode::v_frexp_mant_f64, Definition(dst),
+ get_alu_src(ctx, instr->src[0]));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_frexp_exp: {
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ bld.vop1(aco_opcode::v_frexp_exp_i32_f32, Definition(dst),
+ get_alu_src(ctx, instr->src[0]));
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ bld.vop1(aco_opcode::v_frexp_exp_i32_f64, Definition(dst),
+ get_alu_src(ctx, instr->src[0]));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fsign: {
+ Temp src = as_vgpr(ctx, get_alu_src(ctx, instr->src[0]));
+ if (dst.size() == 1) {
+ Temp cond = bld.vopc(aco_opcode::v_cmp_nlt_f32, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ src = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0x3f800000u), src, cond);
+ cond = bld.vopc(aco_opcode::v_cmp_le_f32, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ bld.vop2(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0xbf800000u), src, cond);
+ } else if (dst.size() == 2) {
+ Temp cond = bld.vopc(aco_opcode::v_cmp_nlt_f64, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ Temp tmp = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(0x3FF00000u));
+ Temp upper = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), tmp, src, cond);
+
+ cond = bld.vopc(aco_opcode::v_cmp_le_f64, bld.hint_vcc(bld.def(s2)), Operand(0u), src);
+ tmp = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(0xBFF00000u));
+ upper = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), tmp, upper, cond);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), Operand(0u), upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2f32: {
+ if (instr->src[0].src.ssa->bit_size == 64) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f32_f64, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2f64: {
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f64_f32, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_i2f32: {
+ assert(dst.size() == 1);
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f32_i32, dst);
+ break;
+ }
+ case nir_op_i2f64: {
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f64_i32, dst);
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ RegClass rc = RegClass(src.type(), 1);
+ Temp lower = bld.tmp(rc), upper = bld.tmp(rc);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), src);
+ lower = bld.vop1(aco_opcode::v_cvt_f64_u32, bld.def(v2), lower);
+ upper = bld.vop1(aco_opcode::v_cvt_f64_i32, bld.def(v2), upper);
+ upper = bld.vop3(aco_opcode::v_ldexp_f64, bld.def(v2), upper, Operand(32u));
+ bld.vop3(aco_opcode::v_add_f64, Definition(dst), lower, upper);
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_u2f32: {
+ assert(dst.size() == 1);
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f32_u32, dst);
+ break;
+ }
+ case nir_op_u2f64: {
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ emit_vop1_instruction(ctx, instr, aco_opcode::v_cvt_f64_u32, dst);
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ RegClass rc = RegClass(src.type(), 1);
+ Temp lower = bld.tmp(rc), upper = bld.tmp(rc);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), src);
+ lower = bld.vop1(aco_opcode::v_cvt_f64_u32, bld.def(v2), lower);
+ upper = bld.vop1(aco_opcode::v_cvt_f64_u32, bld.def(v2), upper);
+ upper = bld.vop3(aco_opcode::v_ldexp_f64, bld.def(v2), upper, Operand(32u));
+ bld.vop3(aco_opcode::v_add_f64, Definition(dst), lower, upper);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2i32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ if (dst.type() == RegType::vgpr)
+ bld.vop1(aco_opcode::v_cvt_i32_f32, Definition(dst), src);
+ else
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst),
+ bld.vop1(aco_opcode::v_cvt_i32_f32, bld.def(v1), src));
+
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ if (dst.type() == RegType::vgpr)
+ bld.vop1(aco_opcode::v_cvt_i32_f64, Definition(dst), src);
+ else
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst),
+ bld.vop1(aco_opcode::v_cvt_i32_f64, bld.def(v1), src));
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2u32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ if (dst.type() == RegType::vgpr)
+ bld.vop1(aco_opcode::v_cvt_u32_f32, Definition(dst), src);
+ else
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst),
+ bld.vop1(aco_opcode::v_cvt_u32_f32, bld.def(v1), src));
+
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ if (dst.type() == RegType::vgpr)
+ bld.vop1(aco_opcode::v_cvt_u32_f64, Definition(dst), src);
+ else
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst),
+ bld.vop1(aco_opcode::v_cvt_u32_f64, bld.def(v1), src));
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2i64: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32 && dst.type() == RegType::vgpr) {
+ Temp exponent = bld.vop1(aco_opcode::v_frexp_exp_i32_f32, bld.def(v1), src);
+ exponent = bld.vop3(aco_opcode::v_med3_i32, bld.def(v1), Operand(0x0u), exponent, Operand(64u));
+ Temp mantissa = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(0x7fffffu), src);
+ Temp sign = bld.vop2(aco_opcode::v_ashrrev_i32, bld.def(v1), Operand(31u), src);
+ mantissa = bld.vop2(aco_opcode::v_or_b32, bld.def(v1), Operand(0x800000u), mantissa);
+ mantissa = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(7u), mantissa);
+ mantissa = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), Operand(0u), mantissa);
+ Temp new_exponent = bld.tmp(v1);
+ Temp borrow = bld.vsub32(Definition(new_exponent), Operand(63u), exponent, true).def(1).getTemp();
+ mantissa = bld.vop3(aco_opcode::v_lshrrev_b64, bld.def(v2), new_exponent, mantissa);
+ Temp saturate = bld.vop1(aco_opcode::v_bfrev_b32, bld.def(v1), Operand(0xfffffffeu));
+ Temp lower = bld.tmp(v1), upper = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), mantissa);
+ lower = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), lower, Operand(0xffffffffu), borrow);
+ upper = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), upper, saturate, borrow);
+ lower = bld.vop2(aco_opcode::v_xor_b32, bld.def(v1), sign, lower);
+ upper = bld.vop2(aco_opcode::v_xor_b32, bld.def(v1), sign, upper);
+ Temp new_lower = bld.tmp(v1);
+ borrow = bld.vsub32(Definition(new_lower), lower, sign, true).def(1).getTemp();
+ Temp new_upper = bld.vsub32(bld.def(v1), upper, sign, false, borrow);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), new_lower, new_upper);
+
+ } else if (instr->src[0].src.ssa->bit_size == 32 && dst.type() == RegType::sgpr) {
+ if (src.type() == RegType::vgpr)
+ src = bld.as_uniform(src);
+ Temp exponent = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), src, Operand(0x80017u));
+ exponent = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc), exponent, Operand(126u));
+ exponent = bld.sop2(aco_opcode::s_max_u32, bld.def(s1), bld.def(s1, scc), Operand(0u), exponent);
+ exponent = bld.sop2(aco_opcode::s_min_u32, bld.def(s1), bld.def(s1, scc), Operand(64u), exponent);
+ Temp mantissa = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), Operand(0x7fffffu), src);
+ Temp sign = bld.sop2(aco_opcode::s_ashr_i32, bld.def(s1), bld.def(s1, scc), src, Operand(31u));
+ mantissa = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), Operand(0x800000u), mantissa);
+ mantissa = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), mantissa, Operand(7u));
+ mantissa = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), mantissa);
+ exponent = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc), Operand(63u), exponent);
+ mantissa = bld.sop2(aco_opcode::s_lshr_b64, bld.def(s2), bld.def(s1, scc), mantissa, exponent);
+ Temp cond = bld.sopc(aco_opcode::s_cmp_eq_u32, bld.def(s1, scc), exponent, Operand(0xffffffffu)); // exp >= 64
+ Temp saturate = bld.sop1(aco_opcode::s_brev_b64, bld.def(s2), Operand(0xfffffffeu));
+ mantissa = bld.sop2(aco_opcode::s_cselect_b64, bld.def(s2), saturate, mantissa, cond);
+ Temp lower = bld.tmp(s1), upper = bld.tmp(s1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), mantissa);
+ lower = bld.sop2(aco_opcode::s_xor_b32, bld.def(s1), bld.def(s1, scc), sign, lower);
+ upper = bld.sop2(aco_opcode::s_xor_b32, bld.def(s1), bld.def(s1, scc), sign, upper);
+ Temp borrow = bld.tmp(s1);
+ lower = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.scc(Definition(borrow)), lower, sign);
+ upper = bld.sop2(aco_opcode::s_subb_u32, bld.def(s1), bld.def(s1, scc), upper, sign, borrow);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ Temp vec = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), Operand(0x3df00000u));
+ Temp trunc = bld.vop1(aco_opcode::v_trunc_f64, bld.def(v2), src);
+ Temp mul = bld.vop3(aco_opcode::v_mul_f64, bld.def(v2), trunc, vec);
+ vec = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), Operand(0xc1f00000u));
+ Temp floor = bld.vop1(aco_opcode::v_floor_f64, bld.def(v2), mul);
+ Temp fma = bld.vop3(aco_opcode::v_fma_f64, bld.def(v2), floor, vec, trunc);
+ Temp lower = bld.vop1(aco_opcode::v_cvt_u32_f64, bld.def(v1), fma);
+ Temp upper = bld.vop1(aco_opcode::v_cvt_i32_f64, bld.def(v1), floor);
+ if (dst.type() == RegType::sgpr) {
+ lower = bld.as_uniform(lower);
+ upper = bld.as_uniform(upper);
+ }
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_f2u64: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32 && dst.type() == RegType::vgpr) {
+ Temp exponent = bld.vop1(aco_opcode::v_frexp_exp_i32_f32, bld.def(v1), src);
+ Temp exponent_in_range = bld.vopc(aco_opcode::v_cmp_ge_i32, bld.hint_vcc(bld.def(s2)), Operand(64u), exponent);
+ exponent = bld.vop2(aco_opcode::v_max_i32, bld.def(v1), Operand(0x0u), exponent);
+ Temp mantissa = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(0x7fffffu), src);
+ mantissa = bld.vop2(aco_opcode::v_or_b32, bld.def(v1), Operand(0x800000u), mantissa);
+ Temp exponent_small = bld.vsub32(bld.def(v1), Operand(24u), exponent);
+ Temp small = bld.vop2(aco_opcode::v_lshrrev_b32, bld.def(v1), exponent_small, mantissa);
+ mantissa = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), Operand(0u), mantissa);
+ Temp new_exponent = bld.tmp(v1);
+ Temp cond_small = bld.vsub32(Definition(new_exponent), exponent, Operand(24u), true).def(1).getTemp();
+ mantissa = bld.vop3(aco_opcode::v_lshlrev_b64, bld.def(v2), new_exponent, mantissa);
+ Temp lower = bld.tmp(v1), upper = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), mantissa);
+ lower = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), lower, small, cond_small);
+ upper = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), upper, Operand(0u), cond_small);
+ lower = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0xffffffffu), lower, exponent_in_range);
+ upper = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0xffffffffu), upper, exponent_in_range);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+
+ } else if (instr->src[0].src.ssa->bit_size == 32 && dst.type() == RegType::sgpr) {
+ if (src.type() == RegType::vgpr)
+ src = bld.as_uniform(src);
+ Temp exponent = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), src, Operand(0x80017u));
+ exponent = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc), exponent, Operand(126u));
+ exponent = bld.sop2(aco_opcode::s_max_u32, bld.def(s1), bld.def(s1, scc), Operand(0u), exponent);
+ Temp mantissa = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), Operand(0x7fffffu), src);
+ mantissa = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), Operand(0x800000u), mantissa);
+ Temp exponent_small = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc), Operand(24u), exponent);
+ Temp small = bld.sop2(aco_opcode::s_lshr_b32, bld.def(s1), bld.def(s1, scc), mantissa, exponent_small);
+ mantissa = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), mantissa);
+ Temp exponent_large = bld.sop2(aco_opcode::s_sub_u32, bld.def(s1), bld.def(s1, scc), exponent, Operand(24u));
+ mantissa = bld.sop2(aco_opcode::s_lshl_b64, bld.def(s2), bld.def(s1, scc), mantissa, exponent_large);
+ Temp cond = bld.sopc(aco_opcode::s_cmp_ge_i32, bld.def(s1, scc), Operand(64u), exponent);
+ mantissa = bld.sop2(aco_opcode::s_cselect_b64, bld.def(s2), mantissa, Operand(0xffffffffu), cond);
+ Temp lower = bld.tmp(s1), upper = bld.tmp(s1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lower), Definition(upper), mantissa);
+ Temp cond_small = bld.sopc(aco_opcode::s_cmp_le_i32, bld.def(s1, scc), exponent, Operand(24u));
+ lower = bld.sop2(aco_opcode::s_cselect_b32, bld.def(s1), small, lower, cond_small);
+ upper = bld.sop2(aco_opcode::s_cselect_b32, bld.def(s1), Operand(0u), upper, cond_small);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ Temp vec = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), Operand(0x3df00000u));
+ Temp trunc = bld.vop1(aco_opcode::v_trunc_f64, bld.def(v2), src);
+ Temp mul = bld.vop3(aco_opcode::v_mul_f64, bld.def(v2), trunc, vec);
+ vec = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(0u), Operand(0xc1f00000u));
+ Temp floor = bld.vop1(aco_opcode::v_floor_f64, bld.def(v2), mul);
+ Temp fma = bld.vop3(aco_opcode::v_fma_f64, bld.def(v2), floor, vec, trunc);
+ Temp lower = bld.vop1(aco_opcode::v_cvt_u32_f64, bld.def(v1), fma);
+ Temp upper = bld.vop1(aco_opcode::v_cvt_u32_f64, bld.def(v1), floor);
+ if (dst.type() == RegType::sgpr) {
+ lower = bld.as_uniform(lower);
+ upper = bld.as_uniform(upper);
+ }
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lower, upper);
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_b2f32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == s1) {
+ src = as_uniform_bool(ctx, src);
+ bld.sop2(aco_opcode::s_mul_i32, Definition(dst), Operand(0x3f800000u), src);
+ } else if (dst.regClass() == v1) {
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), Operand(0x3f800000u),
+ as_divergent_bool(ctx, src, true));
+ } else {
+ unreachable("Wrong destination register class for nir_op_b2f32.");
+ }
+ break;
+ }
+ case nir_op_b2f64: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == s2) {
+ src = as_uniform_bool(ctx, src);
+ bld.sop2(aco_opcode::s_cselect_b64, Definition(dst), Operand(0x3f800000u), Operand(0u), bld.scc(src));
+ } else if (dst.regClass() == v2) {
+ Temp one = bld.vop1(aco_opcode::v_mov_b32, bld.def(v2), Operand(0x3FF00000u));
+ Temp upper = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0u), one,
+ as_divergent_bool(ctx, src, true));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), Operand(0u), upper);
+ } else {
+ unreachable("Wrong destination register class for nir_op_b2f64.");
+ }
+ break;
+ }
+ case nir_op_i2i32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 64) {
+ /* we can actually just say dst = src, as it would map the lower register */
+ emit_extract_vector(ctx, src, 0, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_u2u32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 16) {
+ if (dst.regClass() == s1) {
+ bld.sop2(aco_opcode::s_and_b32, Definition(dst), bld.def(s1, scc), Operand(0xFFFFu), src);
+ } else {
+ // TODO: do better with SDWA
+ bld.vop2(aco_opcode::v_and_b32, Definition(dst), Operand(0xFFFFu), src);
+ }
+ } else if (instr->src[0].src.ssa->bit_size == 64) {
+ /* we can actually just say dst = src, as it would map the lower register */
+ emit_extract_vector(ctx, src, 0, dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_i2i64: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src, Operand(0u));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_u2u64: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (instr->src[0].src.ssa->bit_size == 32) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src, Operand(0u));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_b2i32: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == s1) {
+ if (src.regClass() == s1) {
+ bld.copy(Definition(dst), src);
+ } else {
+ // TODO: in a post-RA optimization, we can check if src is in VCC, and directly use VCCNZ
+ assert(src.regClass() == s2);
+ bld.sopc(aco_opcode::s_cmp_lg_u64, bld.scc(Definition(dst)), Operand(0u), src);
+ }
+ } else {
+ assert(dst.regClass() == v1 && src.regClass() == s2);
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), Operand(1u), src);
+ }
+ break;
+ }
+ case nir_op_i2b1: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (dst.regClass() == s2) {
+ assert(src.regClass() == v1 || src.regClass() == v2);
+ bld.vopc(src.size() == 2 ? aco_opcode::v_cmp_lg_u64 : aco_opcode::v_cmp_lg_u32,
+ Definition(dst), Operand(0u), src).def(0).setHint(vcc);
+ } else {
+ assert(src.regClass() == s1 && dst.regClass() == s1);
+ bld.sopc(aco_opcode::s_cmp_lg_u32, bld.scc(Definition(dst)), Operand(0u), src);
+ }
+ break;
+ }
+ case nir_op_pack_64_2x32_split: {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src0, src1);
+ break;
+ }
+ case nir_op_unpack_64_2x32_split_x:
+ bld.pseudo(aco_opcode::p_split_vector, Definition(dst), bld.def(dst.regClass()), get_alu_src(ctx, instr->src[0]));
+ break;
+ case nir_op_unpack_64_2x32_split_y:
+ bld.pseudo(aco_opcode::p_split_vector, bld.def(dst.regClass()), Definition(dst), get_alu_src(ctx, instr->src[0]));
+ break;
+ case nir_op_pack_half_2x16: {
+ Temp src = get_alu_src(ctx, instr->src[0], 2);
+
+ if (dst.regClass() == v1) {
+ Temp src0 = bld.tmp(v1);
+ Temp src1 = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src0), Definition(src1), src);
+ bld.vop3(aco_opcode::v_cvt_pkrtz_f16_f32, Definition(dst), src0, src1);
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_unpack_half_2x16_split_x: {
+ if (dst.regClass() == v1) {
+ Builder bld(ctx->program, ctx->block);
+ bld.vop1(aco_opcode::v_cvt_f32_f16, Definition(dst), get_alu_src(ctx, instr->src[0]));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_unpack_half_2x16_split_y: {
+ if (dst.regClass() == v1) {
+ Builder bld(ctx->program, ctx->block);
+ /* TODO: use SDWA here */
+ bld.vop1(aco_opcode::v_cvt_f32_f16, Definition(dst),
+ bld.vop2(aco_opcode::v_lshrrev_b32, bld.def(v1), Operand(16u), as_vgpr(ctx, get_alu_src(ctx, instr->src[0]))));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_fquantize2f16: {
+ Temp f16 = bld.vop1(aco_opcode::v_cvt_f16_f32, bld.def(v1), get_alu_src(ctx, instr->src[0]));
+
+ Temp mask = bld.copy(bld.def(s1), Operand(0x36Fu)); /* value is NOT negative/positive denormal value */
+
+ Temp cmp_res = bld.tmp(s2);
+ bld.vopc_e64(aco_opcode::v_cmp_class_f16, Definition(cmp_res), f16, mask).def(0).setHint(vcc);
+
+ Temp f32 = bld.vop1(aco_opcode::v_cvt_f32_f16, bld.def(v1), f16);
+
+ bld.vop2(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), f32, cmp_res);
+ break;
+ }
+ case nir_op_bfm: {
+ Temp bits = get_alu_src(ctx, instr->src[0]);
+ Temp offset = get_alu_src(ctx, instr->src[1]);
+
+ if (dst.regClass() == s1) {
+ bld.sop2(aco_opcode::s_bfm_b32, Definition(dst), bits, offset);
+ } else if (dst.regClass() == v1) {
+ bld.vop3(aco_opcode::v_bfm_b32, Definition(dst), bits, offset);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_bitfield_select: {
+ /* (mask & insert) | (~mask & base) */
+ Temp bitmask = get_alu_src(ctx, instr->src[0]);
+ Temp insert = get_alu_src(ctx, instr->src[1]);
+ Temp base = get_alu_src(ctx, instr->src[2]);
+
+ /* dst = (insert & bitmask) | (base & ~bitmask) */
+ if (dst.regClass() == s1) {
+ aco_ptr<Instruction> sop2;
+ nir_const_value* const_bitmask = nir_src_as_const_value(instr->src[0].src);
+ nir_const_value* const_insert = nir_src_as_const_value(instr->src[1].src);
+ Operand lhs;
+ if (const_insert && const_bitmask) {
+ lhs = Operand(const_insert->u32 & const_bitmask->u32);
+ } else {
+ insert = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), insert, bitmask);
+ lhs = Operand(insert);
+ }
+
+ Operand rhs;
+ nir_const_value* const_base = nir_src_as_const_value(instr->src[2].src);
+ if (const_base && const_bitmask) {
+ rhs = Operand(const_base->u32 & ~const_bitmask->u32);
+ } else {
+ base = bld.sop2(aco_opcode::s_andn2_b32, bld.def(s1), bld.def(s1, scc), base, bitmask);
+ rhs = Operand(base);
+ }
+
+ bld.sop2(aco_opcode::s_or_b32, Definition(dst), bld.def(s1, scc), rhs, lhs);
+
+ } else if (dst.regClass() == v1) {
+ if (base.type() == RegType::sgpr && (bitmask.type() == RegType::sgpr || (insert.type() == RegType::sgpr)))
+ base = as_vgpr(ctx, base);
+ if (insert.type() == RegType::sgpr && bitmask.type() == RegType::sgpr)
+ insert = as_vgpr(ctx, insert);
+
+ bld.vop3(aco_opcode::v_bfi_b32, Definition(dst), bitmask, insert, base);
+
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ubfe:
+ case nir_op_ibfe: {
+ Temp base = get_alu_src(ctx, instr->src[0]);
+ Temp offset = get_alu_src(ctx, instr->src[1]);
+ Temp bits = get_alu_src(ctx, instr->src[2]);
+
+ if (dst.type() == RegType::sgpr) {
+ Operand extract;
+ nir_const_value* const_offset = nir_src_as_const_value(instr->src[1].src);
+ nir_const_value* const_bits = nir_src_as_const_value(instr->src[2].src);
+ if (const_offset && const_bits) {
+ uint32_t const_extract = (const_bits->u32 << 16) | const_offset->u32;
+ extract = Operand(const_extract);
+ } else {
+ Operand width;
+ if (const_bits) {
+ width = Operand(const_bits->u32 << 16);
+ } else {
+ width = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), bits, Operand(16u));
+ }
+ extract = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), offset, width);
+ }
+
+ aco_opcode opcode;
+ if (dst.regClass() == s1) {
+ if (instr->op == nir_op_ubfe)
+ opcode = aco_opcode::s_bfe_u32;
+ else
+ opcode = aco_opcode::s_bfe_i32;
+ } else if (dst.regClass() == s2) {
+ if (instr->op == nir_op_ubfe)
+ opcode = aco_opcode::s_bfe_u64;
+ else
+ opcode = aco_opcode::s_bfe_i64;
+ } else {
+ unreachable("Unsupported BFE bit size");
+ }
+
+ bld.sop2(opcode, Definition(dst), bld.def(s1, scc), base, extract);
+
+ } else {
+ aco_opcode opcode;
+ if (dst.regClass() == v1) {
+ if (instr->op == nir_op_ubfe)
+ opcode = aco_opcode::v_bfe_u32;
+ else
+ opcode = aco_opcode::v_bfe_i32;
+ } else {
+ unreachable("Unsupported BFE bit size");
+ }
+
+ emit_vop3a_instruction(ctx, instr, opcode, dst);
+ }
+ break;
+ }
+ case nir_op_bit_count: {
+ Temp src = get_alu_src(ctx, instr->src[0]);
+ if (src.regClass() == s1) {
+ bld.sop1(aco_opcode::s_bcnt1_i32_b32, Definition(dst), bld.def(s1, scc), src);
+ } else if (src.regClass() == v1) {
+ bld.vop3(aco_opcode::v_bcnt_u32_b32, Definition(dst), src, Operand(0u));
+ } else if (src.regClass() == v2) {
+ bld.vop3(aco_opcode::v_bcnt_u32_b32, Definition(dst),
+ emit_extract_vector(ctx, src, 1, v1),
+ bld.vop3(aco_opcode::v_bcnt_u32_b32, bld.def(v1),
+ emit_extract_vector(ctx, src, 0, v1), Operand(0u)));
+ } else if (src.regClass() == s2) {
+ bld.sop1(aco_opcode::s_bcnt1_i32_b64, Definition(dst), bld.def(s1, scc), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_flt: {
+ if (instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_f32, dst);
+ else if (instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_f64, dst);
+ break;
+ }
+ case nir_op_fge: {
+ if (instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_f32, dst);
+ else if (instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_f64, dst);
+ break;
+ }
+ case nir_op_feq: {
+ if (instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_eq_f32, dst);
+ else if (instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_eq_f64, dst);
+ break;
+ }
+ case nir_op_fne: {
+ if (instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_neq_f32, dst);
+ else if (instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_neq_f64, dst);
+ break;
+ }
+ case nir_op_ilt: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_i32, dst);
+ else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_lt_i32, dst);
+ else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_i64, dst);
+ break;
+ }
+ case nir_op_ige: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_i32, dst);
+ else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_ge_i32, dst);
+ else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_i64, dst);
+ break;
+ }
+ case nir_op_ieq: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32) {
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_eq_i32, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32) {
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_eq_i32, dst);
+ } else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64) {
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_eq_i64, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 64) {
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_eq_u64, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 1) {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ bld.sopc(aco_opcode::s_cmp_eq_i32, bld.scc(Definition(dst)),
+ as_uniform_bool(ctx, src0), as_uniform_bool(ctx, src1));
+ } else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 1) {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ bld.sop2(aco_opcode::s_xnor_b64, Definition(dst), bld.def(s1, scc),
+ as_divergent_bool(ctx, src0, false), as_divergent_bool(ctx, src1, false));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ine: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32) {
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lg_i32, dst);
+ } else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64) {
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lg_i64, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32) {
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_lg_i32, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 64) {
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_lg_u64, dst);
+ } else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 1) {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ bld.sopc(aco_opcode::s_cmp_lg_i32, bld.scc(Definition(dst)),
+ as_uniform_bool(ctx, src0), as_uniform_bool(ctx, src1));
+ } else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 1) {
+ Temp src0 = get_alu_src(ctx, instr->src[0]);
+ Temp src1 = get_alu_src(ctx, instr->src[1]);
+ bld.sop2(aco_opcode::s_xor_b64, Definition(dst), bld.def(s1, scc),
+ as_divergent_bool(ctx, src0, false), as_divergent_bool(ctx, src1, false));
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_op_ult: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_u32, dst);
+ else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_lt_u32, dst);
+ else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_lt_u64, dst);
+ break;
+ }
+ case nir_op_uge: {
+ if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_u32, dst);
+ else if (dst.regClass() == s1 && instr->src[0].src.ssa->bit_size == 32)
+ emit_comparison(ctx, instr, aco_opcode::s_cmp_ge_u32, dst);
+ else if (dst.regClass() == s2 && instr->src[0].src.ssa->bit_size == 64)
+ emit_comparison(ctx, instr, aco_opcode::v_cmp_ge_u64, dst);
+ break;
+ }
+ case nir_op_fddx:
+ case nir_op_fddy:
+ case nir_op_fddx_fine:
+ case nir_op_fddy_fine:
+ case nir_op_fddx_coarse:
+ case nir_op_fddy_coarse: {
+ Definition tl = bld.def(v1);
+ uint16_t dpp_ctrl;
+ if (instr->op == nir_op_fddx_fine) {
+ bld.vop1_dpp(aco_opcode::v_mov_b32, tl, get_alu_src(ctx, instr->src[0]), dpp_quad_perm(0, 0, 2, 2));
+ dpp_ctrl = dpp_quad_perm(1, 1, 3, 3);
+ } else if (instr->op == nir_op_fddy_fine) {
+ bld.vop1_dpp(aco_opcode::v_mov_b32, tl, get_alu_src(ctx, instr->src[0]), dpp_quad_perm(0, 1, 0, 1));
+ dpp_ctrl = dpp_quad_perm(2, 3, 2, 3);
+ } else {
+ bld.vop1_dpp(aco_opcode::v_mov_b32, tl, get_alu_src(ctx, instr->src[0]), dpp_quad_perm(0, 0, 0, 0));
+ if (instr->op == nir_op_fddx || instr->op == nir_op_fddx_coarse)
+ dpp_ctrl = dpp_quad_perm(1, 1, 1, 1);
+ else
+ dpp_ctrl = dpp_quad_perm(2, 2, 2, 2);
+ }
+
+ Definition tmp = bld.def(v1);
+ bld.vop2_dpp(aco_opcode::v_sub_f32, tmp, get_alu_src(ctx, instr->src[0]), tl.getTemp(), dpp_ctrl);
+ emit_wqm(ctx, tmp.getTemp(), dst, true);
+ break;
+ }
+ default:
+ fprintf(stderr, "Unknown NIR ALU instr: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+}
+
+void visit_load_const(isel_context *ctx, nir_load_const_instr *instr)
+{
+ Temp dst = get_ssa_temp(ctx, &instr->def);
+
+ // TODO: we really want to have the resulting type as this would allow for 64bit literals
+ // which get truncated the lsb if double and msb if int
+ // for now, we only use s_mov_b64 with 64bit inline constants
+ assert(instr->def.num_components == 1 && "Vector load_const should be lowered to scalar.");
+ assert(dst.type() == RegType::sgpr);
+
+ if (dst.size() == 1)
+ {
+ Builder(ctx->program, ctx->block).copy(Definition(dst), Operand(instr->value[0].u32));
+ } else {
+ assert(dst.size() != 1);
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size(), 1)};
+ if (instr->def.bit_size == 64)
+ for (unsigned i = 0; i < dst.size(); i++)
+ vec->operands[i] = Operand{(uint32_t)(instr->value[0].u64 >> i * 32)};
+ else {
+ for (unsigned i = 0; i < dst.size(); i++)
+ vec->operands[i] = Operand{instr->value[i].u32};
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+}
+
+uint32_t widen_mask(uint32_t mask, unsigned multiplier)
+{
+ uint32_t new_mask = 0;
+ for(unsigned i = 0; i < 32 && (1u << i) <= mask; ++i)
+ if (mask & (1u << i))
+ new_mask |= ((1u << multiplier) - 1u) << (i * multiplier);
+ return new_mask;
+}
+
+void visit_store_vs_output(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ /* This wouldn't work inside control flow or with indirect offsets but
+ * that doesn't happen because of nir_lower_io_to_temporaries(). */
+
+ unsigned write_mask = nir_intrinsic_write_mask(instr);
+ unsigned component = nir_intrinsic_component(instr);
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ unsigned idx = nir_intrinsic_base(instr) + component;
+
+ nir_instr *off_instr = instr->src[1].ssa->parent_instr;
+ if (off_instr->type != nir_instr_type_load_const) {
+ fprintf(stderr, "Unimplemented nir_intrinsic_load_input offset\n");
+ nir_print_instr(off_instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ idx += nir_instr_as_load_const(off_instr)->value[0].u32 * 4u;
+
+ if (instr->src[0].ssa->bit_size == 64)
+ write_mask = widen_mask(write_mask, 2);
+
+ for (unsigned i = 0; i < 8; ++i) {
+ if (write_mask & (1 << i)) {
+ ctx->vs_output.mask[idx / 4u] |= 1 << (idx % 4u);
+ ctx->vs_output.outputs[idx / 4u][idx % 4u] = emit_extract_vector(ctx, src, i, v1);
+ }
+ idx++;
+ }
+}
+
+void visit_store_fs_output(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ unsigned write_mask = nir_intrinsic_write_mask(instr);
+ Operand values[4];
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ for (unsigned i = 0; i < 4; ++i) {
+ if (write_mask & (1 << i)) {
+ Temp tmp = emit_extract_vector(ctx, src, i, v1);
+ values[i] = Operand(tmp);
+ } else {
+ values[i] = Operand(v1);
+ }
+ }
+
+ unsigned index = nir_intrinsic_base(instr) / 4;
+ unsigned target, col_format;
+ unsigned enabled_channels = 0xF;
+ aco_opcode compr_op = (aco_opcode)0;
+
+ nir_const_value* offset = nir_src_as_const_value(instr->src[1]);
+ assert(offset && "Non-const offsets on exports not yet supported");
+ index += offset->u32;
+
+ assert(index != FRAG_RESULT_COLOR);
+
+ /* Unlike vertex shader exports, it's fine to use multiple exports to
+ * export separate channels of one target. So shaders which export both
+ * FRAG_RESULT_SAMPLE_MASK and FRAG_RESULT_DEPTH should work fine.
+ * TODO: combine the exports in those cases and create better code
+ */
+
+ if (index == FRAG_RESULT_SAMPLE_MASK) {
+
+ if (ctx->program->info->ps.writes_z) {
+ target = V_008DFC_SQ_EXP_MRTZ;
+ enabled_channels = 0x4;
+ col_format = (unsigned) -1;
+
+ values[2] = values[0];
+ values[0] = Operand(v1);
+ } else {
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ exp->valid_mask = false;
+ exp->done = false;
+ exp->compressed = true;
+ exp->dest = V_008DFC_SQ_EXP_MRTZ;
+ exp->enabled_mask = 0xc;
+ for (int i = 0; i < 4; i++)
+ exp->operands[i] = Operand(v1);
+ exp->operands[1] = Operand(values[0]);
+ ctx->block->instructions.emplace_back(std::move(exp));
+ return;
+ }
+
+ } else if (index == FRAG_RESULT_DEPTH) {
+
+ target = V_008DFC_SQ_EXP_MRTZ;
+ enabled_channels = 0x1;
+ col_format = (unsigned) -1;
+
+ } else if (index == FRAG_RESULT_STENCIL) {
+
+ if (ctx->program->info->ps.writes_z) {
+ target = V_008DFC_SQ_EXP_MRTZ;
+ enabled_channels = 0x2;
+ col_format = (unsigned) -1;
+
+ values[1] = values[0];
+ values[0] = Operand(v1);
+ } else {
+ aco_ptr<Instruction> shift{create_instruction<VOP2_instruction>(aco_opcode::v_lshlrev_b32, Format::VOP2, 2, 1)};
+ shift->operands[0] = Operand((uint32_t) 16);
+ shift->operands[1] = values[0];
+ Temp tmp = {ctx->program->allocateId(), v1};
+ shift->definitions[0] = Definition(tmp);
+ ctx->block->instructions.emplace_back(std::move(shift));
+
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ exp->valid_mask = false;
+ exp->done = false;
+ exp->compressed = true;
+ exp->dest = V_008DFC_SQ_EXP_MRTZ;
+ exp->enabled_mask = 0x3;
+ exp->operands[0] = Operand(tmp);
+ for (int i = 1; i < 4; i++)
+ exp->operands[i] = Operand(v1);
+ ctx->block->instructions.emplace_back(std::move(exp));
+ return;
+ }
+
+ } else {
+ index -= FRAG_RESULT_DATA0;
+ target = V_008DFC_SQ_EXP_MRT + index;
+ col_format = (ctx->options->key.fs.col_format >> (4 * index)) & 0xf;
+ }
+ ASSERTED bool is_int8 = (ctx->options->key.fs.is_int8 >> index) & 1;
+ ASSERTED bool is_int10 = (ctx->options->key.fs.is_int10 >> index) & 1;
+ assert(!is_int8 && !is_int10);
+
+ switch (col_format)
+ {
+ case V_028714_SPI_SHADER_ZERO:
+ enabled_channels = 0; /* writemask */
+ target = V_008DFC_SQ_EXP_NULL;
+ break;
+
+ case V_028714_SPI_SHADER_32_R:
+ enabled_channels = 1;
+ break;
+
+ case V_028714_SPI_SHADER_32_GR:
+ enabled_channels = 0x3;
+ break;
+
+ case V_028714_SPI_SHADER_32_AR:
+ enabled_channels = 0x9;
+ break;
+
+ case V_028714_SPI_SHADER_FP16_ABGR:
+ enabled_channels = 0x5;
+ compr_op = aco_opcode::v_cvt_pkrtz_f16_f32;
+ break;
+
+ case V_028714_SPI_SHADER_UNORM16_ABGR:
+ enabled_channels = 0x5;
+ compr_op = aco_opcode::v_cvt_pknorm_u16_f32;
+ break;
+
+ case V_028714_SPI_SHADER_SNORM16_ABGR:
+ enabled_channels = 0x5;
+ compr_op = aco_opcode::v_cvt_pknorm_i16_f32;
+ break;
+
+ case V_028714_SPI_SHADER_UINT16_ABGR:
+ enabled_channels = 0x5;
+ compr_op = aco_opcode::v_cvt_pk_u16_u32;
+ break;
+
+ case V_028714_SPI_SHADER_SINT16_ABGR:
+ enabled_channels = 0x5;
+ compr_op = aco_opcode::v_cvt_pk_i16_i32;
+ break;
+
+ case V_028714_SPI_SHADER_32_ABGR:
+ enabled_channels = 0xF;
+ break;
+
+ default:
+ break;
+ }
+
+ if (target == V_008DFC_SQ_EXP_NULL)
+ return;
+
+ if ((bool)compr_op)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ /* check if at least one of the values to be compressed is enabled */
+ unsigned enabled = (write_mask >> (i*2) | write_mask >> (i*2+1)) & 0x1;
+ if (enabled) {
+ enabled_channels |= enabled << (i*2);
+ aco_ptr<VOP3A_instruction> compr{create_instruction<VOP3A_instruction>(compr_op, Format::VOP3A, 2, 1)};
+ Temp tmp{ctx->program->allocateId(), v1};
+ compr->operands[0] = values[i*2].isUndefined() ? Operand(0u) : values[i*2];
+ compr->operands[1] = values[i*2+1].isUndefined() ? Operand(0u): values[i*2+1];
+ compr->definitions[0] = Definition(tmp);
+ values[i] = Operand(tmp);
+ ctx->block->instructions.emplace_back(std::move(compr));
+ } else {
+ values[i] = Operand(v1);
+ }
+ }
+ }
+
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ exp->valid_mask = false;
+ exp->done = false;
+ exp->compressed = (bool) compr_op;
+ exp->dest = target;
+ exp->enabled_mask = enabled_channels;
+ if ((bool) compr_op) {
+ for (int i = 0; i < 2; i++)
+ exp->operands[i] = enabled_channels & (3 << (i * 2)) ? values[i] : Operand(v1);
+ exp->operands[2] = Operand(v1);
+ exp->operands[3] = Operand(v1);
+ } else {
+ for (int i = 0; i < 4; i++)
+ exp->operands[i] = enabled_channels & (1 << i) ? values[i] : Operand(v1);
+ }
+
+ ctx->block->instructions.emplace_back(std::move(exp));
+}
+
+void visit_store_output(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ if (ctx->stage == vertex_vs) {
+ visit_store_vs_output(ctx, instr);
+ } else if (ctx->stage == fragment_fs) {
+ visit_store_fs_output(ctx, instr);
+ } else {
+ unreachable("Shader stage not implemented");
+ }
+}
+
+void emit_interp_instr(isel_context *ctx, unsigned idx, unsigned component, Temp src, Temp dst, Temp prim_mask)
+{
+ Temp coord1 = emit_extract_vector(ctx, src, 0, v1);
+ Temp coord2 = emit_extract_vector(ctx, src, 1, v1);
+
+ Builder bld(ctx->program, ctx->block);
+ Temp tmp = bld.vintrp(aco_opcode::v_interp_p1_f32, bld.def(v1), coord1, bld.m0(prim_mask), idx, component);
+ bld.vintrp(aco_opcode::v_interp_p2_f32, Definition(dst), coord2, bld.m0(prim_mask), tmp, idx, component);
+}
+
+void emit_load_frag_coord(isel_context *ctx, Temp dst, unsigned num_components)
+{
+ aco_ptr<Pseudo_instruction> vec(create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, num_components, 1));
+ for (unsigned i = 0; i < num_components; i++)
+ vec->operands[i] = Operand(ctx->fs_inputs[fs_input::frag_pos_0 + i]);
+
+ if (ctx->fs_vgpr_args[fs_input::frag_pos_3]) {
+ assert(num_components == 4);
+ Builder bld(ctx->program, ctx->block);
+ vec->operands[3] = bld.vop1(aco_opcode::v_rcp_f32, bld.def(v1), ctx->fs_inputs[fs_input::frag_pos_3]);
+ }
+
+ for (Operand& op : vec->operands)
+ op = op.isUndefined() ? Operand(0u) : op;
+
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ emit_split_vector(ctx, dst, num_components);
+ return;
+}
+
+void visit_load_interpolated_input(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp coords = get_ssa_temp(ctx, instr->src[0].ssa);
+ unsigned idx = nir_intrinsic_base(instr);
+ unsigned component = nir_intrinsic_component(instr);
+ Temp prim_mask = ctx->prim_mask;
+
+ nir_const_value* offset = nir_src_as_const_value(instr->src[1]);
+ if (offset) {
+ assert(offset->u32 == 0);
+ } else {
+ /* the lower 15bit of the prim_mask contain the offset into LDS
+ * while the upper bits contain the number of prims */
+ Temp offset_src = get_ssa_temp(ctx, instr->src[1].ssa);
+ assert(offset_src.regClass() == s1 && "TODO: divergent offsets...");
+ Builder bld(ctx->program, ctx->block);
+ Temp stride = bld.sop2(aco_opcode::s_lshr_b32, bld.def(s1), bld.def(s1, scc), prim_mask, Operand(16u));
+ stride = bld.sop1(aco_opcode::s_bcnt1_i32_b32, bld.def(s1), bld.def(s1, scc), stride);
+ stride = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), stride, Operand(48u));
+ offset_src = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), stride, offset_src);
+ prim_mask = bld.sop2(aco_opcode::s_add_i32, bld.def(s1, m0), bld.def(s1, scc), offset_src, prim_mask);
+ }
+
+ if (instr->dest.ssa.num_components == 1) {
+ emit_interp_instr(ctx, idx, component, coords, dst, prim_mask);
+ } else {
+ aco_ptr<Pseudo_instruction> vec(create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, instr->dest.ssa.num_components, 1));
+ for (unsigned i = 0; i < instr->dest.ssa.num_components; i++)
+ {
+ Temp tmp = {ctx->program->allocateId(), v1};
+ emit_interp_instr(ctx, idx, component+i, coords, tmp, prim_mask);
+ vec->operands[i] = Operand(tmp);
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+}
+
+unsigned get_num_channels_from_data_format(unsigned data_format)
+{
+ switch (data_format) {
+ case V_008F0C_BUF_DATA_FORMAT_8:
+ case V_008F0C_BUF_DATA_FORMAT_16:
+ case V_008F0C_BUF_DATA_FORMAT_32:
+ return 1;
+ case V_008F0C_BUF_DATA_FORMAT_8_8:
+ case V_008F0C_BUF_DATA_FORMAT_16_16:
+ case V_008F0C_BUF_DATA_FORMAT_32_32:
+ return 2;
+ case V_008F0C_BUF_DATA_FORMAT_10_11_11:
+ case V_008F0C_BUF_DATA_FORMAT_11_11_10:
+ case V_008F0C_BUF_DATA_FORMAT_32_32_32:
+ return 3;
+ case V_008F0C_BUF_DATA_FORMAT_8_8_8_8:
+ case V_008F0C_BUF_DATA_FORMAT_10_10_10_2:
+ case V_008F0C_BUF_DATA_FORMAT_2_10_10_10:
+ case V_008F0C_BUF_DATA_FORMAT_16_16_16_16:
+ case V_008F0C_BUF_DATA_FORMAT_32_32_32_32:
+ return 4;
+ default:
+ break;
+ }
+
+ return 4;
+}
+
+/* For 2_10_10_10 formats the alpha is handled as unsigned by pre-vega HW.
+ * so we may need to fix it up. */
+Temp adjust_vertex_fetch_alpha(isel_context *ctx, unsigned adjustment, Temp alpha)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ if (adjustment == RADV_ALPHA_ADJUST_SSCALED)
+ alpha = bld.vop1(aco_opcode::v_cvt_u32_f32, bld.def(v1), alpha);
+
+ /* For the integer-like cases, do a natural sign extension.
+ *
+ * For the SNORM case, the values are 0.0, 0.333, 0.666, 1.0
+ * and happen to contain 0, 1, 2, 3 as the two LSBs of the
+ * exponent.
+ */
+ alpha = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(adjustment == RADV_ALPHA_ADJUST_SNORM ? 7u : 30u), alpha);
+ alpha = bld.vop2(aco_opcode::v_ashrrev_i32, bld.def(v1), Operand(30u), alpha);
+
+ /* Convert back to the right type. */
+ if (adjustment == RADV_ALPHA_ADJUST_SNORM) {
+ alpha = bld.vop1(aco_opcode::v_cvt_f32_i32, bld.def(v1), alpha);
+ Temp clamp = bld.vopc(aco_opcode::v_cmp_le_f32, bld.hint_vcc(bld.def(s2)), Operand(0xbf800000u), alpha);
+ alpha = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0xbf800000u), alpha, clamp);
+ } else if (adjustment == RADV_ALPHA_ADJUST_SSCALED) {
+ alpha = bld.vop1(aco_opcode::v_cvt_f32_i32, bld.def(v1), alpha);
+ }
+
+ return alpha;
+}
+
+void visit_load_input(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (ctx->stage & sw_vs) {
+
+ nir_instr *off_instr = instr->src[0].ssa->parent_instr;
+ if (off_instr->type != nir_instr_type_load_const) {
+ fprintf(stderr, "Unimplemented nir_intrinsic_load_input offset\n");
+ nir_print_instr(off_instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ uint32_t offset = nir_instr_as_load_const(off_instr)->value[0].u32;
+
+ Temp vertex_buffers = convert_pointer_to_64_bit(ctx, ctx->vertex_buffers);
+
+ unsigned location = nir_intrinsic_base(instr) / 4 - VERT_ATTRIB_GENERIC0 + offset;
+ unsigned component = nir_intrinsic_component(instr);
+ unsigned attrib_binding = ctx->options->key.vs.vertex_attribute_bindings[location];
+ uint32_t attrib_offset = ctx->options->key.vs.vertex_attribute_offsets[location];
+ uint32_t attrib_stride = ctx->options->key.vs.vertex_attribute_strides[location];
+ unsigned attrib_format = ctx->options->key.vs.vertex_attribute_formats[location];
+
+ unsigned dfmt = attrib_format & 0xf;
+
+ unsigned nfmt = (attrib_format >> 4) & 0x7;
+ unsigned num_dfmt_channels = get_num_channels_from_data_format(dfmt);
+ unsigned mask = nir_ssa_def_components_read(&instr->dest.ssa) << component;
+ unsigned num_channels = MIN2(util_last_bit(mask), num_dfmt_channels);
+ unsigned alpha_adjust = (ctx->options->key.vs.alpha_adjust >> (location * 2)) & 3;
+ bool post_shuffle = ctx->options->key.vs.post_shuffle & (1 << location);
+ if (post_shuffle)
+ num_channels = MAX2(num_channels, 3);
+
+ Temp list = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), vertex_buffers, Operand(attrib_binding * 16u));
+
+ Temp index;
+ if (ctx->options->key.vs.instance_rate_inputs & (1u << location)) {
+ uint32_t divisor = ctx->options->key.vs.instance_rate_divisors[location];
+ if (divisor) {
+ ctx->needs_instance_id = true;
+
+ if (divisor != 1) {
+ Temp divided = bld.tmp(v1);
+ emit_v_div_u32(ctx, divided, as_vgpr(ctx, ctx->instance_id), divisor);
+ index = bld.vadd32(bld.def(v1), ctx->start_instance, divided);
+ } else {
+ index = bld.vadd32(bld.def(v1), ctx->start_instance, ctx->instance_id);
+ }
+ } else {
+ index = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), ctx->start_instance);
+ }
+ } else {
+ index = bld.vadd32(bld.def(v1), ctx->base_vertex, ctx->vertex_id);
+ }
+
+ if (attrib_stride != 0 && attrib_offset > attrib_stride) {
+ index = bld.vadd32(bld.def(v1), Operand(attrib_offset / attrib_stride), index);
+ attrib_offset = attrib_offset % attrib_stride;
+ }
+
+ Operand soffset(0u);
+ if (attrib_offset >= 4096) {
+ soffset = bld.copy(bld.def(s1), Operand(attrib_offset));
+ attrib_offset = 0;
+ }
+
+ aco_opcode opcode;
+ switch (num_channels) {
+ case 1:
+ opcode = aco_opcode::tbuffer_load_format_x;
+ break;
+ case 2:
+ opcode = aco_opcode::tbuffer_load_format_xy;
+ break;
+ case 3:
+ opcode = aco_opcode::tbuffer_load_format_xyz;
+ break;
+ case 4:
+ opcode = aco_opcode::tbuffer_load_format_xyzw;
+ break;
+ default:
+ unreachable("Unimplemented load_input vector size");
+ }
+
+ Temp tmp = post_shuffle || num_channels != dst.size() || alpha_adjust != RADV_ALPHA_ADJUST_NONE || component ? bld.tmp(RegType::vgpr, num_channels) : dst;
+
+ aco_ptr<MTBUF_instruction> mubuf{create_instruction<MTBUF_instruction>(opcode, Format::MTBUF, 3, 1)};
+ mubuf->operands[0] = Operand(index);
+ mubuf->operands[1] = Operand(list);
+ mubuf->operands[2] = soffset;
+ mubuf->definitions[0] = Definition(tmp);
+ mubuf->idxen = true;
+ mubuf->can_reorder = true;
+ mubuf->dfmt = dfmt;
+ mubuf->nfmt = nfmt;
+ assert(attrib_offset < 4096);
+ mubuf->offset = attrib_offset;
+ ctx->block->instructions.emplace_back(std::move(mubuf));
+
+ emit_split_vector(ctx, tmp, tmp.size());
+
+ if (tmp.id() != dst.id()) {
+ bool is_float = nfmt != V_008F0C_BUF_NUM_FORMAT_UINT &&
+ nfmt != V_008F0C_BUF_NUM_FORMAT_SINT;
+
+ static const unsigned swizzle_normal[4] = {0, 1, 2, 3};
+ static const unsigned swizzle_post_shuffle[4] = {2, 1, 0, 3};
+ const unsigned *swizzle = post_shuffle ? swizzle_post_shuffle : swizzle_normal;
+
+ aco_ptr<Instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size(), 1)};
+ for (unsigned i = 0; i < dst.size(); i++) {
+ unsigned idx = i + component;
+ if (idx == 3 && alpha_adjust != RADV_ALPHA_ADJUST_NONE && num_channels >= 4) {
+ Temp alpha = emit_extract_vector(ctx, tmp, swizzle[3], v1);
+ vec->operands[3] = Operand(adjust_vertex_fetch_alpha(ctx, alpha_adjust, alpha));
+ } else if (idx < num_channels) {
+ vec->operands[i] = Operand(emit_extract_vector(ctx, tmp, swizzle[idx], v1));
+ } else if (is_float && idx == 3) {
+ vec->operands[i] = Operand(0x3f800000u);
+ } else if (!is_float && idx == 3) {
+ vec->operands[i] = Operand(1u);
+ } else {
+ vec->operands[i] = Operand(0u);
+ }
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ emit_split_vector(ctx, dst, dst.size());
+ }
+
+ } else if (ctx->stage == fragment_fs) {
+ nir_instr *off_instr = instr->src[0].ssa->parent_instr;
+ if (off_instr->type != nir_instr_type_load_const ||
+ nir_instr_as_load_const(off_instr)->value[0].u32 != 0) {
+ fprintf(stderr, "Unimplemented nir_intrinsic_load_input offset\n");
+ nir_print_instr(off_instr, stderr);
+ fprintf(stderr, "\n");
+ }
+
+ Temp prim_mask = ctx->prim_mask;
+ nir_const_value* offset = nir_src_as_const_value(instr->src[0]);
+ if (offset) {
+ assert(offset->u32 == 0);
+ } else {
+ /* the lower 15bit of the prim_mask contain the offset into LDS
+ * while the upper bits contain the number of prims */
+ Temp offset_src = get_ssa_temp(ctx, instr->src[0].ssa);
+ assert(offset_src.regClass() == s1 && "TODO: divergent offsets...");
+ Builder bld(ctx->program, ctx->block);
+ Temp stride = bld.sop2(aco_opcode::s_lshr_b32, bld.def(s1), bld.def(s1, scc), prim_mask, Operand(16u));
+ stride = bld.sop1(aco_opcode::s_bcnt1_i32_b32, bld.def(s1), bld.def(s1, scc), stride);
+ stride = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), stride, Operand(48u));
+ offset_src = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), stride, offset_src);
+ prim_mask = bld.sop2(aco_opcode::s_add_i32, bld.def(s1, m0), bld.def(s1, scc), offset_src, prim_mask);
+ }
+
+ unsigned idx = nir_intrinsic_base(instr);
+ unsigned component = nir_intrinsic_component(instr);
+
+ if (dst.size() == 1) {
+ bld.vintrp(aco_opcode::v_interp_mov_f32, Definition(dst), Operand(2u), bld.m0(prim_mask), idx, component);
+ } else {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size(), 1)};
+ for (unsigned i = 0; i < dst.size(); i++)
+ vec->operands[i] = bld.vintrp(aco_opcode::v_interp_mov_f32, bld.def(v1), Operand(2u), bld.m0(prim_mask), idx, component + i);
+ vec->definitions[0] = Definition(dst);
+ bld.insert(std::move(vec));
+ }
+
+ } else {
+ unreachable("Shader stage not implemented");
+ }
+}
+
+Temp load_desc_ptr(isel_context *ctx, unsigned desc_set)
+{
+ if (ctx->program->info->need_indirect_descriptor_sets) {
+ Builder bld(ctx->program, ctx->block);
+ Temp ptr64 = convert_pointer_to_64_bit(ctx, ctx->descriptor_sets[0]);
+ return bld.smem(aco_opcode::s_load_dword, bld.def(s1), ptr64, Operand(desc_set << 2));//, false, false, false);
+ }
+
+ return ctx->descriptor_sets[desc_set];
+}
+
+
+void visit_load_resource(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp index = bld.as_uniform(get_ssa_temp(ctx, instr->src[0].ssa));
+ unsigned desc_set = nir_intrinsic_desc_set(instr);
+ unsigned binding = nir_intrinsic_binding(instr);
+
+ Temp desc_ptr;
+ radv_pipeline_layout *pipeline_layout = ctx->options->layout;
+ radv_descriptor_set_layout *layout = pipeline_layout->set[desc_set].layout;
+ unsigned offset = layout->binding[binding].offset;
+ unsigned stride;
+ if (layout->binding[binding].type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC ||
+ layout->binding[binding].type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC) {
+ unsigned idx = pipeline_layout->set[desc_set].dynamic_offset_start + layout->binding[binding].dynamic_offset_offset;
+ desc_ptr = ctx->push_constants;
+ offset = pipeline_layout->push_constant_size + 16 * idx;
+ stride = 16;
+ } else {
+ desc_ptr = load_desc_ptr(ctx, desc_set);
+ stride = layout->binding[binding].size;
+ }
+
+ nir_const_value* nir_const_index = nir_src_as_const_value(instr->src[0]);
+ unsigned const_index = nir_const_index ? nir_const_index->u32 : 0;
+ if (stride != 1) {
+ if (nir_const_index) {
+ const_index = const_index * stride;
+ } else {
+ index = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), Operand(stride), Operand(index));
+ }
+ }
+ if (offset) {
+ if (nir_const_index) {
+ const_index = const_index + offset;
+ } else {
+ index = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc), Operand(offset), Operand(index));
+ }
+ }
+
+ if (nir_const_index && const_index == 0) {
+ index = desc_ptr;
+ } else {
+ index = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc),
+ nir_const_index ? Operand(const_index) : Operand(index),
+ Operand(desc_ptr));
+ }
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), index);
+}
+
+void load_buffer(isel_context *ctx, unsigned num_components, Temp dst, Temp rsrc, Temp offset, bool glc=false)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ unsigned num_bytes = dst.size() * 4;
+
+ aco_opcode op;
+ if (dst.type() == RegType::vgpr || (glc && ctx->options->chip_class < GFX8)) {
+ if (ctx->options->chip_class < GFX8)
+ offset = as_vgpr(ctx, offset);
+
+ Operand vaddr = offset.type() == RegType::vgpr ? Operand(offset) : Operand(v1);
+ Operand soffset = offset.type() == RegType::sgpr ? Operand(offset) : Operand((uint32_t) 0);
+ unsigned const_offset = 0;
+
+ Temp lower = Temp();
+ if (num_bytes > 16) {
+ assert(num_components == 3 || num_components == 4);
+ op = aco_opcode::buffer_load_dwordx4;
+ lower = bld.tmp(v4);
+ aco_ptr<MUBUF_instruction> mubuf{create_instruction<MUBUF_instruction>(op, Format::MUBUF, 3, 1)};
+ mubuf->definitions[0] = Definition(lower);
+ mubuf->operands[0] = vaddr;
+ mubuf->operands[1] = Operand(rsrc);
+ mubuf->operands[2] = soffset;
+ mubuf->offen = (offset.type() == RegType::vgpr);
+ mubuf->glc = glc;
+ mubuf->barrier = barrier_buffer;
+ bld.insert(std::move(mubuf));
+ emit_split_vector(ctx, lower, 2);
+ num_bytes -= 16;
+ const_offset = 16;
+ }
+
+ switch (num_bytes) {
+ case 4:
+ op = aco_opcode::buffer_load_dword;
+ break;
+ case 8:
+ op = aco_opcode::buffer_load_dwordx2;
+ break;
+ case 12:
+ op = aco_opcode::buffer_load_dwordx3;
+ break;
+ case 16:
+ op = aco_opcode::buffer_load_dwordx4;
+ break;
+ default:
+ unreachable("Load SSBO not implemented for this size.");
+ }
+ aco_ptr<MUBUF_instruction> mubuf{create_instruction<MUBUF_instruction>(op, Format::MUBUF, 3, 1)};
+ mubuf->operands[0] = vaddr;
+ mubuf->operands[1] = Operand(rsrc);
+ mubuf->operands[2] = soffset;
+ mubuf->offen = (offset.type() == RegType::vgpr);
+ mubuf->glc = glc;
+ mubuf->barrier = barrier_buffer;
+ mubuf->offset = const_offset;
+ aco_ptr<Instruction> instr = std::move(mubuf);
+
+ if (dst.size() > 4) {
+ assert(lower != Temp());
+ Temp upper = bld.tmp(RegType::vgpr, dst.size() - lower.size());
+ instr->definitions[0] = Definition(upper);
+ bld.insert(std::move(instr));
+ if (dst.size() == 8)
+ emit_split_vector(ctx, upper, 2);
+ instr.reset(create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size() / 2, 1));
+ instr->operands[0] = Operand(emit_extract_vector(ctx, lower, 0, v2));
+ instr->operands[1] = Operand(emit_extract_vector(ctx, lower, 1, v2));
+ instr->operands[2] = Operand(emit_extract_vector(ctx, upper, 0, v2));
+ if (dst.size() == 8)
+ instr->operands[3] = Operand(emit_extract_vector(ctx, upper, 1, v2));
+ }
+
+ if (dst.type() == RegType::sgpr) {
+ Temp vec = bld.tmp(RegType::vgpr, dst.size());
+ instr->definitions[0] = Definition(vec);
+ bld.insert(std::move(instr));
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), vec);
+ } else {
+ instr->definitions[0] = Definition(dst);
+ bld.insert(std::move(instr));
+ }
+ } else {
+ switch (num_bytes) {
+ case 4:
+ op = aco_opcode::s_buffer_load_dword;
+ break;
+ case 8:
+ op = aco_opcode::s_buffer_load_dwordx2;
+ break;
+ case 12:
+ case 16:
+ op = aco_opcode::s_buffer_load_dwordx4;
+ break;
+ case 24:
+ case 32:
+ op = aco_opcode::s_buffer_load_dwordx8;
+ break;
+ default:
+ unreachable("Load SSBO not implemented for this size.");
+ }
+ aco_ptr<SMEM_instruction> load{create_instruction<SMEM_instruction>(op, Format::SMEM, 2, 1)};
+ load->operands[0] = Operand(rsrc);
+ load->operands[1] = Operand(bld.as_uniform(offset));
+ assert(load->operands[1].getTemp().type() == RegType::sgpr);
+ load->definitions[0] = Definition(dst);
+ load->glc = glc;
+ load->barrier = barrier_buffer;
+ assert(ctx->options->chip_class >= GFX8 || !glc);
+
+ /* trim vector */
+ if (dst.size() == 3) {
+ Temp vec = bld.tmp(s4);
+ load->definitions[0] = Definition(vec);
+ bld.insert(std::move(load));
+ emit_split_vector(ctx, vec, 4);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ emit_extract_vector(ctx, vec, 0, s1),
+ emit_extract_vector(ctx, vec, 1, s1),
+ emit_extract_vector(ctx, vec, 2, s1));
+ } else if (dst.size() == 6) {
+ Temp vec = bld.tmp(s8);
+ load->definitions[0] = Definition(vec);
+ bld.insert(std::move(load));
+ emit_split_vector(ctx, vec, 4);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ emit_extract_vector(ctx, vec, 0, s2),
+ emit_extract_vector(ctx, vec, 1, s2),
+ emit_extract_vector(ctx, vec, 2, s2));
+ } else {
+ bld.insert(std::move(load));
+ }
+
+ }
+ emit_split_vector(ctx, dst, num_components);
+}
+
+void visit_load_ubo(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp rsrc = get_ssa_temp(ctx, instr->src[0].ssa);
+
+ Builder bld(ctx->program, ctx->block);
+
+ nir_intrinsic_instr* idx_instr = nir_instr_as_intrinsic(instr->src[0].ssa->parent_instr);
+ unsigned desc_set = nir_intrinsic_desc_set(idx_instr);
+ unsigned binding = nir_intrinsic_binding(idx_instr);
+ radv_descriptor_set_layout *layout = ctx->options->layout->set[desc_set].layout;
+
+ if (layout->binding[binding].type == VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT) {
+ uint32_t desc_type = S_008F0C_DST_SEL_X(V_008F0C_SQ_SEL_X) |
+ S_008F0C_DST_SEL_Y(V_008F0C_SQ_SEL_Y) |
+ S_008F0C_DST_SEL_Z(V_008F0C_SQ_SEL_Z) |
+ S_008F0C_DST_SEL_W(V_008F0C_SQ_SEL_W) |
+ S_008F0C_NUM_FORMAT(V_008F0C_BUF_NUM_FORMAT_FLOAT) |
+ S_008F0C_DATA_FORMAT(V_008F0C_BUF_DATA_FORMAT_32);
+ Temp upper_dwords = bld.pseudo(aco_opcode::p_create_vector, bld.def(s3),
+ Operand(S_008F04_BASE_ADDRESS_HI(ctx->options->address32_hi)),
+ Operand(0xFFFFFFFFu),
+ Operand(desc_type));
+ rsrc = bld.pseudo(aco_opcode::p_create_vector, bld.def(s4),
+ rsrc, upper_dwords);
+ } else {
+ rsrc = convert_pointer_to_64_bit(ctx, rsrc);
+ rsrc = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), rsrc, Operand(0u));
+ }
+
+ load_buffer(ctx, instr->num_components, dst, rsrc, get_ssa_temp(ctx, instr->src[1].ssa));
+}
+
+void visit_load_push_constant(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ unsigned offset = nir_intrinsic_base(instr);
+ nir_const_value *index_cv = nir_src_as_const_value(instr->src[0]);
+ if (index_cv && instr->dest.ssa.bit_size == 32) {
+
+ unsigned count = instr->dest.ssa.num_components;
+ unsigned start = (offset + index_cv->u32) / 4u;
+ start -= ctx->base_inline_push_consts;
+ if (start + count <= ctx->num_inline_push_consts) {
+ std::array<Temp,NIR_MAX_VEC_COMPONENTS> elems;
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, count, 1)};
+ for (unsigned i = 0; i < count; ++i) {
+ elems[i] = ctx->inline_push_consts[start + i];
+ vec->operands[i] = Operand{elems[i]};
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), elems);
+ return;
+ }
+ }
+
+ Temp index = bld.as_uniform(get_ssa_temp(ctx, instr->src[0].ssa));
+ if (offset != 0) // TODO check if index != 0 as well
+ index = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc), Operand(offset), index);
+ Temp ptr = convert_pointer_to_64_bit(ctx, ctx->push_constants);
+ Temp vec = dst;
+ bool trim = false;
+ aco_opcode op;
+
+ switch (dst.size()) {
+ case 1:
+ op = aco_opcode::s_load_dword;
+ break;
+ case 2:
+ op = aco_opcode::s_load_dwordx2;
+ break;
+ case 3:
+ vec = bld.tmp(s4);
+ trim = true;
+ case 4:
+ op = aco_opcode::s_load_dwordx4;
+ break;
+ case 6:
+ vec = bld.tmp(s8);
+ trim = true;
+ case 8:
+ op = aco_opcode::s_load_dwordx8;
+ break;
+ default:
+ unreachable("unimplemented or forbidden load_push_constant.");
+ }
+
+ bld.smem(op, Definition(vec), ptr, index);
+
+ if (trim) {
+ emit_split_vector(ctx, vec, 4);
+ RegClass rc = dst.size() == 3 ? s1 : s2;
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ emit_extract_vector(ctx, vec, 0, rc),
+ emit_extract_vector(ctx, vec, 1, rc),
+ emit_extract_vector(ctx, vec, 2, rc));
+
+ }
+ emit_split_vector(ctx, dst, instr->dest.ssa.num_components);
+}
+
+void visit_load_constant(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ Builder bld(ctx->program, ctx->block);
+
+ uint32_t desc_type = S_008F0C_DST_SEL_X(V_008F0C_SQ_SEL_X) |
+ S_008F0C_DST_SEL_Y(V_008F0C_SQ_SEL_Y) |
+ S_008F0C_DST_SEL_Z(V_008F0C_SQ_SEL_Z) |
+ S_008F0C_DST_SEL_W(V_008F0C_SQ_SEL_W);
+ if (ctx->options->chip_class >= GFX10) {
+ desc_type |= S_008F0C_FORMAT(V_008F0C_IMG_FORMAT_32_FLOAT) |
+ S_008F0C_OOB_SELECT(3) |
+ S_008F0C_RESOURCE_LEVEL(1);
+ } else {
+ desc_type |= S_008F0C_NUM_FORMAT(V_008F0C_BUF_NUM_FORMAT_FLOAT) |
+ S_008F0C_DATA_FORMAT(V_008F0C_BUF_DATA_FORMAT_32);
+ }
+
+ unsigned base = nir_intrinsic_base(instr) + ctx->constant_data_offset;
+ unsigned range = nir_intrinsic_range(instr);
+
+ Temp offset = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (base && offset.type() == RegType::sgpr)
+ offset = bld.sop2(aco_opcode::s_add_u32, bld.def(s1), bld.def(s1, scc), offset, Operand(base));
+ else if (base && offset.type() == RegType::vgpr)
+ offset = bld.vadd32(bld.def(v1), Operand(base), offset);
+
+ Temp rsrc = bld.pseudo(aco_opcode::p_create_vector, bld.def(s4),
+ bld.sop1(aco_opcode::p_constaddr, bld.def(s2), bld.def(s1, scc), Operand(0u)),
+ Operand(MIN2(range, ctx->shader->constant_data_size - nir_intrinsic_base(instr))),
+ Operand(desc_type));
+
+ load_buffer(ctx, instr->num_components, dst, rsrc, offset);
+}
+
+void visit_discard_if(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ if (ctx->cf_info.loop_nest_depth || ctx->cf_info.parent_if.is_divergent)
+ ctx->cf_info.exec_potentially_empty = true;
+
+ ctx->program->needs_exact = true;
+
+ Builder bld(ctx->program, ctx->block);
+ Temp src = as_divergent_bool(ctx, get_ssa_temp(ctx, instr->src[0].ssa), false);
+ src = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2));
+ bld.pseudo(aco_opcode::p_discard_if, src);
+ ctx->block->kind |= block_kind_uses_discard_if;
+ return;
+}
+
+void visit_discard(isel_context* ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ if (ctx->cf_info.loop_nest_depth || ctx->cf_info.parent_if.is_divergent)
+ ctx->cf_info.exec_potentially_empty = true;
+
+ bool divergent = ctx->cf_info.parent_if.is_divergent ||
+ ctx->cf_info.parent_loop.has_divergent_continue;
+
+ if (ctx->block->loop_nest_depth &&
+ ((nir_instr_is_last(&instr->instr) && !divergent) || divergent)) {
+ /* we handle discards the same way as jump instructions */
+ append_logical_end(ctx->block);
+
+ /* in loops, discard behaves like break */
+ Block *linear_target = ctx->cf_info.parent_loop.exit;
+ ctx->block->kind |= block_kind_discard;
+
+ if (!divergent) {
+ /* uniform discard - loop ends here */
+ assert(nir_instr_is_last(&instr->instr));
+ ctx->block->kind |= block_kind_uniform;
+ ctx->cf_info.has_branch = true;
+ bld.branch(aco_opcode::p_branch);
+ add_linear_edge(ctx->block->index, linear_target);
+ return;
+ }
+
+ /* we add a break right behind the discard() instructions */
+ ctx->block->kind |= block_kind_break;
+ unsigned idx = ctx->block->index;
+
+ /* remove critical edges from linear CFG */
+ bld.branch(aco_opcode::p_branch);
+ Block* break_block = ctx->program->create_and_insert_block();
+ break_block->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ break_block->kind |= block_kind_uniform;
+ add_linear_edge(idx, break_block);
+ add_linear_edge(break_block->index, linear_target);
+ bld.reset(break_block);
+ bld.branch(aco_opcode::p_branch);
+
+ Block* continue_block = ctx->program->create_and_insert_block();
+ continue_block->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_linear_edge(idx, continue_block);
+ append_logical_start(continue_block);
+ ctx->block = continue_block;
+
+ return;
+ }
+
+ /* it can currently happen that NIR doesn't remove the unreachable code */
+ if (!nir_instr_is_last(&instr->instr)) {
+ ctx->program->needs_exact = true;
+ /* save exec somewhere temporarily so that it doesn't get
+ * overwritten before the discard from outer exec masks */
+ Temp cond = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), Operand(0xFFFFFFFF), Operand(exec, s2));
+ bld.pseudo(aco_opcode::p_discard_if, cond);
+ ctx->block->kind |= block_kind_uses_discard_if;
+ return;
+ }
+
+ /* This condition is incorrect for uniformly branched discards in a loop
+ * predicated by a divergent condition, but the above code catches that case
+ * and the discard would end up turning into a discard_if.
+ * For example:
+ * if (divergent) {
+ * while (...) {
+ * if (uniform) {
+ * discard;
+ * }
+ * }
+ * }
+ */
+ if (!ctx->cf_info.parent_if.is_divergent) {
+ /* program just ends here */
+ ctx->block->kind |= block_kind_uniform;
+ bld.exp(aco_opcode::exp, Operand(v1), Operand(v1), Operand(v1), Operand(v1),
+ 0 /* enabled mask */, 9 /* dest */,
+ false /* compressed */, true/* done */, true /* valid mask */);
+ bld.sopp(aco_opcode::s_endpgm);
+ // TODO: it will potentially be followed by a branch which is dead code to sanitize NIR phis
+ } else {
+ ctx->block->kind |= block_kind_discard;
+ /* branch and linear edge is added by visit_if() */
+ }
+}
+
+enum aco_descriptor_type {
+ ACO_DESC_IMAGE,
+ ACO_DESC_FMASK,
+ ACO_DESC_SAMPLER,
+ ACO_DESC_BUFFER,
+ ACO_DESC_PLANE_0,
+ ACO_DESC_PLANE_1,
+ ACO_DESC_PLANE_2,
+};
+
+enum aco_image_dim {
+ aco_image_1d,
+ aco_image_2d,
+ aco_image_3d,
+ aco_image_cube, // includes cube arrays
+ aco_image_1darray,
+ aco_image_2darray,
+ aco_image_2dmsaa,
+ aco_image_2darraymsaa,
+};
+
+static enum aco_image_dim
+get_sampler_dim(isel_context *ctx, enum glsl_sampler_dim dim, bool is_array)
+{
+ switch (dim) {
+ case GLSL_SAMPLER_DIM_1D:
+ if (ctx->options->chip_class >= GFX9)
+ return is_array ? aco_image_2darray : aco_image_2d;
+ return is_array ? aco_image_1darray : aco_image_1d;
+ case GLSL_SAMPLER_DIM_2D:
+ case GLSL_SAMPLER_DIM_RECT:
+ case GLSL_SAMPLER_DIM_EXTERNAL:
+ return is_array ? aco_image_2darray : aco_image_2d;
+ case GLSL_SAMPLER_DIM_3D:
+ return aco_image_3d;
+ case GLSL_SAMPLER_DIM_CUBE:
+ return aco_image_cube;
+ case GLSL_SAMPLER_DIM_MS:
+ return is_array ? aco_image_2darraymsaa : aco_image_2dmsaa;
+ case GLSL_SAMPLER_DIM_SUBPASS:
+ return aco_image_2darray;
+ case GLSL_SAMPLER_DIM_SUBPASS_MS:
+ return aco_image_2darraymsaa;
+ default:
+ unreachable("bad sampler dim");
+ }
+}
+
+static bool
+should_declare_array(isel_context *ctx, enum glsl_sampler_dim sampler_dim, bool is_array) {
+ if (sampler_dim == GLSL_SAMPLER_DIM_BUF)
+ return false;
+ aco_image_dim dim = get_sampler_dim(ctx, sampler_dim, is_array);
+ return dim == aco_image_cube ||
+ dim == aco_image_1darray ||
+ dim == aco_image_2darray ||
+ dim == aco_image_2darraymsaa;
+}
+
+Temp get_sampler_desc(isel_context *ctx, nir_deref_instr *deref_instr,
+ enum aco_descriptor_type desc_type,
+ const nir_tex_instr *tex_instr, bool image, bool write)
+{
+/* FIXME: we should lower the deref with some new nir_intrinsic_load_desc
+ std::unordered_map<uint64_t, Temp>::iterator it = ctx->tex_desc.find((uint64_t) desc_type << 32 | deref_instr->dest.ssa.index);
+ if (it != ctx->tex_desc.end())
+ return it->second;
+*/
+ Temp index = Temp();
+ bool index_set = false;
+ unsigned constant_index = 0;
+ unsigned descriptor_set;
+ unsigned base_index;
+ Builder bld(ctx->program, ctx->block);
+
+ if (!deref_instr) {
+ assert(tex_instr && !image);
+ descriptor_set = 0;
+ base_index = tex_instr->sampler_index;
+ } else {
+ while(deref_instr->deref_type != nir_deref_type_var) {
+ unsigned array_size = glsl_get_aoa_size(deref_instr->type);
+ if (!array_size)
+ array_size = 1;
+
+ assert(deref_instr->deref_type == nir_deref_type_array);
+ nir_const_value *const_value = nir_src_as_const_value(deref_instr->arr.index);
+ if (const_value) {
+ constant_index += array_size * const_value->u32;
+ } else {
+ Temp indirect = bld.as_uniform(get_ssa_temp(ctx, deref_instr->arr.index.ssa));
+
+ if (array_size != 1)
+ indirect = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), Operand(array_size), indirect);
+
+ if (!index_set) {
+ index = indirect;
+ index_set = true;
+ } else {
+ index = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc), index, indirect);
+ }
+ }
+
+ deref_instr = nir_src_as_deref(deref_instr->parent);
+ }
+ descriptor_set = deref_instr->var->data.descriptor_set;
+ base_index = deref_instr->var->data.binding;
+ }
+
+ Temp list = load_desc_ptr(ctx, descriptor_set);
+ list = convert_pointer_to_64_bit(ctx, list);
+
+ struct radv_descriptor_set_layout *layout = ctx->options->layout->set[descriptor_set].layout;
+ struct radv_descriptor_set_binding_layout *binding = layout->binding + base_index;
+ unsigned offset = binding->offset;
+ unsigned stride = binding->size;
+ aco_opcode opcode;
+ RegClass type;
+
+ assert(base_index < layout->binding_count);
+
+ switch (desc_type) {
+ case ACO_DESC_IMAGE:
+ type = s8;
+ opcode = aco_opcode::s_load_dwordx8;
+ break;
+ case ACO_DESC_FMASK:
+ type = s8;
+ opcode = aco_opcode::s_load_dwordx8;
+ offset += 32;
+ break;
+ case ACO_DESC_SAMPLER:
+ type = s4;
+ opcode = aco_opcode::s_load_dwordx4;
+ if (binding->type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
+ offset += radv_combined_image_descriptor_sampler_offset(binding);
+ break;
+ case ACO_DESC_BUFFER:
+ type = s4;
+ opcode = aco_opcode::s_load_dwordx4;
+ break;
+ case ACO_DESC_PLANE_0:
+ case ACO_DESC_PLANE_1:
+ type = s8;
+ opcode = aco_opcode::s_load_dwordx8;
+ offset += 32 * (desc_type - ACO_DESC_PLANE_0);
+ break;
+ case ACO_DESC_PLANE_2:
+ type = s4;
+ opcode = aco_opcode::s_load_dwordx4;
+ offset += 64;
+ break;
+ default:
+ unreachable("invalid desc_type\n");
+ }
+
+ offset += constant_index * stride;
+
+ if (desc_type == ACO_DESC_SAMPLER && binding->immutable_samplers_offset &&
+ (!index_set || binding->immutable_samplers_equal)) {
+ if (binding->immutable_samplers_equal)
+ constant_index = 0;
+
+ const uint32_t *samplers = radv_immutable_samplers(layout, binding);
+ return bld.pseudo(aco_opcode::p_create_vector, bld.def(s4),
+ Operand(samplers[constant_index * 4 + 0]),
+ Operand(samplers[constant_index * 4 + 1]),
+ Operand(samplers[constant_index * 4 + 2]),
+ Operand(samplers[constant_index * 4 + 3]));
+ }
+
+ Operand off;
+ if (!index_set) {
+ off = Operand(offset);
+ } else {
+ off = Operand((Temp)bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc), Operand(offset),
+ bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), Operand(stride), index)));
+ }
+
+ Temp res = bld.smem(opcode, bld.def(type), list, off);
+
+ if (desc_type == ACO_DESC_PLANE_2) {
+ Temp components[8];
+ for (unsigned i = 0; i < 8; i++)
+ components[i] = bld.tmp(s1);
+ bld.pseudo(aco_opcode::p_split_vector,
+ Definition(components[0]),
+ Definition(components[1]),
+ Definition(components[2]),
+ Definition(components[3]),
+ res);
+
+ Temp desc2 = get_sampler_desc(ctx, deref_instr, ACO_DESC_PLANE_1, tex_instr, image, write);
+ bld.pseudo(aco_opcode::p_split_vector,
+ bld.def(s1), bld.def(s1), bld.def(s1), bld.def(s1),
+ Definition(components[4]),
+ Definition(components[5]),
+ Definition(components[6]),
+ Definition(components[7]),
+ desc2);
+
+ res = bld.pseudo(aco_opcode::p_create_vector, bld.def(s8),
+ components[0], components[1], components[2], components[3],
+ components[4], components[5], components[6], components[7]);
+ }
+
+ return res;
+}
+
+static int image_type_to_components_count(enum glsl_sampler_dim dim, bool array)
+{
+ switch (dim) {
+ case GLSL_SAMPLER_DIM_BUF:
+ return 1;
+ case GLSL_SAMPLER_DIM_1D:
+ return array ? 2 : 1;
+ case GLSL_SAMPLER_DIM_2D:
+ return array ? 3 : 2;
+ case GLSL_SAMPLER_DIM_MS:
+ return array ? 4 : 3;
+ case GLSL_SAMPLER_DIM_3D:
+ case GLSL_SAMPLER_DIM_CUBE:
+ return 3;
+ case GLSL_SAMPLER_DIM_RECT:
+ case GLSL_SAMPLER_DIM_SUBPASS:
+ return 2;
+ case GLSL_SAMPLER_DIM_SUBPASS_MS:
+ return 3;
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+/* Adjust the sample index according to FMASK.
+ *
+ * For uncompressed MSAA surfaces, FMASK should return 0x76543210,
+ * which is the identity mapping. Each nibble says which physical sample
+ * should be fetched to get that sample.
+ *
+ * For example, 0x11111100 means there are only 2 samples stored and
+ * the second sample covers 3/4 of the pixel. When reading samples 0
+ * and 1, return physical sample 0 (determined by the first two 0s
+ * in FMASK), otherwise return physical sample 1.
+ *
+ * The sample index should be adjusted as follows:
+ * sample_index = (fmask >> (sample_index * 4)) & 0xF;
+ */
+static Temp adjust_sample_index_using_fmask(isel_context *ctx, bool da, Temp coords, Operand sample_index, Temp fmask_desc_ptr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp fmask = bld.tmp(v1);
+
+ aco_ptr<MIMG_instruction> load{create_instruction<MIMG_instruction>(aco_opcode::image_load, Format::MIMG, 2, 1)};
+ load->operands[0] = Operand(coords);
+ load->operands[1] = Operand(fmask_desc_ptr);
+ load->definitions[0] = Definition(fmask);
+ load->glc = false;
+ load->dmask = 0x1;
+ load->unrm = true;
+ load->da = da;
+ load->can_reorder = true; /* fmask images shouldn't be modified */
+ ctx->block->instructions.emplace_back(std::move(load));
+
+ Operand sample_index4;
+ if (sample_index.isConstant() && sample_index.constantValue() < 16) {
+ sample_index4 = Operand(sample_index.constantValue() << 2);
+ } else if (sample_index.regClass() == s1) {
+ sample_index4 = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), sample_index, Operand(2u));
+ } else {
+ assert(sample_index.regClass() == v1);
+ sample_index4 = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(2u), sample_index);
+ }
+
+ Temp final_sample;
+ if (sample_index4.isConstant() && sample_index4.constantValue() == 0)
+ final_sample = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(15u), fmask);
+ else if (sample_index4.isConstant() && sample_index4.constantValue() == 28)
+ final_sample = bld.vop2(aco_opcode::v_lshrrev_b32, bld.def(v1), Operand(28u), fmask);
+ else
+ final_sample = bld.vop3(aco_opcode::v_bfe_u32, bld.def(v1), fmask, sample_index4, Operand(4u));
+
+ /* Don't rewrite the sample index if WORD1.DATA_FORMAT of the FMASK
+ * resource descriptor is 0 (invalid),
+ */
+ Temp compare = bld.tmp(s2);
+ bld.vopc_e64(aco_opcode::v_cmp_lg_u32, Definition(compare),
+ Operand(0u), emit_extract_vector(ctx, fmask_desc_ptr, 1, s1)).def(0).setHint(vcc);
+
+ Temp sample_index_v = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), sample_index);
+
+ /* Replace the MSAA sample index. */
+ return bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), sample_index_v, final_sample, compare);
+}
+
+static Temp get_image_coords(isel_context *ctx, const nir_intrinsic_instr *instr, const struct glsl_type *type)
+{
+
+ Temp src0 = get_ssa_temp(ctx, instr->src[1].ssa);
+ enum glsl_sampler_dim dim = glsl_get_sampler_dim(type);
+ bool is_array = glsl_sampler_type_is_array(type);
+ ASSERTED bool add_frag_pos = (dim == GLSL_SAMPLER_DIM_SUBPASS || dim == GLSL_SAMPLER_DIM_SUBPASS_MS);
+ assert(!add_frag_pos && "Input attachments should be lowered.");
+ bool is_ms = (dim == GLSL_SAMPLER_DIM_MS || dim == GLSL_SAMPLER_DIM_SUBPASS_MS);
+ bool gfx9_1d = ctx->options->chip_class >= GFX9 && dim == GLSL_SAMPLER_DIM_1D;
+ int count = image_type_to_components_count(dim, is_array);
+ std::vector<Operand> coords(count);
+
+ if (is_ms) {
+ Operand sample_index;
+ nir_const_value *sample_cv = nir_src_as_const_value(instr->src[2]);
+ if (sample_cv)
+ sample_index = Operand(sample_cv->u32);
+ else
+ sample_index = Operand(emit_extract_vector(ctx, get_ssa_temp(ctx, instr->src[2].ssa), 0, v1));
+
+ if (instr->intrinsic == nir_intrinsic_image_deref_load) {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, is_array ? 3 : 2, 1)};
+ for (unsigned i = 0; i < vec->operands.size(); i++)
+ vec->operands[i] = Operand(emit_extract_vector(ctx, src0, i, v1));
+ Temp fmask_load_address = {ctx->program->allocateId(), is_array ? v3 : v2};
+ vec->definitions[0] = Definition(fmask_load_address);
+ ctx->block->instructions.emplace_back(std::move(vec));
+
+ Temp fmask_desc_ptr = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_FMASK, nullptr, false, false);
+ sample_index = Operand(adjust_sample_index_using_fmask(ctx, is_array, fmask_load_address, sample_index, fmask_desc_ptr));
+ }
+ count--;
+ coords[count] = sample_index;
+ }
+
+ if (count == 1 && !gfx9_1d)
+ return emit_extract_vector(ctx, src0, 0, v1);
+
+ if (gfx9_1d) {
+ coords[0] = Operand(emit_extract_vector(ctx, src0, 0, v1));
+ coords.resize(coords.size() + 1);
+ coords[1] = Operand((uint32_t) 0);
+ if (is_array)
+ coords[2] = Operand(emit_extract_vector(ctx, src0, 1, v1));
+ } else {
+ for (int i = 0; i < count; i++)
+ coords[i] = Operand(emit_extract_vector(ctx, src0, i, v1));
+ }
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, coords.size(), 1)};
+ for (unsigned i = 0; i < coords.size(); i++)
+ vec->operands[i] = coords[i];
+ Temp res = {ctx->program->allocateId(), RegClass(RegType::vgpr, coords.size())};
+ vec->definitions[0] = Definition(res);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ return res;
+}
+
+
+void visit_image_load(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ const nir_variable *var = nir_deref_instr_get_variable(nir_instr_as_deref(instr->src[0].ssa->parent_instr));
+ const struct glsl_type *type = glsl_without_array(var->type);
+ const enum glsl_sampler_dim dim = glsl_get_sampler_dim(type);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ if (dim == GLSL_SAMPLER_DIM_BUF) {
+ unsigned mask = nir_ssa_def_components_read(&instr->dest.ssa);
+ unsigned num_channels = util_last_bit(mask);
+ Temp rsrc = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_BUFFER, nullptr, true, true);
+ Temp vindex = emit_extract_vector(ctx, get_ssa_temp(ctx, instr->src[1].ssa), 0, v1);
+
+ aco_opcode opcode;
+ switch (num_channels) {
+ case 1:
+ opcode = aco_opcode::buffer_load_format_x;
+ break;
+ case 2:
+ opcode = aco_opcode::buffer_load_format_xy;
+ break;
+ case 3:
+ opcode = aco_opcode::buffer_load_format_xyz;
+ break;
+ case 4:
+ opcode = aco_opcode::buffer_load_format_xyzw;
+ break;
+ default:
+ unreachable(">4 channel buffer image load");
+ }
+ aco_ptr<MUBUF_instruction> load{create_instruction<MUBUF_instruction>(opcode, Format::MUBUF, 3, 1)};
+ load->operands[0] = Operand(vindex);
+ load->operands[1] = Operand(rsrc);
+ load->operands[2] = Operand((uint32_t) 0);
+ Temp tmp;
+ if (num_channels == instr->dest.ssa.num_components && dst.type() == RegType::vgpr)
+ tmp = dst;
+ else
+ tmp = {ctx->program->allocateId(), RegClass(RegType::vgpr, num_channels)};
+ load->definitions[0] = Definition(tmp);
+ load->idxen = true;
+ load->barrier = barrier_image;
+ ctx->block->instructions.emplace_back(std::move(load));
+
+ expand_vector(ctx, tmp, dst, instr->dest.ssa.num_components, (1 << num_channels) - 1);
+ return;
+ }
+
+ Temp coords = get_image_coords(ctx, instr, type);
+ Temp resource = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_IMAGE, nullptr, true, true);
+ //aco_image_dim img_dim = get_image_dim(ctx, glsl_get_sampler_dim(type), glsl_sampler_type_is_array(type));
+
+ unsigned dmask = nir_ssa_def_components_read(&instr->dest.ssa);
+ unsigned num_components = util_bitcount(dmask);
+ Temp tmp;
+ if (num_components == instr->dest.ssa.num_components && dst.type() == RegType::vgpr)
+ tmp = dst;
+ else
+ tmp = {ctx->program->allocateId(), RegClass(RegType::vgpr, num_components)};
+
+ aco_ptr<MIMG_instruction> load{create_instruction<MIMG_instruction>(aco_opcode::image_load, Format::MIMG, 2, 1)};
+ load->operands[0] = Operand(coords);
+ load->operands[1] = Operand(resource);
+ load->definitions[0] = Definition(tmp);
+ load->glc = var->data.image.access & (ACCESS_VOLATILE | ACCESS_COHERENT) ? 1 : 0;
+ load->dmask = dmask;
+ load->unrm = true;
+ load->da = should_declare_array(ctx, dim, glsl_sampler_type_is_array(type));
+ load->barrier = barrier_image;
+ ctx->block->instructions.emplace_back(std::move(load));
+
+ expand_vector(ctx, tmp, dst, instr->dest.ssa.num_components, dmask);
+ return;
+}
+
+void visit_image_store(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ const nir_variable *var = nir_deref_instr_get_variable(nir_instr_as_deref(instr->src[0].ssa->parent_instr));
+ const struct glsl_type *type = glsl_without_array(var->type);
+ const enum glsl_sampler_dim dim = glsl_get_sampler_dim(type);
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[3].ssa));
+
+ bool glc = ctx->options->chip_class == GFX6 || var->data.image.access & (ACCESS_VOLATILE | ACCESS_COHERENT | ACCESS_NON_READABLE) ? 1 : 0;
+
+ if (dim == GLSL_SAMPLER_DIM_BUF) {
+ Temp rsrc = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_BUFFER, nullptr, true, true);
+ Temp vindex = emit_extract_vector(ctx, get_ssa_temp(ctx, instr->src[1].ssa), 0, v1);
+ aco_opcode opcode;
+ switch (data.size()) {
+ case 1:
+ opcode = aco_opcode::buffer_store_format_x;
+ break;
+ case 2:
+ opcode = aco_opcode::buffer_store_format_xy;
+ break;
+ case 3:
+ opcode = aco_opcode::buffer_store_format_xyz;
+ break;
+ case 4:
+ opcode = aco_opcode::buffer_store_format_xyzw;
+ break;
+ default:
+ unreachable(">4 channel buffer image store");
+ }
+ aco_ptr<MUBUF_instruction> store{create_instruction<MUBUF_instruction>(opcode, Format::MUBUF, 4, 0)};
+ store->operands[0] = Operand(vindex);
+ store->operands[1] = Operand(rsrc);
+ store->operands[2] = Operand((uint32_t) 0);
+ store->operands[3] = Operand(data);
+ store->idxen = true;
+ store->glc = glc;
+ store->disable_wqm = true;
+ store->barrier = barrier_image;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(store));
+ return;
+ }
+
+ assert(data.type() == RegType::vgpr);
+ Temp coords = get_image_coords(ctx, instr, type);
+ Temp resource = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_IMAGE, nullptr, true, true);
+
+ aco_ptr<MIMG_instruction> store{create_instruction<MIMG_instruction>(aco_opcode::image_store, Format::MIMG, 4, 0)};
+ store->operands[0] = Operand(coords);
+ store->operands[1] = Operand(resource);
+ store->operands[2] = Operand(s4);
+ store->operands[3] = Operand(data);
+ store->glc = glc;
+ store->dmask = (1 << data.size()) - 1;
+ store->unrm = true;
+ store->da = should_declare_array(ctx, dim, glsl_sampler_type_is_array(type));
+ store->disable_wqm = true;
+ store->barrier = barrier_image;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(store));
+ return;
+}
+
+void visit_image_atomic(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ /* return the previous value if dest is ever used */
+ bool return_previous = false;
+ nir_foreach_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+ nir_foreach_if_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+
+ const nir_variable *var = nir_deref_instr_get_variable(nir_instr_as_deref(instr->src[0].ssa->parent_instr));
+ const struct glsl_type *type = glsl_without_array(var->type);
+ const enum glsl_sampler_dim dim = glsl_get_sampler_dim(type);
+ Builder bld(ctx->program, ctx->block);
+
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[3].ssa));
+ assert(data.size() == 1 && "64bit ssbo atomics not yet implemented.");
+
+ if (instr->intrinsic == nir_intrinsic_image_deref_atomic_comp_swap)
+ data = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), get_ssa_temp(ctx, instr->src[4].ssa), data);
+
+ aco_opcode buf_op, image_op;
+ switch (instr->intrinsic) {
+ case nir_intrinsic_image_deref_atomic_add:
+ buf_op = aco_opcode::buffer_atomic_add;
+ image_op = aco_opcode::image_atomic_add;
+ break;
+ case nir_intrinsic_image_deref_atomic_umin:
+ buf_op = aco_opcode::buffer_atomic_umin;
+ image_op = aco_opcode::image_atomic_umin;
+ break;
+ case nir_intrinsic_image_deref_atomic_imin:
+ buf_op = aco_opcode::buffer_atomic_smin;
+ image_op = aco_opcode::image_atomic_smin;
+ break;
+ case nir_intrinsic_image_deref_atomic_umax:
+ buf_op = aco_opcode::buffer_atomic_umax;
+ image_op = aco_opcode::image_atomic_umax;
+ break;
+ case nir_intrinsic_image_deref_atomic_imax:
+ buf_op = aco_opcode::buffer_atomic_smax;
+ image_op = aco_opcode::image_atomic_smax;
+ break;
+ case nir_intrinsic_image_deref_atomic_and:
+ buf_op = aco_opcode::buffer_atomic_and;
+ image_op = aco_opcode::image_atomic_and;
+ break;
+ case nir_intrinsic_image_deref_atomic_or:
+ buf_op = aco_opcode::buffer_atomic_or;
+ image_op = aco_opcode::image_atomic_or;
+ break;
+ case nir_intrinsic_image_deref_atomic_xor:
+ buf_op = aco_opcode::buffer_atomic_xor;
+ image_op = aco_opcode::image_atomic_xor;
+ break;
+ case nir_intrinsic_image_deref_atomic_exchange:
+ buf_op = aco_opcode::buffer_atomic_swap;
+ image_op = aco_opcode::image_atomic_swap;
+ break;
+ case nir_intrinsic_image_deref_atomic_comp_swap:
+ buf_op = aco_opcode::buffer_atomic_cmpswap;
+ image_op = aco_opcode::image_atomic_cmpswap;
+ break;
+ default:
+ unreachable("visit_image_atomic should only be called with nir_intrinsic_image_deref_atomic_* instructions.");
+ }
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ if (dim == GLSL_SAMPLER_DIM_BUF) {
+ Temp vindex = emit_extract_vector(ctx, get_ssa_temp(ctx, instr->src[1].ssa), 0, v1);
+ Temp resource = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_BUFFER, nullptr, true, true);
+ //assert(ctx->options->chip_class < GFX9 && "GFX9 stride size workaround not yet implemented.");
+ aco_ptr<MUBUF_instruction> mubuf{create_instruction<MUBUF_instruction>(buf_op, Format::MUBUF, 4, return_previous ? 1 : 0)};
+ mubuf->operands[0] = Operand(vindex);
+ mubuf->operands[1] = Operand(resource);
+ mubuf->operands[2] = Operand((uint32_t)0);
+ mubuf->operands[3] = Operand(data);
+ if (return_previous)
+ mubuf->definitions[0] = Definition(dst);
+ mubuf->offset = 0;
+ mubuf->idxen = true;
+ mubuf->glc = return_previous;
+ mubuf->disable_wqm = true;
+ mubuf->barrier = barrier_image;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(mubuf));
+ return;
+ }
+
+ Temp coords = get_image_coords(ctx, instr, type);
+ Temp resource = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_IMAGE, nullptr, true, true);
+ aco_ptr<MIMG_instruction> mimg{create_instruction<MIMG_instruction>(image_op, Format::MIMG, 4, return_previous ? 1 : 0)};
+ mimg->operands[0] = Operand(coords);
+ mimg->operands[1] = Operand(resource);
+ mimg->operands[2] = Operand(s4); /* no sampler */
+ mimg->operands[3] = Operand(data);
+ if (return_previous)
+ mimg->definitions[0] = Definition(dst);
+ mimg->glc = return_previous;
+ mimg->dmask = (1 << data.size()) - 1;
+ mimg->unrm = true;
+ mimg->da = should_declare_array(ctx, dim, glsl_sampler_type_is_array(type));
+ mimg->disable_wqm = true;
+ mimg->barrier = barrier_image;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(mimg));
+ return;
+}
+
+void get_buffer_size(isel_context *ctx, Temp desc, Temp dst, bool in_elements)
+{
+ if (in_elements && ctx->options->chip_class == GFX8) {
+ Builder bld(ctx->program, ctx->block);
+
+ Temp stride = emit_extract_vector(ctx, desc, 1, s1);
+ stride = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), stride, Operand((5u << 16) | 16u));
+ stride = bld.vop1(aco_opcode::v_cvt_f32_ubyte0, bld.def(v1), stride);
+ stride = bld.vop1(aco_opcode::v_rcp_iflag_f32, bld.def(v1), stride);
+
+ Temp size = emit_extract_vector(ctx, desc, 2, s1);
+ size = bld.vop1(aco_opcode::v_cvt_f32_u32, bld.def(v1), size);
+
+ Temp res = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), size, stride);
+ res = bld.vop1(aco_opcode::v_cvt_u32_f32, bld.def(v1), res);
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), res);
+
+ // TODO: we can probably calculate this faster on the scalar unit to do: size / stride{1,2,4,8,12,16}
+ /* idea
+ * for 1,2,4,8,16, the result is just (stride >> S_FF1_I32_B32)
+ * in case 12 (or 3?), we have to divide by 3:
+ * set v_skip in case it's 12 (if we also have to take care of 3, shift first)
+ * use v_mul_hi_u32 with magic number to divide
+ * we need some pseudo merge opcode to overwrite the original SALU result with readfirstlane
+ * disable v_skip
+ * total: 6 SALU + 2 VALU instructions vs 1 SALU + 6 VALU instructions
+ */
+
+ } else {
+ emit_extract_vector(ctx, desc, 2, dst);
+ }
+}
+
+void visit_image_size(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ const nir_variable *var = nir_deref_instr_get_variable(nir_instr_as_deref(instr->src[0].ssa->parent_instr));
+ const struct glsl_type *type = glsl_without_array(var->type);
+ Builder bld(ctx->program, ctx->block);
+
+ if (glsl_get_sampler_dim(type) == GLSL_SAMPLER_DIM_BUF) {
+ Temp desc = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_BUFFER, NULL, true, false);
+ return get_buffer_size(ctx, desc, get_ssa_temp(ctx, &instr->dest.ssa), true);
+ }
+
+ /* LOD */
+ Temp lod = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(0u));
+
+ /* Resource */
+ Temp resource = get_sampler_desc(ctx, nir_instr_as_deref(instr->src[0].ssa->parent_instr), ACO_DESC_IMAGE, NULL, true, false);
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ aco_ptr<MIMG_instruction> mimg{create_instruction<MIMG_instruction>(aco_opcode::image_get_resinfo, Format::MIMG, 2, 1)};
+ mimg->operands[0] = Operand(lod);
+ mimg->operands[1] = Operand(resource);
+ unsigned& dmask = mimg->dmask;
+ mimg->dmask = (1 << instr->dest.ssa.num_components) - 1;
+ mimg->da = glsl_sampler_type_is_array(type);
+ mimg->can_reorder = true;
+ Definition& def = mimg->definitions[0];
+ ctx->block->instructions.emplace_back(std::move(mimg));
+
+ if (glsl_get_sampler_dim(type) == GLSL_SAMPLER_DIM_CUBE &&
+ glsl_sampler_type_is_array(type)) {
+
+ assert(instr->dest.ssa.num_components == 3);
+ Temp tmp = {ctx->program->allocateId(), v3};
+ def = Definition(tmp);
+ emit_split_vector(ctx, tmp, 3);
+
+ /* divide 3rd value by 6 by multiplying with magic number */
+ Temp c = bld.copy(bld.def(s1), Operand((uint32_t) 0x2AAAAAAB));
+ Temp by_6 = bld.vop3(aco_opcode::v_mul_hi_i32, bld.def(v1), emit_extract_vector(ctx, tmp, 2, v1), c);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ emit_extract_vector(ctx, tmp, 0, v1),
+ emit_extract_vector(ctx, tmp, 1, v1),
+ by_6);
+
+ } else if (ctx->options->chip_class >= GFX9 &&
+ glsl_get_sampler_dim(type) == GLSL_SAMPLER_DIM_1D &&
+ glsl_sampler_type_is_array(type)) {
+ assert(instr->dest.ssa.num_components == 2);
+ def = Definition(dst);
+ dmask = 0x5;
+ } else {
+ def = Definition(dst);
+ }
+
+ emit_split_vector(ctx, dst, instr->dest.ssa.num_components);
+}
+
+void visit_load_ssbo(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ unsigned num_components = instr->num_components;
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp rsrc = convert_pointer_to_64_bit(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ rsrc = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), rsrc, Operand(0u));
+
+ bool glc = nir_intrinsic_access(instr) & (ACCESS_VOLATILE | ACCESS_COHERENT);
+ load_buffer(ctx, num_components, dst, rsrc, get_ssa_temp(ctx, instr->src[1].ssa), glc);
+}
+
+void visit_store_ssbo(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp data = get_ssa_temp(ctx, instr->src[0].ssa);
+ unsigned elem_size_bytes = instr->src[0].ssa->bit_size / 8;
+ unsigned writemask = nir_intrinsic_write_mask(instr);
+
+ Temp offset;
+ if (ctx->options->chip_class < GFX8)
+ offset = as_vgpr(ctx,get_ssa_temp(ctx, instr->src[2].ssa));
+ else
+ offset = get_ssa_temp(ctx, instr->src[2].ssa);
+
+ Temp rsrc = convert_pointer_to_64_bit(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+ rsrc = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), rsrc, Operand(0u));
+
+ bool smem = !ctx->divergent_vals[instr->src[2].ssa->index] &&
+ ctx->options->chip_class >= GFX8;
+ if (smem)
+ offset = bld.as_uniform(offset);
+ bool smem_nonfs = smem && ctx->stage != fragment_fs;
+
+ while (writemask) {
+ int start, count;
+ u_bit_scan_consecutive_range(&writemask, &start, &count);
+ if (count == 3 && smem) {
+ writemask |= 1u << (start + 2);
+ count = 2;
+ }
+ int num_bytes = count * elem_size_bytes;
+
+ if (num_bytes > 16) {
+ assert(elem_size_bytes == 8);
+ writemask |= (((count - 2) << 1) - 1) << (start + 2);
+ count = 2;
+ num_bytes = 16;
+ }
+
+ // TODO: check alignment of sub-dword stores
+ // TODO: split 3 bytes. there is no store instruction for that
+
+ Temp write_data;
+ if (count != instr->num_components) {
+ emit_split_vector(ctx, data, instr->num_components);
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, count, 1)};
+ for (int i = 0; i < count; i++) {
+ Temp elem = emit_extract_vector(ctx, data, start + i, RegClass(data.type(), elem_size_bytes / 4));
+ vec->operands[i] = Operand(smem_nonfs ? bld.as_uniform(elem) : elem);
+ }
+ write_data = bld.tmp(smem_nonfs ? RegType::sgpr : data.type(), count * elem_size_bytes / 4);
+ vec->definitions[0] = Definition(write_data);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ } else if (!smem && data.type() != RegType::vgpr) {
+ assert(num_bytes % 4 == 0);
+ write_data = bld.copy(bld.def(RegType::vgpr, num_bytes / 4), data);
+ } else if (smem_nonfs && data.type() == RegType::vgpr) {
+ assert(num_bytes % 4 == 0);
+ write_data = bld.as_uniform(data);
+ } else {
+ write_data = data;
+ }
+
+ aco_opcode vmem_op, smem_op;
+ switch (num_bytes) {
+ case 4:
+ vmem_op = aco_opcode::buffer_store_dword;
+ smem_op = aco_opcode::s_buffer_store_dword;
+ break;
+ case 8:
+ vmem_op = aco_opcode::buffer_store_dwordx2;
+ smem_op = aco_opcode::s_buffer_store_dwordx2;
+ break;
+ case 12:
+ vmem_op = aco_opcode::buffer_store_dwordx3;
+ smem_op = aco_opcode::last_opcode;
+ assert(!smem);
+ break;
+ case 16:
+ vmem_op = aco_opcode::buffer_store_dwordx4;
+ smem_op = aco_opcode::s_buffer_store_dwordx4;
+ break;
+ default:
+ unreachable("Store SSBO not implemented for this size.");
+ }
+ if (ctx->stage == fragment_fs)
+ smem_op = aco_opcode::p_fs_buffer_store_smem;
+
+ if (smem) {
+ aco_ptr<SMEM_instruction> store{create_instruction<SMEM_instruction>(smem_op, Format::SMEM, 3, 0)};
+ store->operands[0] = Operand(rsrc);
+ if (start) {
+ Temp off = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc),
+ offset, Operand(start * elem_size_bytes));
+ store->operands[1] = Operand(off);
+ } else {
+ store->operands[1] = Operand(offset);
+ }
+ if (smem_op != aco_opcode::p_fs_buffer_store_smem)
+ store->operands[1].setFixed(m0);
+ store->operands[2] = Operand(write_data);
+ store->glc = nir_intrinsic_access(instr) & (ACCESS_VOLATILE | ACCESS_COHERENT | ACCESS_NON_READABLE);
+ store->disable_wqm = true;
+ store->barrier = barrier_buffer;
+ ctx->block->instructions.emplace_back(std::move(store));
+ ctx->program->wb_smem_l1_on_end = true;
+ if (smem_op == aco_opcode::p_fs_buffer_store_smem) {
+ ctx->block->kind |= block_kind_needs_lowering;
+ ctx->program->needs_exact = true;
+ }
+ } else {
+ aco_ptr<MUBUF_instruction> store{create_instruction<MUBUF_instruction>(vmem_op, Format::MUBUF, 4, 0)};
+ store->operands[0] = offset.type() == RegType::vgpr ? Operand(offset) : Operand(v1);
+ store->operands[1] = Operand(rsrc);
+ store->operands[2] = offset.type() == RegType::sgpr ? Operand(offset) : Operand((uint32_t) 0);
+ store->operands[3] = Operand(write_data);
+ store->offset = start * elem_size_bytes;
+ store->offen = (offset.type() == RegType::vgpr);
+ store->glc = nir_intrinsic_access(instr) & (ACCESS_VOLATILE | ACCESS_COHERENT | ACCESS_NON_READABLE);
+ store->disable_wqm = true;
+ store->barrier = barrier_buffer;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(store));
+ }
+ }
+}
+
+void visit_atomic_ssbo(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ /* return the previous value if dest is ever used */
+ bool return_previous = false;
+ nir_foreach_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+ nir_foreach_if_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+
+ Builder bld(ctx->program, ctx->block);
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[2].ssa));
+
+ if (instr->intrinsic == nir_intrinsic_ssbo_atomic_comp_swap)
+ data = bld.pseudo(aco_opcode::p_create_vector, bld.def(RegType::vgpr, data.size() * 2),
+ get_ssa_temp(ctx, instr->src[3].ssa), data);
+
+ Temp offset;
+ if (ctx->options->chip_class < GFX8)
+ offset = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+ else
+ offset = get_ssa_temp(ctx, instr->src[1].ssa);
+
+ Temp rsrc = convert_pointer_to_64_bit(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ rsrc = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), rsrc, Operand(0u));
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ aco_opcode op32, op64;
+ switch (instr->intrinsic) {
+ case nir_intrinsic_ssbo_atomic_add:
+ op32 = aco_opcode::buffer_atomic_add;
+ op64 = aco_opcode::buffer_atomic_add_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_imin:
+ op32 = aco_opcode::buffer_atomic_smin;
+ op64 = aco_opcode::buffer_atomic_smin_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_umin:
+ op32 = aco_opcode::buffer_atomic_umin;
+ op64 = aco_opcode::buffer_atomic_umin_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_imax:
+ op32 = aco_opcode::buffer_atomic_smax;
+ op64 = aco_opcode::buffer_atomic_smax_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_umax:
+ op32 = aco_opcode::buffer_atomic_umax;
+ op64 = aco_opcode::buffer_atomic_umax_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_and:
+ op32 = aco_opcode::buffer_atomic_and;
+ op64 = aco_opcode::buffer_atomic_and_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_or:
+ op32 = aco_opcode::buffer_atomic_or;
+ op64 = aco_opcode::buffer_atomic_or_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_xor:
+ op32 = aco_opcode::buffer_atomic_xor;
+ op64 = aco_opcode::buffer_atomic_xor_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_exchange:
+ op32 = aco_opcode::buffer_atomic_swap;
+ op64 = aco_opcode::buffer_atomic_swap_x2;
+ break;
+ case nir_intrinsic_ssbo_atomic_comp_swap:
+ op32 = aco_opcode::buffer_atomic_cmpswap;
+ op64 = aco_opcode::buffer_atomic_cmpswap_x2;
+ break;
+ default:
+ unreachable("visit_atomic_ssbo should only be called with nir_intrinsic_ssbo_atomic_* instructions.");
+ }
+ aco_opcode op = instr->dest.ssa.bit_size == 32 ? op32 : op64;
+ aco_ptr<MUBUF_instruction> mubuf{create_instruction<MUBUF_instruction>(op, Format::MUBUF, 4, return_previous ? 1 : 0)};
+ mubuf->operands[0] = offset.type() == RegType::vgpr ? Operand(offset) : Operand(v1);
+ mubuf->operands[1] = Operand(rsrc);
+ mubuf->operands[2] = offset.type() == RegType::sgpr ? Operand(offset) : Operand((uint32_t) 0);
+ mubuf->operands[3] = Operand(data);
+ if (return_previous)
+ mubuf->definitions[0] = Definition(dst);
+ mubuf->offset = 0;
+ mubuf->offen = (offset.type() == RegType::vgpr);
+ mubuf->glc = return_previous;
+ mubuf->disable_wqm = true;
+ mubuf->barrier = barrier_buffer;
+ ctx->program->needs_exact = true;
+ ctx->block->instructions.emplace_back(std::move(mubuf));
+}
+
+void visit_get_buffer_size(isel_context *ctx, nir_intrinsic_instr *instr) {
+
+ Temp index = convert_pointer_to_64_bit(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Builder bld(ctx->program, ctx->block);
+ Temp desc = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), index, Operand(0u));
+ get_buffer_size(ctx, desc, get_ssa_temp(ctx, &instr->dest.ssa), false);
+}
+
+void visit_load_global(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ unsigned num_components = instr->num_components;
+ unsigned num_bytes = num_components * instr->dest.ssa.bit_size / 8;
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp addr = get_ssa_temp(ctx, instr->src[0].ssa);
+
+ bool glc = nir_intrinsic_access(instr) & (ACCESS_VOLATILE | ACCESS_COHERENT);
+ aco_opcode op;
+ if (dst.type() == RegType::vgpr || (glc && ctx->options->chip_class < GFX8)) {
+ bool global = ctx->options->chip_class >= GFX9;
+ aco_opcode op;
+ switch (num_bytes) {
+ case 4:
+ op = global ? aco_opcode::global_load_dword : aco_opcode::flat_load_dword;
+ break;
+ case 8:
+ op = global ? aco_opcode::global_load_dwordx2 : aco_opcode::flat_load_dwordx2;
+ break;
+ case 12:
+ op = global ? aco_opcode::global_load_dwordx3 : aco_opcode::flat_load_dwordx3;
+ break;
+ case 16:
+ op = global ? aco_opcode::global_load_dwordx4 : aco_opcode::flat_load_dwordx4;
+ break;
+ default:
+ unreachable("load_global not implemented for this size.");
+ }
+ aco_ptr<FLAT_instruction> flat{create_instruction<FLAT_instruction>(op, global ? Format::GLOBAL : Format::FLAT, 2, 1)};
+ flat->operands[0] = Operand(addr);
+ flat->operands[1] = Operand(s1);
+ flat->glc = glc;
+
+ if (dst.type() == RegType::sgpr) {
+ Temp vec = bld.tmp(RegType::vgpr, dst.size());
+ flat->definitions[0] = Definition(vec);
+ ctx->block->instructions.emplace_back(std::move(flat));
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), vec);
+ } else {
+ flat->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(flat));
+ }
+ emit_split_vector(ctx, dst, num_components);
+ } else {
+ switch (num_bytes) {
+ case 4:
+ op = aco_opcode::s_load_dword;
+ break;
+ case 8:
+ op = aco_opcode::s_load_dwordx2;
+ break;
+ case 12:
+ case 16:
+ op = aco_opcode::s_load_dwordx4;
+ break;
+ default:
+ unreachable("load_global not implemented for this size.");
+ }
+ aco_ptr<SMEM_instruction> load{create_instruction<SMEM_instruction>(op, Format::SMEM, 2, 1)};
+ load->operands[0] = Operand(addr);
+ load->operands[1] = Operand(0u);
+ load->definitions[0] = Definition(dst);
+ load->glc = glc;
+ load->barrier = barrier_buffer;
+ assert(ctx->options->chip_class >= GFX8 || !glc);
+
+ if (dst.size() == 3) {
+ /* trim vector */
+ Temp vec = bld.tmp(s4);
+ load->definitions[0] = Definition(vec);
+ ctx->block->instructions.emplace_back(std::move(load));
+ emit_split_vector(ctx, vec, 4);
+
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ emit_extract_vector(ctx, vec, 0, s1),
+ emit_extract_vector(ctx, vec, 1, s1),
+ emit_extract_vector(ctx, vec, 2, s1));
+ } else {
+ ctx->block->instructions.emplace_back(std::move(load));
+ }
+ }
+}
+
+void visit_store_global(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ unsigned elem_size_bytes = instr->src[0].ssa->bit_size / 8;
+
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Temp addr = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+
+ unsigned writemask = nir_intrinsic_write_mask(instr);
+ while (writemask) {
+ int start, count;
+ u_bit_scan_consecutive_range(&writemask, &start, &count);
+ unsigned num_bytes = count * elem_size_bytes;
+
+ Temp write_data = data;
+ if (count != instr->num_components) {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, count, 1)};
+ for (int i = 0; i < count; i++)
+ vec->operands[i] = Operand(emit_extract_vector(ctx, data, start + i, v1));
+ write_data = bld.tmp(RegType::vgpr, count);
+ vec->definitions[0] = Definition(write_data);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+
+ unsigned offset = start * elem_size_bytes;
+ if (offset > 0 && ctx->options->chip_class < GFX9) {
+ Temp addr0 = bld.tmp(v1), addr1 = bld.tmp(v1);
+ Temp new_addr0 = bld.tmp(v1), new_addr1 = bld.tmp(v1);
+ Temp carry = bld.tmp(s2);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(addr0), Definition(addr1), addr);
+
+ bld.vop2(aco_opcode::v_add_co_u32, Definition(new_addr0), bld.hint_vcc(Definition(carry)),
+ Operand(offset), addr0);
+ bld.vop2(aco_opcode::v_addc_co_u32, Definition(new_addr1), bld.def(s2),
+ Operand(0u), addr1,
+ carry).def(1).setHint(vcc);
+
+ addr = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), new_addr0, new_addr1);
+
+ offset = 0;
+ }
+
+ bool glc = nir_intrinsic_access(instr) & (ACCESS_VOLATILE | ACCESS_COHERENT | ACCESS_NON_READABLE);
+ bool global = ctx->options->chip_class >= GFX9;
+ aco_opcode op;
+ switch (num_bytes) {
+ case 4:
+ op = global ? aco_opcode::global_store_dword : aco_opcode::flat_store_dword;
+ break;
+ case 8:
+ op = global ? aco_opcode::global_store_dwordx2 : aco_opcode::flat_store_dwordx2;
+ break;
+ case 12:
+ op = global ? aco_opcode::global_store_dwordx3 : aco_opcode::flat_store_dwordx3;
+ break;
+ case 16:
+ op = global ? aco_opcode::global_store_dwordx4 : aco_opcode::flat_store_dwordx4;
+ break;
+ default:
+ unreachable("store_global not implemented for this size.");
+ }
+ aco_ptr<FLAT_instruction> flat{create_instruction<FLAT_instruction>(op, global ? Format::GLOBAL : Format::FLAT, 3, 0)};
+ flat->operands[0] = Operand(addr);
+ flat->operands[1] = Operand(s1);
+ flat->operands[2] = Operand(data);
+ flat->glc = glc;
+ flat->offset = offset;
+ ctx->block->instructions.emplace_back(std::move(flat));
+ }
+}
+
+void emit_memory_barrier(isel_context *ctx, nir_intrinsic_instr *instr) {
+ Builder bld(ctx->program, ctx->block);
+ switch(instr->intrinsic) {
+ case nir_intrinsic_group_memory_barrier:
+ case nir_intrinsic_memory_barrier:
+ bld.barrier(aco_opcode::p_memory_barrier_all);
+ break;
+ case nir_intrinsic_memory_barrier_atomic_counter:
+ bld.barrier(aco_opcode::p_memory_barrier_atomic);
+ break;
+ case nir_intrinsic_memory_barrier_buffer:
+ bld.barrier(aco_opcode::p_memory_barrier_buffer);
+ break;
+ case nir_intrinsic_memory_barrier_image:
+ bld.barrier(aco_opcode::p_memory_barrier_image);
+ break;
+ case nir_intrinsic_memory_barrier_shared:
+ bld.barrier(aco_opcode::p_memory_barrier_shared);
+ break;
+ default:
+ unreachable("Unimplemented memory barrier intrinsic");
+ break;
+ }
+}
+
+Operand load_lds_size_m0(isel_context *ctx)
+{
+ /* TODO: m0 does not need to be initialized on GFX9+ */
+ Builder bld(ctx->program, ctx->block);
+ return bld.m0((Temp)bld.sopk(aco_opcode::s_movk_i32, bld.def(s1, m0), 0xffff));
+}
+
+
+void visit_load_shared(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ // TODO: implement sparse reads using ds_read2_b32 and nir_ssa_def_components_read()
+ Operand m = load_lds_size_m0(ctx);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ assert(instr->dest.ssa.bit_size >= 32 && "Bitsize not supported in load_shared.");
+ Temp address = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Builder bld(ctx->program, ctx->block);
+
+ unsigned elem_size_bytes = instr->dest.ssa.bit_size / 8;
+ unsigned bytes_read = 0;
+ unsigned result_size = 0;
+ unsigned total_bytes = instr->num_components * elem_size_bytes;
+ unsigned align = nir_intrinsic_align_mul(instr) ? nir_intrinsic_align(instr) : instr->dest.ssa.bit_size / 8;
+ std::array<Temp, 4> result;
+
+ while (bytes_read < total_bytes) {
+ unsigned todo = total_bytes - bytes_read;
+ bool aligned8 = bytes_read % 8 == 0 && align % 8 == 0;
+ bool aligned16 = bytes_read % 16 == 0 && align % 16 == 0;
+
+ aco_opcode op = aco_opcode::last_opcode;
+ if (todo >= 16 && aligned16) {
+ op = aco_opcode::ds_read_b128;
+ todo = 16;
+ } else if (todo >= 12 && aligned16) {
+ op = aco_opcode::ds_read_b96;
+ todo = 12;
+ } else if (todo >= 8) {
+ op = aligned8 ? aco_opcode::ds_read_b64 : aco_opcode::ds_read2_b32;
+ todo = 8;
+ } else if (todo >= 4) {
+ op = aco_opcode::ds_read_b32;
+ todo = 4;
+ } else {
+ assert(false);
+ }
+ assert(todo % elem_size_bytes == 0);
+ unsigned num_elements = todo / elem_size_bytes;
+ unsigned offset = nir_intrinsic_base(instr) + bytes_read;
+ unsigned max_offset = op == aco_opcode::ds_read2_b32 ? 1019 : 65535;
+
+ Temp address_offset = address;
+ if (offset > max_offset) {
+ address_offset = bld.vadd32(bld.def(v1), Operand((uint32_t)nir_intrinsic_base(instr)), address_offset);
+ offset = bytes_read;
+ }
+ assert(offset <= max_offset); /* bytes_read shouldn't be large enough for this to happen */
+
+ Temp res;
+ if (instr->num_components == 1 && dst.type() == RegType::vgpr)
+ res = dst;
+ else
+ res = bld.tmp(RegClass(RegType::vgpr, todo / 4));
+
+ if (op == aco_opcode::ds_read2_b32)
+ res = bld.ds(op, Definition(res), address_offset, m, offset >> 2, (offset >> 2) + 1);
+ else
+ res = bld.ds(op, Definition(res), address_offset, m, offset);
+
+ if (instr->num_components == 1) {
+ assert(todo == total_bytes);
+ if (dst.type() == RegType::sgpr)
+ bld.pseudo(aco_opcode::p_as_uniform, Definition(dst), res);
+ return;
+ }
+
+ if (dst.type() == RegType::sgpr)
+ res = bld.as_uniform(res);
+
+ if (num_elements == 1) {
+ result[result_size++] = res;
+ } else {
+ assert(res != dst && res.size() % num_elements == 0);
+ aco_ptr<Pseudo_instruction> split{create_instruction<Pseudo_instruction>(aco_opcode::p_split_vector, Format::PSEUDO, 1, num_elements)};
+ split->operands[0] = Operand(res);
+ for (unsigned i = 0; i < num_elements; i++)
+ split->definitions[i] = Definition(result[result_size++] = bld.tmp(res.type(), elem_size_bytes / 4));
+ ctx->block->instructions.emplace_back(std::move(split));
+ }
+
+ bytes_read += todo;
+ }
+
+ assert(result_size == instr->num_components && result_size > 1);
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, result_size, 1)};
+ for (unsigned i = 0; i < result_size; i++)
+ vec->operands[i] = Operand(result[i]);
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), result);
+}
+
+void ds_write_helper(isel_context *ctx, Operand m, Temp address, Temp data, unsigned offset0, unsigned offset1, unsigned align)
+{
+ Builder bld(ctx->program, ctx->block);
+ unsigned bytes_written = 0;
+ while (bytes_written < data.size() * 4) {
+ unsigned todo = data.size() * 4 - bytes_written;
+ bool aligned8 = bytes_written % 8 == 0 && align % 8 == 0;
+ bool aligned16 = bytes_written % 16 == 0 && align % 16 == 0;
+
+ aco_opcode op = aco_opcode::last_opcode;
+ unsigned size = 0;
+ if (todo >= 16 && aligned16) {
+ op = aco_opcode::ds_write_b128;
+ size = 4;
+ } else if (todo >= 12 && aligned16) {
+ op = aco_opcode::ds_write_b96;
+ size = 3;
+ } else if (todo >= 8) {
+ op = aligned8 ? aco_opcode::ds_write_b64 : aco_opcode::ds_write2_b32;
+ size = 2;
+ } else if (todo >= 4) {
+ op = aco_opcode::ds_write_b32;
+ size = 1;
+ } else {
+ assert(false);
+ }
+
+ bool write2 = op == aco_opcode::ds_write2_b32;
+ unsigned offset = offset0 + offset1 + bytes_written;
+ unsigned max_offset = write2 ? 1020 : 65535;
+ Temp address_offset = address;
+ if (offset > max_offset) {
+ address_offset = bld.vadd32(bld.def(v1), Operand(offset0), address_offset);
+ offset = offset1 + bytes_written;
+ }
+ assert(offset <= max_offset); /* offset1 shouldn't be large enough for this to happen */
+
+ if (write2) {
+ Temp val0 = emit_extract_vector(ctx, data, bytes_written >> 2, v1);
+ Temp val1 = emit_extract_vector(ctx, data, (bytes_written >> 2) + 1, v1);
+ bld.ds(op, address_offset, val0, val1, m, offset >> 2, (offset >> 2) + 1);
+ } else {
+ Temp val = emit_extract_vector(ctx, data, bytes_written >> 2, RegClass(RegType::vgpr, size));
+ bld.ds(op, address_offset, val, m, offset);
+ }
+
+ bytes_written += size * 4;
+ }
+}
+
+void visit_store_shared(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ unsigned offset = nir_intrinsic_base(instr);
+ unsigned writemask = nir_intrinsic_write_mask(instr);
+ Operand m = load_lds_size_m0(ctx);
+ Temp data = get_ssa_temp(ctx, instr->src[0].ssa);
+ Temp address = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+ unsigned elem_size_bytes = instr->src[0].ssa->bit_size / 8;
+ assert(elem_size_bytes >= 4 && "Only 32bit & 64bit store_shared currently supported.");
+
+ /* we need at most two stores for 32bit variables */
+ int start[2], count[2];
+ u_bit_scan_consecutive_range(&writemask, &start[0], &count[0]);
+ u_bit_scan_consecutive_range(&writemask, &start[1], &count[1]);
+ assert(writemask == 0);
+
+ /* one combined store is sufficient */
+ if (count[0] == count[1]) {
+ Builder bld(ctx->program, ctx->block);
+
+ Temp address_offset = address;
+ if ((offset >> 2) + start[1] > 255) {
+ address_offset = bld.vadd32(bld.def(v1), Operand(offset), address_offset);
+ offset = 0;
+ }
+
+ assert(count[0] == 1);
+ Temp val0 = emit_extract_vector(ctx, data, start[0], v1);
+ Temp val1 = emit_extract_vector(ctx, data, start[1], v1);
+ aco_opcode op = elem_size_bytes == 4 ? aco_opcode::ds_write2_b32 : aco_opcode::ds_write2_b64;
+ offset = offset / elem_size_bytes;
+ bld.ds(op, address_offset, val0, val1, m,
+ offset + start[0], offset + start[1]);
+ return;
+ }
+
+ unsigned align = nir_intrinsic_align_mul(instr) ? nir_intrinsic_align(instr) : elem_size_bytes;
+ for (unsigned i = 0; i < 2; i++) {
+ if (count[i] == 0)
+ continue;
+
+ Temp write_data = emit_extract_vector(ctx, data, start[i], RegClass(RegType::vgpr, count[i] * elem_size_bytes / 4));
+ ds_write_helper(ctx, m, address, write_data, offset, start[i] * elem_size_bytes, align);
+ }
+ return;
+}
+
+void visit_shared_atomic(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ unsigned offset = nir_intrinsic_base(instr);
+ Operand m = load_lds_size_m0(ctx);
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+ Temp address = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+
+ unsigned num_operands = 3;
+ aco_opcode op32, op64, op32_rtn, op64_rtn;
+ switch(instr->intrinsic) {
+ case nir_intrinsic_shared_atomic_add:
+ op32 = aco_opcode::ds_add_u32;
+ op64 = aco_opcode::ds_add_u64;
+ op32_rtn = aco_opcode::ds_add_rtn_u32;
+ op64_rtn = aco_opcode::ds_add_rtn_u64;
+ break;
+ case nir_intrinsic_shared_atomic_imin:
+ op32 = aco_opcode::ds_min_i32;
+ op64 = aco_opcode::ds_min_i64;
+ op32_rtn = aco_opcode::ds_min_rtn_i32;
+ op64_rtn = aco_opcode::ds_min_rtn_i64;
+ break;
+ case nir_intrinsic_shared_atomic_umin:
+ op32 = aco_opcode::ds_min_u32;
+ op64 = aco_opcode::ds_min_u64;
+ op32_rtn = aco_opcode::ds_min_rtn_u32;
+ op64_rtn = aco_opcode::ds_min_rtn_u64;
+ break;
+ case nir_intrinsic_shared_atomic_imax:
+ op32 = aco_opcode::ds_max_i32;
+ op64 = aco_opcode::ds_max_i64;
+ op32_rtn = aco_opcode::ds_max_rtn_i32;
+ op64_rtn = aco_opcode::ds_max_rtn_i64;
+ break;
+ case nir_intrinsic_shared_atomic_umax:
+ op32 = aco_opcode::ds_max_u32;
+ op64 = aco_opcode::ds_max_u64;
+ op32_rtn = aco_opcode::ds_max_rtn_u32;
+ op64_rtn = aco_opcode::ds_max_rtn_u64;
+ break;
+ case nir_intrinsic_shared_atomic_and:
+ op32 = aco_opcode::ds_and_b32;
+ op64 = aco_opcode::ds_and_b64;
+ op32_rtn = aco_opcode::ds_and_rtn_b32;
+ op64_rtn = aco_opcode::ds_and_rtn_b64;
+ break;
+ case nir_intrinsic_shared_atomic_or:
+ op32 = aco_opcode::ds_or_b32;
+ op64 = aco_opcode::ds_or_b64;
+ op32_rtn = aco_opcode::ds_or_rtn_b32;
+ op64_rtn = aco_opcode::ds_or_rtn_b64;
+ break;
+ case nir_intrinsic_shared_atomic_xor:
+ op32 = aco_opcode::ds_xor_b32;
+ op64 = aco_opcode::ds_xor_b64;
+ op32_rtn = aco_opcode::ds_xor_rtn_b32;
+ op64_rtn = aco_opcode::ds_xor_rtn_b64;
+ break;
+ case nir_intrinsic_shared_atomic_exchange:
+ op32 = aco_opcode::ds_write_b32;
+ op64 = aco_opcode::ds_write_b64;
+ op32_rtn = aco_opcode::ds_wrxchg_rtn_b32;
+ op64_rtn = aco_opcode::ds_wrxchg2_rtn_b64;
+ break;
+ case nir_intrinsic_shared_atomic_comp_swap:
+ op32 = aco_opcode::ds_cmpst_b32;
+ op64 = aco_opcode::ds_cmpst_b64;
+ op32_rtn = aco_opcode::ds_cmpst_rtn_b32;
+ op64_rtn = aco_opcode::ds_cmpst_rtn_b64;
+ num_operands = 4;
+ break;
+ default:
+ unreachable("Unhandled shared atomic intrinsic");
+ }
+
+ /* return the previous value if dest is ever used */
+ bool return_previous = false;
+ nir_foreach_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+ nir_foreach_if_use_safe(use_src, &instr->dest.ssa) {
+ return_previous = true;
+ break;
+ }
+
+ aco_opcode op;
+ if (data.size() == 1) {
+ assert(instr->dest.ssa.bit_size == 32);
+ op = return_previous ? op32_rtn : op32;
+ } else {
+ assert(instr->dest.ssa.bit_size == 64);
+ op = return_previous ? op64_rtn : op64;
+ }
+
+ if (offset > 65535) {
+ Builder bld(ctx->program, ctx->block);
+ address = bld.vadd32(bld.def(v1), Operand(offset), address);
+ offset = 0;
+ }
+
+ aco_ptr<DS_instruction> ds;
+ ds.reset(create_instruction<DS_instruction>(op, Format::DS, num_operands, return_previous ? 1 : 0));
+ ds->operands[0] = Operand(address);
+ ds->operands[1] = Operand(data);
+ if (num_operands == 4)
+ ds->operands[2] = Operand(get_ssa_temp(ctx, instr->src[2].ssa));
+ ds->operands[num_operands - 1] = m;
+ ds->offset0 = offset;
+ if (return_previous)
+ ds->definitions[0] = Definition(get_ssa_temp(ctx, &instr->dest.ssa));
+ ctx->block->instructions.emplace_back(std::move(ds));
+}
+
+void visit_load_scratch(isel_context *ctx, nir_intrinsic_instr *instr) {
+ assert(instr->dest.ssa.bit_size == 32 || instr->dest.ssa.bit_size == 64);
+ Builder bld(ctx->program, ctx->block);
+ Temp scratch_addr = ctx->private_segment_buffer;
+ if (ctx->stage != MESA_SHADER_COMPUTE)
+ scratch_addr = bld.smem(aco_opcode::s_load_dwordx2, bld.def(s2), ctx->private_segment_buffer, Operand(0u));
+ uint32_t rsrc_conf;
+ /* older generations need element size = 16 bytes */
+ if (ctx->program->chip_class >= GFX9)
+ rsrc_conf = 0x00E00000u;
+ else
+ rsrc_conf = 0x00F80000u;
+ /* buffer res = addr + num_records = -1, index_stride = 64, add_tid_enable = true */
+ Temp rsrc = bld.pseudo(aco_opcode::p_create_vector, bld.def(s4), scratch_addr, Operand(-1u), Operand(rsrc_conf));
+ Temp offset = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ aco_opcode op;
+ switch (dst.size()) {
+ case 1:
+ op = aco_opcode::buffer_load_dword;
+ break;
+ case 2:
+ op = aco_opcode::buffer_load_dwordx2;
+ break;
+ case 3:
+ op = aco_opcode::buffer_load_dwordx3;
+ break;
+ case 4:
+ op = aco_opcode::buffer_load_dwordx4;
+ break;
+ case 6:
+ case 8: {
+ std::array<Temp,NIR_MAX_VEC_COMPONENTS> elems;
+ Temp lower = bld.mubuf(aco_opcode::buffer_load_dwordx4,
+ bld.def(v4), offset, rsrc,
+ ctx->scratch_offset, 0, true);
+ Temp upper = bld.mubuf(dst.size() == 6 ? aco_opcode::buffer_load_dwordx2 :
+ aco_opcode::buffer_load_dwordx4,
+ dst.size() == 6 ? bld.def(v2) : bld.def(v4),
+ offset, rsrc, ctx->scratch_offset, 16, true);
+ emit_split_vector(ctx, lower, 2);
+ elems[0] = emit_extract_vector(ctx, lower, 0, v2);
+ elems[1] = emit_extract_vector(ctx, lower, 1, v2);
+ if (dst.size() == 8) {
+ emit_split_vector(ctx, upper, 2);
+ elems[2] = emit_extract_vector(ctx, upper, 0, v2);
+ elems[3] = emit_extract_vector(ctx, upper, 1, v2);
+ } else {
+ elems[2] = upper;
+ }
+
+ aco_ptr<Instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector,
+ Format::PSEUDO, dst.size() / 2, 1)};
+ for (unsigned i = 0; i < dst.size() / 2; i++)
+ vec->operands[i] = Operand(elems[i]);
+ vec->definitions[0] = Definition(dst);
+ bld.insert(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), elems);
+ return;
+ }
+ default:
+ unreachable("Wrong dst size for nir_intrinsic_load_scratch");
+ }
+
+ bld.mubuf(op, Definition(dst), offset, rsrc, ctx->scratch_offset, 0, true);
+ emit_split_vector(ctx, dst, instr->num_components);
+}
+
+void visit_store_scratch(isel_context *ctx, nir_intrinsic_instr *instr) {
+ assert(instr->src[0].ssa->bit_size == 32 || instr->src[0].ssa->bit_size == 64);
+ Builder bld(ctx->program, ctx->block);
+ Temp scratch_addr = ctx->private_segment_buffer;
+ if (ctx->stage != MESA_SHADER_COMPUTE)
+ scratch_addr = bld.smem(aco_opcode::s_load_dwordx2, bld.def(s2), ctx->private_segment_buffer, Operand(0u));
+ uint32_t rsrc_conf;
+ /* older generations need element size = 16 bytes */
+ if (ctx->program->chip_class >= GFX9)
+ rsrc_conf = 0x00E00000u;
+ else
+ rsrc_conf = 0x00F80000u;
+ /* buffer res = addr + num_records = -1, index_stride = 64, add_tid_enable = true */
+ Temp rsrc = bld.pseudo(aco_opcode::p_create_vector, bld.def(s4), scratch_addr, Operand(-1u), Operand(rsrc_conf));
+ Temp data = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Temp offset = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[1].ssa));
+
+ unsigned elem_size_bytes = instr->src[0].ssa->bit_size / 8;
+ unsigned writemask = nir_intrinsic_write_mask(instr);
+
+ while (writemask) {
+ int start, count;
+ u_bit_scan_consecutive_range(&writemask, &start, &count);
+ int num_bytes = count * elem_size_bytes;
+
+ if (num_bytes > 16) {
+ assert(elem_size_bytes == 8);
+ writemask |= (((count - 2) << 1) - 1) << (start + 2);
+ count = 2;
+ num_bytes = 16;
+ }
+
+ // TODO: check alignment of sub-dword stores
+ // TODO: split 3 bytes. there is no store instruction for that
+
+ Temp write_data;
+ if (count != instr->num_components) {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, count, 1)};
+ for (int i = 0; i < count; i++) {
+ Temp elem = emit_extract_vector(ctx, data, start + i, RegClass(RegType::vgpr, elem_size_bytes / 4));
+ vec->operands[i] = Operand(elem);
+ }
+ write_data = bld.tmp(RegClass(RegType::vgpr, count * elem_size_bytes / 4));
+ vec->definitions[0] = Definition(write_data);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ } else {
+ write_data = data;
+ }
+
+ aco_opcode op;
+ switch (num_bytes) {
+ case 4:
+ op = aco_opcode::buffer_store_dword;
+ break;
+ case 8:
+ op = aco_opcode::buffer_store_dwordx2;
+ break;
+ case 12:
+ op = aco_opcode::buffer_store_dwordx3;
+ break;
+ case 16:
+ op = aco_opcode::buffer_store_dwordx4;
+ break;
+ default:
+ unreachable("Invalid data size for nir_intrinsic_store_scratch.");
+ }
+
+ bld.mubuf(op, offset, rsrc, ctx->scratch_offset, write_data, start * elem_size_bytes, true);
+ }
+}
+
+void visit_load_sample_mask_in(isel_context *ctx, nir_intrinsic_instr *instr) {
+ uint8_t log2_ps_iter_samples;
+ if (ctx->program->info->ps.force_persample) {
+ log2_ps_iter_samples =
+ util_logbase2(ctx->options->key.fs.num_samples);
+ } else {
+ log2_ps_iter_samples = ctx->options->key.fs.log2_ps_iter_samples;
+ }
+
+ /* The bit pattern matches that used by fixed function fragment
+ * processing. */
+ static const unsigned ps_iter_masks[] = {
+ 0xffff, /* not used */
+ 0x5555,
+ 0x1111,
+ 0x0101,
+ 0x0001,
+ };
+ assert(log2_ps_iter_samples < ARRAY_SIZE(ps_iter_masks));
+
+ Builder bld(ctx->program, ctx->block);
+
+ Temp sample_id = bld.vop3(aco_opcode::v_bfe_u32, bld.def(v1), ctx->fs_inputs[fs_input::ancillary], Operand(8u), Operand(4u));
+ Temp ps_iter_mask = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(ps_iter_masks[log2_ps_iter_samples]));
+ Temp mask = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), sample_id, ps_iter_mask);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.vop2(aco_opcode::v_and_b32, Definition(dst), mask, ctx->fs_inputs[fs_input::sample_coverage]);
+}
+
+Temp emit_boolean_reduce(isel_context *ctx, nir_op op, unsigned cluster_size, Temp src)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ if (cluster_size == 1) {
+ return src;
+ } if (op == nir_op_iand && cluster_size == 4) {
+ //subgroupClusteredAnd(val, 4) -> ~wqm(exec & ~val)
+ Temp tmp = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.def(s1, scc), Operand(exec, s2), src);
+ return bld.sop1(aco_opcode::s_not_b64, bld.def(s2), bld.def(s1, scc),
+ bld.sop1(aco_opcode::s_wqm_b64, bld.def(s2), bld.def(s1, scc), tmp));
+ } else if (op == nir_op_ior && cluster_size == 4) {
+ //subgroupClusteredOr(val, 4) -> wqm(val & exec)
+ return bld.sop1(aco_opcode::s_wqm_b64, bld.def(s2), bld.def(s1, scc),
+ bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2)));
+ } else if (op == nir_op_iand && cluster_size == 64) {
+ //subgroupAnd(val) -> (exec & ~val) == 0
+ Temp tmp = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.def(s1, scc), Operand(exec, s2), src).def(1).getTemp();
+ return bld.sopc(aco_opcode::s_cmp_eq_u32, bld.def(s1, scc), tmp, Operand(0u));
+ } else if (op == nir_op_ior && cluster_size == 64) {
+ //subgroupOr(val) -> (val & exec) != 0
+ return bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2)).def(1).getTemp();
+ } else if (op == nir_op_ixor && cluster_size == 64) {
+ //subgroupXor(val) -> s_bcnt1_i32_b64(val & exec) & 1
+ Temp tmp = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2));
+ tmp = bld.sop1(aco_opcode::s_bcnt1_i32_b64, bld.def(s2), bld.def(s1, scc), tmp);
+ return bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), tmp, Operand(1u)).def(1).getTemp();
+ } else {
+ //subgroupClustered{And,Or,Xor}(val, n) ->
+ //lane_id = v_mbcnt_hi_u32_b32(-1, v_mbcnt_lo_u32_b32(-1, 0))
+ //cluster_offset = ~(n - 1) & lane_id
+ //cluster_mask = ((1 << n) - 1)
+ //subgroupClusteredAnd():
+ // return ((val | ~exec) >> cluster_offset) & cluster_mask == cluster_mask
+ //subgroupClusteredOr():
+ // return ((val & exec) >> cluster_offset) & cluster_mask != 0
+ //subgroupClusteredXor():
+ // return v_bnt_u32_b32(((val & exec) >> cluster_offset) & cluster_mask, 0) & 1 != 0
+ Temp lane_id = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), Operand((uint32_t) -1),
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), Operand((uint32_t) -1), Operand(0u)));
+ Temp cluster_offset = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(~uint32_t(cluster_size - 1)), lane_id);
+
+ Temp tmp;
+ if (op == nir_op_iand)
+ tmp = bld.sop2(aco_opcode::s_orn2_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2));
+ else
+ tmp = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2));
+
+ uint32_t cluster_mask = cluster_size == 32 ? -1 : (1u << cluster_size) - 1u;
+ tmp = bld.vop3(aco_opcode::v_lshrrev_b64, bld.def(v2), cluster_offset, tmp);
+ tmp = emit_extract_vector(ctx, tmp, 0, v1);
+ if (cluster_mask != 0xffffffff)
+ tmp = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(cluster_mask), tmp);
+
+ Definition cmp_def = Definition();
+ if (op == nir_op_iand) {
+ cmp_def = bld.vopc(aco_opcode::v_cmp_eq_u32, bld.def(s2), Operand(cluster_mask), tmp).def(0);
+ } else if (op == nir_op_ior) {
+ cmp_def = bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u), tmp).def(0);
+ } else if (op == nir_op_ixor) {
+ tmp = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(1u),
+ bld.vop3(aco_opcode::v_bcnt_u32_b32, bld.def(v1), tmp, Operand(0u)));
+ cmp_def = bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u), tmp).def(0);
+ }
+ cmp_def.setHint(vcc);
+ return cmp_def.getTemp();
+ }
+}
+
+Temp emit_boolean_exclusive_scan(isel_context *ctx, nir_op op, Temp src)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ //subgroupExclusiveAnd(val) -> mbcnt(exec & ~val) == 0
+ //subgroupExclusiveOr(val) -> mbcnt(val & exec) != 0
+ //subgroupExclusiveXor(val) -> mbcnt(val & exec) & 1 != 0
+ Temp tmp;
+ if (op == nir_op_iand)
+ tmp = bld.sop2(aco_opcode::s_andn2_b64, bld.def(s2), bld.def(s1, scc), Operand(exec, s2), src);
+ else
+ tmp = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2));
+
+ Builder::Result lohi = bld.pseudo(aco_opcode::p_split_vector, bld.def(s1), bld.def(s1), tmp);
+ Temp lo = lohi.def(0).getTemp();
+ Temp hi = lohi.def(1).getTemp();
+ Temp mbcnt = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), hi,
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), lo, Operand(0u)));
+
+ Definition cmp_def = Definition();
+ if (op == nir_op_iand)
+ cmp_def = bld.vopc(aco_opcode::v_cmp_eq_u32, bld.def(s2), Operand(0u), mbcnt).def(0);
+ else if (op == nir_op_ior)
+ cmp_def = bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u), mbcnt).def(0);
+ else if (op == nir_op_ixor)
+ cmp_def = bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u),
+ bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(1u), mbcnt)).def(0);
+ cmp_def.setHint(vcc);
+ return cmp_def.getTemp();
+}
+
+Temp emit_boolean_inclusive_scan(isel_context *ctx, nir_op op, Temp src)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ //subgroupInclusiveAnd(val) -> subgroupExclusiveAnd(val) && val
+ //subgroupInclusiveOr(val) -> subgroupExclusiveOr(val) || val
+ //subgroupInclusiveXor(val) -> subgroupExclusiveXor(val) ^^ val
+ Temp tmp = emit_boolean_exclusive_scan(ctx, op, src);
+ if (op == nir_op_iand)
+ return bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), tmp, src);
+ else if (op == nir_op_ior)
+ return bld.sop2(aco_opcode::s_or_b64, bld.def(s2), bld.def(s1, scc), tmp, src);
+ else if (op == nir_op_ixor)
+ return bld.sop2(aco_opcode::s_xor_b64, bld.def(s2), bld.def(s1, scc), tmp, src);
+
+ assert(false);
+ return Temp();
+}
+
+void emit_uniform_subgroup(isel_context *ctx, nir_intrinsic_instr *instr, Temp src)
+{
+ Builder bld(ctx->program, ctx->block);
+ Definition dst(get_ssa_temp(ctx, &instr->dest.ssa));
+ if (src.regClass().type() == RegType::vgpr) {
+ bld.pseudo(aco_opcode::p_as_uniform, dst, src);
+ } else if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ bld.sopc(aco_opcode::s_cmp_lg_u64, bld.scc(dst), Operand(0u), Operand(src));
+ } else if (src.regClass() == s1) {
+ bld.sop1(aco_opcode::s_mov_b32, dst, src);
+ } else if (src.regClass() == s2) {
+ bld.sop1(aco_opcode::s_mov_b64, dst, src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+}
+
+void emit_interp_center(isel_context *ctx, Temp dst, Temp pos1, Temp pos2)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp p1 = ctx->fs_inputs[fs_input::persp_center_p1];
+ Temp p2 = ctx->fs_inputs[fs_input::persp_center_p2];
+
+ /* Build DD X/Y */
+ Temp tl_1 = bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), p1, dpp_quad_perm(0, 0, 0, 0));
+ Temp ddx_1 = bld.vop2_dpp(aco_opcode::v_sub_f32, bld.def(v1), p1, tl_1, dpp_quad_perm(1, 1, 1, 1));
+ Temp ddy_1 = bld.vop2_dpp(aco_opcode::v_sub_f32, bld.def(v1), p1, tl_1, dpp_quad_perm(2, 2, 2, 2));
+ Temp tl_2 = bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), p2, dpp_quad_perm(0, 0, 0, 0));
+ Temp ddx_2 = bld.vop2_dpp(aco_opcode::v_sub_f32, bld.def(v1), p2, tl_2, dpp_quad_perm(1, 1, 1, 1));
+ Temp ddy_2 = bld.vop2_dpp(aco_opcode::v_sub_f32, bld.def(v1), p2, tl_2, dpp_quad_perm(2, 2, 2, 2));
+
+ /* res_k = p_k + ddx_k * pos1 + ddy_k * pos2 */
+ Temp tmp1 = bld.vop3(aco_opcode::v_mad_f32, bld.def(v1), ddx_1, pos1, p1);
+ Temp tmp2 = bld.vop3(aco_opcode::v_mad_f32, bld.def(v1), ddx_2, pos1, p2);
+ tmp1 = bld.vop3(aco_opcode::v_mad_f32, bld.def(v1), ddy_1, pos2, tmp1);
+ tmp2 = bld.vop3(aco_opcode::v_mad_f32, bld.def(v1), ddy_2, pos2, tmp2);
+ Temp wqm1 = bld.tmp(v1);
+ emit_wqm(ctx, tmp1, wqm1, true);
+ Temp wqm2 = bld.tmp(v1);
+ emit_wqm(ctx, tmp2, wqm2, true);
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), wqm1, wqm2);
+ return;
+}
+
+void visit_intrinsic(isel_context *ctx, nir_intrinsic_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ switch(instr->intrinsic) {
+ case nir_intrinsic_load_barycentric_sample:
+ case nir_intrinsic_load_barycentric_pixel:
+ case nir_intrinsic_load_barycentric_centroid: {
+ glsl_interp_mode mode = (glsl_interp_mode)nir_intrinsic_interp_mode(instr);
+ fs_input input = get_interp_input(instr->intrinsic, mode);
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (input == fs_input::max_inputs) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ Operand(0u), Operand(0u));
+ } else {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ ctx->fs_inputs[input],
+ ctx->fs_inputs[input + 1]);
+ }
+ emit_split_vector(ctx, dst, 2);
+ break;
+ }
+ case nir_intrinsic_load_barycentric_at_sample: {
+ uint32_t sample_pos_offset = RING_PS_SAMPLE_POSITIONS * 16;
+ switch (ctx->options->key.fs.num_samples) {
+ case 2: sample_pos_offset += 1 << 3; break;
+ case 4: sample_pos_offset += 3 << 3; break;
+ case 8: sample_pos_offset += 7 << 3; break;
+ default: break;
+ }
+ Temp sample_pos;
+ Temp addr = get_ssa_temp(ctx, instr->src[0].ssa);
+ nir_const_value* const_addr = nir_src_as_const_value(instr->src[0]);
+ if (addr.type() == RegType::sgpr) {
+ Operand offset;
+ if (const_addr) {
+ sample_pos_offset += const_addr->u32 << 3;
+ offset = Operand(sample_pos_offset);
+ } else if (ctx->options->chip_class >= GFX9) {
+ offset = bld.sop2(aco_opcode::s_lshl3_add_u32, bld.def(s1), bld.def(s1, scc), addr, Operand(sample_pos_offset));
+ } else {
+ offset = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), addr, Operand(3u));
+ offset = bld.sop2(aco_opcode::s_add_u32, bld.def(s1), bld.def(s1, scc), addr, Operand(sample_pos_offset));
+ }
+ addr = ctx->private_segment_buffer;
+ sample_pos = bld.smem(aco_opcode::s_load_dwordx2, bld.def(s2), addr, Operand(offset));
+
+ } else if (ctx->options->chip_class >= GFX9) {
+ addr = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(3u), addr);
+ sample_pos = bld.global(aco_opcode::global_load_dwordx2, bld.def(v2), addr, ctx->private_segment_buffer, sample_pos_offset);
+ } else {
+ /* addr += ctx->private_segment_buffer + sample_pos_offset */
+ Temp tmp0 = bld.tmp(s1);
+ Temp tmp1 = bld.tmp(s1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(tmp0), Definition(tmp1), ctx->private_segment_buffer);
+ Definition scc_tmp = bld.def(s1, scc);
+ tmp0 = bld.sop2(aco_opcode::s_add_u32, bld.def(s1), scc_tmp, tmp0, Operand(sample_pos_offset));
+ tmp1 = bld.sop2(aco_opcode::s_addc_u32, bld.def(s1), bld.def(s1, scc), tmp1, Operand(0u), scc_tmp.getTemp());
+ addr = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(3u), addr);
+ Temp pck0 = bld.tmp(v1);
+ Temp carry = bld.vadd32(Definition(pck0), tmp0, addr, true).def(1).getTemp();
+ tmp1 = as_vgpr(ctx, tmp1);
+ Temp pck1 = bld.vop2_e64(aco_opcode::v_addc_co_u32, bld.def(v1), bld.hint_vcc(bld.def(s2)), tmp1, Operand(0u), carry);
+ addr = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), pck0, pck1);
+
+ /* sample_pos = flat_load_dwordx2 addr */
+ sample_pos = bld.flat(aco_opcode::flat_load_dwordx2, bld.def(v2), addr, Operand(s1));
+ }
+
+ /* sample_pos -= 0.5 */
+ Temp pos1 = bld.tmp(RegClass(sample_pos.type(), 1));
+ Temp pos2 = bld.tmp(RegClass(sample_pos.type(), 1));
+ bld.pseudo(aco_opcode::p_split_vector, Definition(pos1), Definition(pos2), sample_pos);
+ pos1 = bld.vop2_e64(aco_opcode::v_sub_f32, bld.def(v1), pos1, Operand(0x3f000000u));
+ pos2 = bld.vop2_e64(aco_opcode::v_sub_f32, bld.def(v1), pos2, Operand(0x3f000000u));
+
+ emit_interp_center(ctx, get_ssa_temp(ctx, &instr->dest.ssa), pos1, pos2);
+ break;
+ }
+ case nir_intrinsic_load_barycentric_at_offset: {
+ Temp offset = get_ssa_temp(ctx, instr->src[0].ssa);
+ RegClass rc = RegClass(offset.type(), 1);
+ Temp pos1 = bld.tmp(rc), pos2 = bld.tmp(rc);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(pos1), Definition(pos2), offset);
+ emit_interp_center(ctx, get_ssa_temp(ctx, &instr->dest.ssa), pos1, pos2);
+ break;
+ }
+ case nir_intrinsic_load_front_face: {
+ bld.vopc(aco_opcode::v_cmp_lg_u32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)),
+ Operand(0u), ctx->fs_inputs[fs_input::front_face]).def(0).setHint(vcc);
+ break;
+ }
+ case nir_intrinsic_load_view_index:
+ case nir_intrinsic_load_layer_id: {
+ if (instr->intrinsic == nir_intrinsic_load_view_index && (ctx->stage & sw_vs)) {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), Operand(ctx->view_index));
+ break;
+ }
+
+ unsigned idx = nir_intrinsic_base(instr);
+ bld.vintrp(aco_opcode::v_interp_mov_f32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)),
+ Operand(2u), bld.m0(ctx->prim_mask), idx, 0);
+ break;
+ }
+ case nir_intrinsic_load_frag_coord: {
+ emit_load_frag_coord(ctx, get_ssa_temp(ctx, &instr->dest.ssa), 4);
+ break;
+ }
+ case nir_intrinsic_load_sample_pos: {
+ Temp posx = ctx->fs_inputs[fs_input::frag_pos_0];
+ Temp posy = ctx->fs_inputs[fs_input::frag_pos_1];
+ bld.pseudo(aco_opcode::p_create_vector, Definition(get_ssa_temp(ctx, &instr->dest.ssa)),
+ posx.id() ? bld.vop1(aco_opcode::v_fract_f32, bld.def(v1), posx) : Operand(0u),
+ posy.id() ? bld.vop1(aco_opcode::v_fract_f32, bld.def(v1), posy) : Operand(0u));
+ break;
+ }
+ case nir_intrinsic_load_interpolated_input:
+ visit_load_interpolated_input(ctx, instr);
+ break;
+ case nir_intrinsic_store_output:
+ visit_store_output(ctx, instr);
+ break;
+ case nir_intrinsic_load_input:
+ visit_load_input(ctx, instr);
+ break;
+ case nir_intrinsic_load_ubo:
+ visit_load_ubo(ctx, instr);
+ break;
+ case nir_intrinsic_load_push_constant:
+ visit_load_push_constant(ctx, instr);
+ break;
+ case nir_intrinsic_load_constant:
+ visit_load_constant(ctx, instr);
+ break;
+ case nir_intrinsic_vulkan_resource_index:
+ visit_load_resource(ctx, instr);
+ break;
+ case nir_intrinsic_discard:
+ visit_discard(ctx, instr);
+ break;
+ case nir_intrinsic_discard_if:
+ visit_discard_if(ctx, instr);
+ break;
+ case nir_intrinsic_load_shared:
+ visit_load_shared(ctx, instr);
+ break;
+ case nir_intrinsic_store_shared:
+ visit_store_shared(ctx, instr);
+ break;
+ case nir_intrinsic_shared_atomic_add:
+ case nir_intrinsic_shared_atomic_imin:
+ case nir_intrinsic_shared_atomic_umin:
+ case nir_intrinsic_shared_atomic_imax:
+ case nir_intrinsic_shared_atomic_umax:
+ case nir_intrinsic_shared_atomic_and:
+ case nir_intrinsic_shared_atomic_or:
+ case nir_intrinsic_shared_atomic_xor:
+ case nir_intrinsic_shared_atomic_exchange:
+ case nir_intrinsic_shared_atomic_comp_swap:
+ visit_shared_atomic(ctx, instr);
+ break;
+ case nir_intrinsic_image_deref_load:
+ visit_image_load(ctx, instr);
+ break;
+ case nir_intrinsic_image_deref_store:
+ visit_image_store(ctx, instr);
+ break;
+ case nir_intrinsic_image_deref_atomic_add:
+ case nir_intrinsic_image_deref_atomic_umin:
+ case nir_intrinsic_image_deref_atomic_imin:
+ case nir_intrinsic_image_deref_atomic_umax:
+ case nir_intrinsic_image_deref_atomic_imax:
+ case nir_intrinsic_image_deref_atomic_and:
+ case nir_intrinsic_image_deref_atomic_or:
+ case nir_intrinsic_image_deref_atomic_xor:
+ case nir_intrinsic_image_deref_atomic_exchange:
+ case nir_intrinsic_image_deref_atomic_comp_swap:
+ visit_image_atomic(ctx, instr);
+ break;
+ case nir_intrinsic_image_deref_size:
+ visit_image_size(ctx, instr);
+ break;
+ case nir_intrinsic_load_ssbo:
+ visit_load_ssbo(ctx, instr);
+ break;
+ case nir_intrinsic_store_ssbo:
+ visit_store_ssbo(ctx, instr);
+ break;
+ case nir_intrinsic_load_global:
+ visit_load_global(ctx, instr);
+ break;
+ case nir_intrinsic_store_global:
+ visit_store_global(ctx, instr);
+ break;
+ case nir_intrinsic_ssbo_atomic_add:
+ case nir_intrinsic_ssbo_atomic_imin:
+ case nir_intrinsic_ssbo_atomic_umin:
+ case nir_intrinsic_ssbo_atomic_imax:
+ case nir_intrinsic_ssbo_atomic_umax:
+ case nir_intrinsic_ssbo_atomic_and:
+ case nir_intrinsic_ssbo_atomic_or:
+ case nir_intrinsic_ssbo_atomic_xor:
+ case nir_intrinsic_ssbo_atomic_exchange:
+ case nir_intrinsic_ssbo_atomic_comp_swap:
+ visit_atomic_ssbo(ctx, instr);
+ break;
+ case nir_intrinsic_load_scratch:
+ visit_load_scratch(ctx, instr);
+ break;
+ case nir_intrinsic_store_scratch:
+ visit_store_scratch(ctx, instr);
+ break;
+ case nir_intrinsic_get_buffer_size:
+ visit_get_buffer_size(ctx, instr);
+ break;
+ case nir_intrinsic_barrier: {
+ unsigned* bsize = ctx->program->info->cs.block_size;
+ unsigned workgroup_size = bsize[0] * bsize[1] * bsize[2];
+ if (workgroup_size > 64)
+ bld.sopp(aco_opcode::s_barrier);
+ break;
+ }
+ case nir_intrinsic_group_memory_barrier:
+ case nir_intrinsic_memory_barrier:
+ case nir_intrinsic_memory_barrier_atomic_counter:
+ case nir_intrinsic_memory_barrier_buffer:
+ case nir_intrinsic_memory_barrier_image:
+ case nir_intrinsic_memory_barrier_shared:
+ emit_memory_barrier(ctx, instr);
+ break;
+ case nir_intrinsic_load_num_work_groups:
+ case nir_intrinsic_load_work_group_id:
+ case nir_intrinsic_load_local_invocation_id: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp* ids;
+ if (instr->intrinsic == nir_intrinsic_load_num_work_groups)
+ ids = ctx->num_workgroups;
+ else if (instr->intrinsic == nir_intrinsic_load_work_group_id)
+ ids = ctx->workgroup_ids;
+ else
+ ids = ctx->local_invocation_ids;
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst),
+ ids[0].id() ? Operand(ids[0]) : Operand(1u),
+ ids[1].id() ? Operand(ids[1]) : Operand(1u),
+ ids[2].id() ? Operand(ids[2]) : Operand(1u));
+ emit_split_vector(ctx, dst, 3);
+ break;
+ }
+ case nir_intrinsic_load_local_invocation_index: {
+ Temp id = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), Operand((uint32_t) -1),
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), Operand((uint32_t) -1), Operand(0u)));
+ Temp tg_num = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), Operand(0xfc0u), ctx->tg_size);
+ bld.vop2(aco_opcode::v_or_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), tg_num, id);
+ break;
+ }
+ case nir_intrinsic_load_subgroup_id: {
+ if (ctx->stage == compute_cs) {
+ Temp tg_num = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), Operand(0xfc0u), ctx->tg_size);
+ bld.sop2(aco_opcode::s_lshr_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), bld.def(s1, scc), tg_num, Operand(0x6u));
+ } else {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), Operand(0x0u));
+ }
+ break;
+ }
+ case nir_intrinsic_load_subgroup_invocation: {
+ bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), Operand((uint32_t) -1),
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), Operand((uint32_t) -1), Operand(0u)));
+ break;
+ }
+ case nir_intrinsic_load_num_subgroups: {
+ if (ctx->stage == compute_cs)
+ bld.sop2(aco_opcode::s_and_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), bld.def(s1, scc), Operand(0x3fu), ctx->tg_size);
+ else
+ bld.sop1(aco_opcode::s_mov_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)), Operand(0x1u));
+ break;
+ }
+ case nir_intrinsic_ballot: {
+ Definition tmp = bld.def(s2);
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (instr->src[0].ssa->bit_size == 1 && src.regClass() == s2) {
+ bld.sop2(aco_opcode::s_and_b64, tmp, bld.def(s1, scc), Operand(exec, s2), src);
+ } else if (instr->src[0].ssa->bit_size == 1 && src.regClass() == s1) {
+ bld.sop2(aco_opcode::s_cselect_b64, tmp, Operand(exec, s2), Operand(0u), bld.scc(src));
+ } else if (instr->src[0].ssa->bit_size == 32 && src.regClass() == v1) {
+ bld.vopc(aco_opcode::v_cmp_lg_u32, tmp, Operand(0u), src);
+ } else if (instr->src[0].ssa->bit_size == 64 && src.regClass() == v2) {
+ bld.vopc(aco_opcode::v_cmp_lg_u64, tmp, Operand(0u), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ emit_wqm(ctx, tmp.getTemp(), get_ssa_temp(ctx, &instr->dest.ssa));
+ break;
+ }
+ case nir_intrinsic_shuffle: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (!ctx->divergent_vals[instr->dest.ssa.index]) {
+ emit_uniform_subgroup(ctx, instr, src);
+ } else {
+ Temp tid = get_ssa_temp(ctx, instr->src[1].ssa);
+ assert(tid.regClass() == v1);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (src.regClass() == v1) {
+ tid = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(2u), tid);
+ emit_wqm(ctx, bld.ds(aco_opcode::ds_bpermute_b32, bld.def(v1), tid, src), dst);
+ } else if (src.regClass() == v2) {
+ tid = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(2u), tid);
+
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.ds(aco_opcode::ds_bpermute_b32, bld.def(v1), tid, lo));
+ hi = emit_wqm(ctx, bld.ds(aco_opcode::ds_bpermute_b32, bld.def(v1), tid, hi));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ Temp tmp = bld.vop3(aco_opcode::v_lshrrev_b64, bld.def(v2), tid, src);
+ tmp = emit_extract_vector(ctx, tmp, 0, v1);
+ tmp = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(1u), tmp);
+ emit_wqm(ctx, bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u), tmp), dst);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ }
+ break;
+ }
+ case nir_intrinsic_load_sample_id: {
+ bld.vop3(aco_opcode::v_bfe_u32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)),
+ ctx->fs_inputs[ancillary], Operand(8u), Operand(4u));
+ break;
+ }
+ case nir_intrinsic_load_sample_mask_in: {
+ visit_load_sample_mask_in(ctx, instr);
+ break;
+ }
+ case nir_intrinsic_read_first_invocation: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (src.regClass() == v1) {
+ emit_wqm(ctx,
+ bld.vop1(aco_opcode::v_readfirstlane_b32, bld.def(s1), src),
+ dst);
+ } else if (src.regClass() == v2) {
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.vop1(aco_opcode::v_readfirstlane_b32, bld.def(s1), lo));
+ hi = emit_wqm(ctx, bld.vop1(aco_opcode::v_readfirstlane_b32, bld.def(s1), hi));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ emit_wqm(ctx,
+ bld.sopc(aco_opcode::s_bitcmp1_b64, bld.def(s1, scc), src,
+ bld.sop1(aco_opcode::s_ff1_i32_b64, bld.def(s1), Operand(exec, s2))),
+ dst);
+ } else if (src.regClass() == s1) {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), src);
+ } else if (src.regClass() == s2) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_intrinsic_read_invocation: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ Temp lane = get_ssa_temp(ctx, instr->src[1].ssa);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ assert(lane.regClass() == s1);
+ if (src.regClass() == v1) {
+ emit_wqm(ctx, bld.vop3(aco_opcode::v_readlane_b32, bld.def(s1), src, lane), dst);
+ } else if (src.regClass() == v2) {
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.vop3(aco_opcode::v_readlane_b32, bld.def(s1), lo, lane));
+ hi = emit_wqm(ctx, bld.vop3(aco_opcode::v_readlane_b32, bld.def(s1), hi, lane));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ emit_wqm(ctx, bld.sopc(aco_opcode::s_bitcmp1_b64, bld.def(s1, scc), src, lane), dst);
+ } else if (src.regClass() == s1) {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), src);
+ } else if (src.regClass() == s2) {
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), src);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_intrinsic_vote_all: {
+ Temp src = as_divergent_bool(ctx, get_ssa_temp(ctx, instr->src[0].ssa), false);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ assert(src.regClass() == s2);
+ assert(dst.regClass() == s1);
+
+ Definition tmp = bld.def(s1);
+ bld.sopc(aco_opcode::s_cmp_eq_u64, bld.scc(tmp),
+ bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2)),
+ Operand(exec, s2));
+ emit_wqm(ctx, tmp.getTemp(), dst);
+ break;
+ }
+ case nir_intrinsic_vote_any: {
+ Temp src = as_divergent_bool(ctx, get_ssa_temp(ctx, instr->src[0].ssa), false);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ assert(src.regClass() == s2);
+ assert(dst.regClass() == s1);
+
+ Definition tmp = bld.def(s1);
+ bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.scc(tmp), src, Operand(exec, s2));
+ emit_wqm(ctx, tmp.getTemp(), dst);
+ break;
+ }
+ case nir_intrinsic_reduce:
+ case nir_intrinsic_inclusive_scan:
+ case nir_intrinsic_exclusive_scan: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ nir_op op = (nir_op) nir_intrinsic_reduction_op(instr);
+ unsigned cluster_size = instr->intrinsic == nir_intrinsic_reduce ?
+ nir_intrinsic_cluster_size(instr) : 0;
+ cluster_size = util_next_power_of_two(MIN2(cluster_size ? cluster_size : 64, 64));
+
+ if (!ctx->divergent_vals[instr->src[0].ssa->index] && (op == nir_op_ior || op == nir_op_iand)) {
+ emit_uniform_subgroup(ctx, instr, src);
+ } else if (instr->dest.ssa.bit_size == 1) {
+ if (op == nir_op_imul || op == nir_op_umin || op == nir_op_imin)
+ op = nir_op_iand;
+ else if (op == nir_op_iadd)
+ op = nir_op_ixor;
+ else if (op == nir_op_umax || op == nir_op_imax)
+ op = nir_op_ior;
+ assert(op == nir_op_iand || op == nir_op_ior || op == nir_op_ixor);
+
+ switch (instr->intrinsic) {
+ case nir_intrinsic_reduce:
+ emit_wqm(ctx, emit_boolean_reduce(ctx, op, cluster_size, src), dst);
+ break;
+ case nir_intrinsic_exclusive_scan:
+ emit_wqm(ctx, emit_boolean_exclusive_scan(ctx, op, src), dst);
+ break;
+ case nir_intrinsic_inclusive_scan:
+ emit_wqm(ctx, emit_boolean_inclusive_scan(ctx, op, src), dst);
+ break;
+ default:
+ assert(false);
+ }
+ } else if (cluster_size == 1) {
+ bld.copy(Definition(dst), src);
+ } else {
+ src = as_vgpr(ctx, src);
+
+ ReduceOp reduce_op;
+ switch (op) {
+ #define CASE(name) case nir_op_##name: reduce_op = (src.regClass() == v1) ? name##32 : name##64; break;
+ CASE(iadd)
+ CASE(imul)
+ CASE(fadd)
+ CASE(fmul)
+ CASE(imin)
+ CASE(umin)
+ CASE(fmin)
+ CASE(imax)
+ CASE(umax)
+ CASE(fmax)
+ CASE(iand)
+ CASE(ior)
+ CASE(ixor)
+ default:
+ unreachable("unknown reduction op");
+ #undef CASE
+ }
+
+ aco_opcode aco_op;
+ switch (instr->intrinsic) {
+ case nir_intrinsic_reduce: aco_op = aco_opcode::p_reduce; break;
+ case nir_intrinsic_inclusive_scan: aco_op = aco_opcode::p_inclusive_scan; break;
+ case nir_intrinsic_exclusive_scan: aco_op = aco_opcode::p_exclusive_scan; break;
+ default:
+ unreachable("unknown reduce intrinsic");
+ }
+
+ aco_ptr<Pseudo_reduction_instruction> reduce{create_instruction<Pseudo_reduction_instruction>(aco_op, Format::PSEUDO_REDUCTION, 3, 5)};
+ reduce->operands[0] = Operand(src);
+ // filled in by aco_reduce_assign.cpp, used internally as part of the
+ // reduce sequence
+ assert(dst.size() == 1 || dst.size() == 2);
+ reduce->operands[1] = Operand(RegClass(RegType::vgpr, dst.size()).as_linear());
+ reduce->operands[2] = Operand(v1.as_linear());
+
+ Temp tmp_dst = bld.tmp(dst.regClass());
+ reduce->definitions[0] = Definition(tmp_dst);
+ reduce->definitions[1] = bld.def(s2); // used internally
+ reduce->definitions[2] = Definition();
+ reduce->definitions[3] = Definition(scc, s1);
+ reduce->definitions[4] = Definition();
+ reduce->reduce_op = reduce_op;
+ reduce->cluster_size = cluster_size;
+ ctx->block->instructions.emplace_back(std::move(reduce));
+
+ emit_wqm(ctx, tmp_dst, dst);
+ }
+ break;
+ }
+ case nir_intrinsic_quad_broadcast: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (!ctx->divergent_vals[instr->dest.ssa.index]) {
+ emit_uniform_subgroup(ctx, instr, src);
+ } else {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ unsigned lane = nir_src_as_const_value(instr->src[1])->u32;
+ if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ uint32_t half_mask = 0x11111111u << lane;
+ Temp mask_tmp = bld.pseudo(aco_opcode::p_create_vector, bld.def(s2), Operand(half_mask), Operand(half_mask));
+ Temp tmp = bld.tmp(s2);
+ bld.sop1(aco_opcode::s_wqm_b64, Definition(tmp),
+ bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), mask_tmp,
+ bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc), src, Operand(exec, s2))));
+ emit_wqm(ctx, tmp, dst);
+ } else if (instr->dest.ssa.bit_size == 32) {
+ emit_wqm(ctx,
+ bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), src,
+ dpp_quad_perm(lane, lane, lane, lane)),
+ dst);
+ } else if (instr->dest.ssa.bit_size == 64) {
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), lo, dpp_quad_perm(lane, lane, lane, lane)));
+ hi = emit_wqm(ctx, bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), hi, dpp_quad_perm(lane, lane, lane, lane)));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ }
+ break;
+ }
+ case nir_intrinsic_quad_swap_horizontal:
+ case nir_intrinsic_quad_swap_vertical:
+ case nir_intrinsic_quad_swap_diagonal:
+ case nir_intrinsic_quad_swizzle_amd: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (!ctx->divergent_vals[instr->dest.ssa.index]) {
+ emit_uniform_subgroup(ctx, instr, src);
+ break;
+ }
+ uint16_t dpp_ctrl = 0;
+ switch (instr->intrinsic) {
+ case nir_intrinsic_quad_swap_horizontal:
+ dpp_ctrl = dpp_quad_perm(1, 0, 3, 2);
+ break;
+ case nir_intrinsic_quad_swap_vertical:
+ dpp_ctrl = dpp_quad_perm(2, 3, 0, 1);
+ break;
+ case nir_intrinsic_quad_swap_diagonal:
+ dpp_ctrl = dpp_quad_perm(3, 2, 1, 0);
+ break;
+ case nir_intrinsic_quad_swizzle_amd: {
+ dpp_ctrl = nir_intrinsic_swizzle_mask(instr);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (instr->dest.ssa.bit_size == 1 && src.regClass() == s2) {
+ src = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), Operand(0u), Operand((uint32_t)-1), src);
+ src = bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), src, dpp_ctrl);
+ Temp tmp = bld.vopc(aco_opcode::v_cmp_lg_u32, bld.def(s2), Operand(0u), src);
+ emit_wqm(ctx, tmp, dst);
+ } else if (instr->dest.ssa.bit_size == 32) {
+ Temp tmp = bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), src, dpp_ctrl);
+ emit_wqm(ctx, tmp, dst);
+ } else if (instr->dest.ssa.bit_size == 64) {
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), lo, dpp_ctrl));
+ hi = emit_wqm(ctx, bld.vop1_dpp(aco_opcode::v_mov_b32, bld.def(v1), hi, dpp_ctrl));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_intrinsic_masked_swizzle_amd: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ if (!ctx->divergent_vals[instr->dest.ssa.index]) {
+ emit_uniform_subgroup(ctx, instr, src);
+ break;
+ }
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ uint32_t mask = nir_intrinsic_swizzle_mask(instr);
+ if (dst.regClass() == v1) {
+ emit_wqm(ctx,
+ bld.ds(aco_opcode::ds_swizzle_b32, bld.def(v1), src, mask, 0, false),
+ dst);
+ } else if (dst.regClass() == v2) {
+ Temp lo = bld.tmp(v1), hi = bld.tmp(v1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(lo), Definition(hi), src);
+ lo = emit_wqm(ctx, bld.ds(aco_opcode::ds_swizzle_b32, bld.def(v1), lo, mask, 0, false));
+ hi = emit_wqm(ctx, bld.ds(aco_opcode::ds_swizzle_b32, bld.def(v1), hi, mask, 0, false));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_intrinsic_write_invocation_amd: {
+ Temp src = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[0].ssa));
+ Temp val = bld.as_uniform(get_ssa_temp(ctx, instr->src[1].ssa));
+ Temp lane = bld.as_uniform(get_ssa_temp(ctx, instr->src[2].ssa));
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ if (dst.regClass() == v1) {
+ /* src2 is ignored for writelane. RA assigns the same reg for dst */
+ emit_wqm(ctx, bld.vop3(aco_opcode::v_writelane_b32, bld.def(v1), val, lane, src), dst);
+ } else if (dst.regClass() == v2) {
+ Temp src_lo = bld.tmp(v1), src_hi = bld.tmp(v1);
+ Temp val_lo = bld.tmp(s1), val_hi = bld.tmp(s1);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(src_lo), Definition(src_hi), src);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(val_lo), Definition(val_hi), val);
+ Temp lo = emit_wqm(ctx, bld.vop3(aco_opcode::v_writelane_b32, bld.def(v1), val_lo, lane, src_hi));
+ Temp hi = emit_wqm(ctx, bld.vop3(aco_opcode::v_writelane_b32, bld.def(v1), val_hi, lane, src_hi));
+ bld.pseudo(aco_opcode::p_create_vector, Definition(dst), lo, hi);
+ emit_split_vector(ctx, dst, 2);
+ } else {
+ fprintf(stderr, "Unimplemented NIR instr bit size: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ }
+ break;
+ }
+ case nir_intrinsic_mbcnt_amd: {
+ Temp src = get_ssa_temp(ctx, instr->src[0].ssa);
+ RegClass rc = RegClass(src.type(), 1);
+ Temp mask_lo = bld.tmp(rc), mask_hi = bld.tmp(rc);
+ bld.pseudo(aco_opcode::p_split_vector, Definition(mask_lo), Definition(mask_hi), src);
+ Temp tmp = bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), mask_lo, Operand(0u));
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp wqm_tmp = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), mask_hi, tmp);
+ emit_wqm(ctx, wqm_tmp, dst);
+ break;
+ }
+ case nir_intrinsic_load_helper_invocation: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.pseudo(aco_opcode::p_load_helper, Definition(dst));
+ ctx->block->kind |= block_kind_needs_lowering;
+ ctx->program->needs_exact = true;
+ break;
+ }
+ case nir_intrinsic_is_helper_invocation: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.pseudo(aco_opcode::p_is_helper, Definition(dst));
+ ctx->block->kind |= block_kind_needs_lowering;
+ ctx->program->needs_exact = true;
+ break;
+ }
+ case nir_intrinsic_demote:
+ bld.pseudo(aco_opcode::p_demote_to_helper);
+ ctx->block->kind |= block_kind_needs_lowering;
+ ctx->program->needs_exact = true;
+ break;
+ case nir_intrinsic_demote_if: {
+ Temp cond = bld.sop2(aco_opcode::s_and_b64, bld.def(s2), bld.def(s1, scc),
+ as_divergent_bool(ctx, get_ssa_temp(ctx, instr->src[0].ssa), false),
+ Operand(exec, s2));
+ bld.pseudo(aco_opcode::p_demote_to_helper, cond);
+ ctx->block->kind |= block_kind_needs_lowering;
+ ctx->program->needs_exact = true;
+ break;
+ }
+ case nir_intrinsic_first_invocation: {
+ emit_wqm(ctx, bld.sop1(aco_opcode::s_ff1_i32_b64, bld.def(s1), Operand(exec, s2)),
+ get_ssa_temp(ctx, &instr->dest.ssa));
+ break;
+ }
+ case nir_intrinsic_shader_clock:
+ bld.smem(aco_opcode::s_memtime, Definition(get_ssa_temp(ctx, &instr->dest.ssa)));
+ break;
+ case nir_intrinsic_load_vertex_id_zero_base: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), ctx->vertex_id);
+ break;
+ }
+ case nir_intrinsic_load_first_vertex: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), ctx->base_vertex);
+ break;
+ }
+ case nir_intrinsic_load_base_instance: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), ctx->start_instance);
+ break;
+ }
+ case nir_intrinsic_load_instance_id: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), ctx->instance_id);
+ break;
+ }
+ case nir_intrinsic_load_draw_id: {
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ bld.copy(Definition(dst), ctx->draw_id);
+ break;
+ }
+ default:
+ fprintf(stderr, "Unimplemented intrinsic instr: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ abort();
+
+ break;
+ }
+}
+
+
+void tex_fetch_ptrs(isel_context *ctx, nir_tex_instr *instr,
+ Temp *res_ptr, Temp *samp_ptr, Temp *fmask_ptr,
+ enum glsl_base_type *stype)
+{
+ nir_deref_instr *texture_deref_instr = NULL;
+ nir_deref_instr *sampler_deref_instr = NULL;
+ int plane = -1;
+
+ for (unsigned i = 0; i < instr->num_srcs; i++) {
+ switch (instr->src[i].src_type) {
+ case nir_tex_src_texture_deref:
+ texture_deref_instr = nir_src_as_deref(instr->src[i].src);
+ break;
+ case nir_tex_src_sampler_deref:
+ sampler_deref_instr = nir_src_as_deref(instr->src[i].src);
+ break;
+ case nir_tex_src_plane:
+ plane = nir_src_as_int(instr->src[i].src);
+ break;
+ default:
+ break;
+ }
+ }
+
+ *stype = glsl_get_sampler_result_type(texture_deref_instr->type);
+
+ if (!sampler_deref_instr)
+ sampler_deref_instr = texture_deref_instr;
+
+ if (plane >= 0) {
+ assert(instr->op != nir_texop_txf_ms &&
+ instr->op != nir_texop_samples_identical);
+ assert(instr->sampler_dim != GLSL_SAMPLER_DIM_BUF);
+ *res_ptr = get_sampler_desc(ctx, texture_deref_instr, (aco_descriptor_type)(ACO_DESC_PLANE_0 + plane), instr, false, false);
+ } else if (instr->sampler_dim == GLSL_SAMPLER_DIM_BUF) {
+ *res_ptr = get_sampler_desc(ctx, texture_deref_instr, ACO_DESC_BUFFER, instr, false, false);
+ } else {
+ *res_ptr = get_sampler_desc(ctx, texture_deref_instr, ACO_DESC_IMAGE, instr, false, false);
+ }
+ if (samp_ptr) {
+ *samp_ptr = get_sampler_desc(ctx, sampler_deref_instr, ACO_DESC_SAMPLER, instr, false, false);
+ if (instr->sampler_dim < GLSL_SAMPLER_DIM_RECT && ctx->options->chip_class < GFX8) {
+ fprintf(stderr, "Unimplemented sampler descriptor: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ abort();
+ // TODO: build samp_ptr = and(samp_ptr, res_ptr)
+ }
+ }
+ if (fmask_ptr && (instr->op == nir_texop_txf_ms ||
+ instr->op == nir_texop_samples_identical))
+ *fmask_ptr = get_sampler_desc(ctx, texture_deref_instr, ACO_DESC_FMASK, instr, false, false);
+}
+
+void build_cube_select(isel_context *ctx, Temp ma, Temp id, Temp deriv,
+ Temp *out_ma, Temp *out_sc, Temp *out_tc)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ Temp deriv_x = emit_extract_vector(ctx, deriv, 0, v1);
+ Temp deriv_y = emit_extract_vector(ctx, deriv, 1, v1);
+ Temp deriv_z = emit_extract_vector(ctx, deriv, 2, v1);
+
+ Operand neg_one(0xbf800000u);
+ Operand one(0x3f800000u);
+ Operand two(0x40000000u);
+ Operand four(0x40800000u);
+
+ Temp is_ma_positive = bld.vopc(aco_opcode::v_cmp_le_f32, bld.hint_vcc(bld.def(s2)), Operand(0u), ma);
+ Temp sgn_ma = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1), neg_one, one, is_ma_positive);
+ Temp neg_sgn_ma = bld.vop2(aco_opcode::v_sub_f32, bld.def(v1), Operand(0u), sgn_ma);
+
+ Temp is_ma_z = bld.vopc(aco_opcode::v_cmp_le_f32, bld.hint_vcc(bld.def(s2)), four, id);
+ Temp is_ma_y = bld.vopc(aco_opcode::v_cmp_le_f32, bld.def(s2), two, id);
+ is_ma_y = bld.sop2(aco_opcode::s_andn2_b64, bld.hint_vcc(bld.def(s2)), is_ma_y, is_ma_z);
+ Temp is_not_ma_x = bld.sop2(aco_opcode::s_or_b64, bld.hint_vcc(bld.def(s2)), bld.def(s1, scc), is_ma_z, is_ma_y);
+
+ // select sc
+ Temp tmp = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), deriv_z, deriv_x, is_not_ma_x);
+ Temp sgn = bld.vop2_e64(aco_opcode::v_cndmask_b32, bld.def(v1),
+ bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), neg_sgn_ma, sgn_ma, is_ma_z),
+ one, is_ma_y);
+ *out_sc = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), tmp, sgn);
+
+ // select tc
+ tmp = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), deriv_y, deriv_z, is_ma_y);
+ sgn = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), neg_one, sgn_ma, is_ma_y);
+ *out_tc = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), tmp, sgn);
+
+ // select ma
+ tmp = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1),
+ bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), deriv_x, deriv_y, is_ma_y),
+ deriv_z, is_ma_z);
+ tmp = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(0x7fffffffu), tmp);
+ *out_ma = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), two, tmp);
+}
+
+void prepare_cube_coords(isel_context *ctx, Temp* coords, Temp* ddx, Temp* ddy, bool is_deriv, bool is_array)
+{
+ Builder bld(ctx->program, ctx->block);
+ Temp coord_args[4], ma, tc, sc, id;
+ for (unsigned i = 0; i < (is_array ? 4 : 3); i++)
+ coord_args[i] = emit_extract_vector(ctx, *coords, i, v1);
+
+ if (is_array) {
+ coord_args[3] = bld.vop1(aco_opcode::v_rndne_f32, bld.def(v1), coord_args[3]);
+
+ // see comment in ac_prepare_cube_coords()
+ if (ctx->options->chip_class <= GFX8)
+ coord_args[3] = bld.vop2(aco_opcode::v_max_f32, bld.def(v1), Operand(0u), coord_args[3]);
+ }
+
+ ma = bld.vop3(aco_opcode::v_cubema_f32, bld.def(v1), coord_args[0], coord_args[1], coord_args[2]);
+
+ aco_ptr<VOP3A_instruction> vop3a{create_instruction<VOP3A_instruction>(aco_opcode::v_rcp_f32, asVOP3(Format::VOP1), 1, 1)};
+ vop3a->operands[0] = Operand(ma);
+ vop3a->abs[0] = true;
+ Temp invma = bld.tmp(v1);
+ vop3a->definitions[0] = Definition(invma);
+ ctx->block->instructions.emplace_back(std::move(vop3a));
+
+ sc = bld.vop3(aco_opcode::v_cubesc_f32, bld.def(v1), coord_args[0], coord_args[1], coord_args[2]);
+ if (!is_deriv)
+ sc = bld.vop2(aco_opcode::v_madak_f32, bld.def(v1), sc, invma, Operand(0x3fc00000u/*1.5*/));
+
+ tc = bld.vop3(aco_opcode::v_cubetc_f32, bld.def(v1), coord_args[0], coord_args[1], coord_args[2]);
+ if (!is_deriv)
+ tc = bld.vop2(aco_opcode::v_madak_f32, bld.def(v1), tc, invma, Operand(0x3fc00000u/*1.5*/));
+
+ id = bld.vop3(aco_opcode::v_cubeid_f32, bld.def(v1), coord_args[0], coord_args[1], coord_args[2]);
+
+ if (is_deriv) {
+ sc = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), sc, invma);
+ tc = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), tc, invma);
+
+ for (unsigned i = 0; i < 2; i++) {
+ // see comment in ac_prepare_cube_coords()
+ Temp deriv_ma;
+ Temp deriv_sc, deriv_tc;
+ build_cube_select(ctx, ma, id, i ? *ddy : *ddx,
+ &deriv_ma, &deriv_sc, &deriv_tc);
+
+ deriv_ma = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), deriv_ma, invma);
+
+ Temp x = bld.vop2(aco_opcode::v_sub_f32, bld.def(v1),
+ bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), deriv_sc, invma),
+ bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), deriv_ma, sc));
+ Temp y = bld.vop2(aco_opcode::v_sub_f32, bld.def(v1),
+ bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), deriv_tc, invma),
+ bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), deriv_ma, tc));
+ *(i ? ddy : ddx) = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2), x, y);
+ }
+
+ sc = bld.vop2(aco_opcode::v_add_f32, bld.def(v1), Operand(0x3fc00000u/*1.5*/), sc);
+ tc = bld.vop2(aco_opcode::v_add_f32, bld.def(v1), Operand(0x3fc00000u/*1.5*/), tc);
+ }
+
+ if (is_array)
+ id = bld.vop2(aco_opcode::v_madmk_f32, bld.def(v1), coord_args[3], id, Operand(0x41000000u/*8.0*/));
+ *coords = bld.pseudo(aco_opcode::p_create_vector, bld.def(v3), sc, tc, id);
+
+}
+
+Temp apply_round_slice(isel_context *ctx, Temp coords, unsigned idx)
+{
+ Temp coord_vec[3];
+ for (unsigned i = 0; i < coords.size(); i++)
+ coord_vec[i] = emit_extract_vector(ctx, coords, i, v1);
+
+ Builder bld(ctx->program, ctx->block);
+ coord_vec[idx] = bld.vop1(aco_opcode::v_rndne_f32, bld.def(v1), coord_vec[idx]);
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, coords.size(), 1)};
+ for (unsigned i = 0; i < coords.size(); i++)
+ vec->operands[i] = Operand(coord_vec[i]);
+ Temp res = bld.tmp(RegType::vgpr, coords.size());
+ vec->definitions[0] = Definition(res);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ return res;
+}
+
+void get_const_vec(nir_ssa_def *vec, nir_const_value *cv[4])
+{
+ if (vec->parent_instr->type != nir_instr_type_alu)
+ return;
+ nir_alu_instr *vec_instr = nir_instr_as_alu(vec->parent_instr);
+ if (vec_instr->op != nir_op_vec(vec->num_components))
+ return;
+
+ for (unsigned i = 0; i < vec->num_components; i++) {
+ cv[i] = vec_instr->src[i].swizzle[0] == 0 ?
+ nir_src_as_const_value(vec_instr->src[i].src) : NULL;
+ }
+}
+
+void visit_tex(isel_context *ctx, nir_tex_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ bool has_bias = false, has_lod = false, level_zero = false, has_compare = false,
+ has_offset = false, has_ddx = false, has_ddy = false, has_derivs = false, has_sample_index = false;
+ Temp resource, sampler, fmask_ptr, bias = Temp(), coords, compare = Temp(), sample_index = Temp(),
+ lod = Temp(), offset = Temp(), ddx = Temp(), ddy = Temp(), derivs = Temp();
+ nir_const_value *sample_index_cv = NULL;
+ nir_const_value *const_offset[4] = {NULL, NULL, NULL, NULL};
+ enum glsl_base_type stype;
+ tex_fetch_ptrs(ctx, instr, &resource, &sampler, &fmask_ptr, &stype);
+
+ bool tg4_integer_workarounds = ctx->options->chip_class <= GFX8 && instr->op == nir_texop_tg4 &&
+ (stype == GLSL_TYPE_UINT || stype == GLSL_TYPE_INT);
+ bool tg4_integer_cube_workaround = tg4_integer_workarounds &&
+ instr->sampler_dim == GLSL_SAMPLER_DIM_CUBE;
+
+ for (unsigned i = 0; i < instr->num_srcs; i++) {
+ switch (instr->src[i].src_type) {
+ case nir_tex_src_coord:
+ coords = as_vgpr(ctx, get_ssa_temp(ctx, instr->src[i].src.ssa));
+ break;
+ case nir_tex_src_bias:
+ if (instr->op == nir_texop_txb) {
+ bias = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ has_bias = true;
+ }
+ break;
+ case nir_tex_src_lod: {
+ nir_const_value *val = nir_src_as_const_value(instr->src[i].src);
+
+ if (val && val->f32 <= 0.0) {
+ level_zero = true;
+ } else {
+ lod = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ has_lod = true;
+ }
+ break;
+ }
+ case nir_tex_src_comparator:
+ if (instr->is_shadow) {
+ compare = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ has_compare = true;
+ }
+ break;
+ case nir_tex_src_offset:
+ offset = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ get_const_vec(instr->src[i].src.ssa, const_offset);
+ has_offset = true;
+ break;
+ case nir_tex_src_ddx:
+ ddx = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ has_ddx = true;
+ break;
+ case nir_tex_src_ddy:
+ ddy = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ has_ddy = true;
+ break;
+ case nir_tex_src_ms_index:
+ sample_index = get_ssa_temp(ctx, instr->src[i].src.ssa);
+ sample_index_cv = nir_src_as_const_value(instr->src[i].src);
+ has_sample_index = true;
+ break;
+ case nir_tex_src_texture_offset:
+ case nir_tex_src_sampler_offset:
+ default:
+ break;
+ }
+ }
+// TODO: all other cases: structure taken from ac_nir_to_llvm.c
+ if (instr->op == nir_texop_txs && instr->sampler_dim == GLSL_SAMPLER_DIM_BUF)
+ return get_buffer_size(ctx, resource, get_ssa_temp(ctx, &instr->dest.ssa), true);
+
+ if (instr->op == nir_texop_texture_samples) {
+ Temp dword3 = emit_extract_vector(ctx, resource, 3, s1);
+
+ Temp samples_log2 = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), dword3, Operand(16u | 4u<<16));
+ Temp samples = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), Operand(1u), samples_log2);
+ Temp type = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), dword3, Operand(28u | 4u<<16 /* offset=28, width=4 */));
+ Temp is_msaa = bld.sopc(aco_opcode::s_cmp_ge_u32, bld.def(s1, scc), type, Operand(14u));
+
+ bld.sop2(aco_opcode::s_cselect_b32, Definition(get_ssa_temp(ctx, &instr->dest.ssa)),
+ samples, Operand(1u), bld.scc(is_msaa));
+ return;
+ }
+
+ if (has_offset && instr->op != nir_texop_txf && instr->op != nir_texop_txf_ms) {
+ aco_ptr<Instruction> tmp_instr;
+ Temp acc, pack = Temp();
+
+ uint32_t pack_const = 0;
+ for (unsigned i = 0; i < offset.size(); i++) {
+ if (!const_offset[i])
+ continue;
+ pack_const |= (const_offset[i]->u32 & 0x3Fu) << (8u * i);
+ }
+
+ if (offset.type() == RegType::sgpr) {
+ for (unsigned i = 0; i < offset.size(); i++) {
+ if (const_offset[i])
+ continue;
+
+ acc = emit_extract_vector(ctx, offset, i, s1);
+ acc = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), acc, Operand(0x3Fu));
+
+ if (i) {
+ acc = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), acc, Operand(8u * i));
+ }
+
+ if (pack == Temp()) {
+ pack = acc;
+ } else {
+ pack = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), pack, acc);
+ }
+ }
+
+ if (pack_const && pack != Temp())
+ pack = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), Operand(pack_const), pack);
+ } else {
+ for (unsigned i = 0; i < offset.size(); i++) {
+ if (const_offset[i])
+ continue;
+
+ acc = emit_extract_vector(ctx, offset, i, v1);
+ acc = bld.vop2(aco_opcode::v_and_b32, bld.def(v1), Operand(0x3Fu), acc);
+
+ if (i) {
+ acc = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(8u * i), acc);
+ }
+
+ if (pack == Temp()) {
+ pack = acc;
+ } else {
+ pack = bld.vop2(aco_opcode::v_or_b32, bld.def(v1), pack, acc);
+ }
+ }
+
+ if (pack_const && pack != Temp())
+ pack = bld.sop2(aco_opcode::v_or_b32, bld.def(v1), Operand(pack_const), pack);
+ }
+ if (pack_const && pack == Temp())
+ offset = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(pack_const));
+ else if (pack == Temp())
+ has_offset = false;
+ else
+ offset = pack;
+ }
+
+ if (instr->sampler_dim == GLSL_SAMPLER_DIM_CUBE && instr->coord_components)
+ prepare_cube_coords(ctx, &coords, &ddx, &ddy, instr->op == nir_texop_txd, instr->is_array && instr->op != nir_texop_lod);
+
+ /* pack derivatives */
+ if (has_ddx || has_ddy) {
+ if (instr->sampler_dim == GLSL_SAMPLER_DIM_1D && ctx->options->chip_class >= GFX9) {
+ derivs = bld.pseudo(aco_opcode::p_create_vector, bld.def(v4),
+ ddx, Operand(0u), ddy, Operand(0u));
+ } else {
+ derivs = bld.pseudo(aco_opcode::p_create_vector, bld.def(RegType::vgpr, ddx.size() + ddy.size()), ddx, ddy);
+ }
+ has_derivs = true;
+ }
+
+ if (instr->coord_components > 1 &&
+ instr->sampler_dim == GLSL_SAMPLER_DIM_1D &&
+ instr->is_array &&
+ instr->op != nir_texop_txf)
+ coords = apply_round_slice(ctx, coords, 1);
+
+ if (instr->coord_components > 2 &&
+ (instr->sampler_dim == GLSL_SAMPLER_DIM_2D ||
+ instr->sampler_dim == GLSL_SAMPLER_DIM_MS ||
+ instr->sampler_dim == GLSL_SAMPLER_DIM_SUBPASS ||
+ instr->sampler_dim == GLSL_SAMPLER_DIM_SUBPASS_MS) &&
+ instr->is_array &&
+ instr->op != nir_texop_txf && instr->op != nir_texop_txf_ms)
+ coords = apply_round_slice(ctx, coords, 2);
+
+ if (ctx->options->chip_class >= GFX9 &&
+ instr->sampler_dim == GLSL_SAMPLER_DIM_1D &&
+ instr->op != nir_texop_lod && instr->coord_components) {
+ assert(coords.size() > 0 && coords.size() < 3);
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, coords.size() + 1, 1)};
+ vec->operands[0] = Operand(emit_extract_vector(ctx, coords, 0, v1));
+ vec->operands[1] = instr->op == nir_texop_txf ? Operand((uint32_t) 0) : Operand((uint32_t) 0x3f000000);
+ if (coords.size() > 1)
+ vec->operands[2] = Operand(emit_extract_vector(ctx, coords, 1, v1));
+ coords = bld.tmp(RegType::vgpr, coords.size() + 1);
+ vec->definitions[0] = Definition(coords);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+
+ bool da = should_declare_array(ctx, instr->sampler_dim, instr->is_array);
+
+ if (instr->op == nir_texop_samples_identical)
+ resource = fmask_ptr;
+
+ else if ((instr->sampler_dim == GLSL_SAMPLER_DIM_MS ||
+ instr->sampler_dim == GLSL_SAMPLER_DIM_SUBPASS_MS) &&
+ instr->op != nir_texop_txs) {
+ assert(has_sample_index);
+ Operand op(sample_index);
+ if (sample_index_cv)
+ op = Operand(sample_index_cv->u32);
+ sample_index = adjust_sample_index_using_fmask(ctx, da, coords, op, fmask_ptr);
+ }
+
+ if (has_offset && (instr->op == nir_texop_txf || instr->op == nir_texop_txf_ms)) {
+ Temp split_coords[coords.size()];
+ emit_split_vector(ctx, coords, coords.size());
+ for (unsigned i = 0; i < coords.size(); i++)
+ split_coords[i] = emit_extract_vector(ctx, coords, i, v1);
+
+ unsigned i = 0;
+ for (; i < std::min(offset.size(), instr->coord_components); i++) {
+ Temp off = emit_extract_vector(ctx, offset, i, v1);
+ split_coords[i] = bld.vadd32(bld.def(v1), split_coords[i], off);
+ }
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, coords.size(), 1)};
+ for (unsigned i = 0; i < coords.size(); i++)
+ vec->operands[i] = Operand(split_coords[i]);
+ coords = bld.tmp(coords.regClass());
+ vec->definitions[0] = Definition(coords);
+ ctx->block->instructions.emplace_back(std::move(vec));
+
+ has_offset = false;
+ }
+
+ /* Build tex instruction */
+ unsigned dmask = nir_ssa_def_components_read(&instr->dest.ssa);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+ Temp tmp_dst = dst;
+
+ /* gather4 selects the component by dmask and always returns vec4 */
+ if (instr->op == nir_texop_tg4) {
+ assert(instr->dest.ssa.num_components == 4);
+ if (instr->is_shadow)
+ dmask = 1;
+ else
+ dmask = 1 << instr->component;
+ if (tg4_integer_cube_workaround || dst.type() == RegType::sgpr)
+ tmp_dst = bld.tmp(v4);
+ } else if (instr->op == nir_texop_samples_identical) {
+ tmp_dst = bld.tmp(v1);
+ } else if (util_bitcount(dmask) != instr->dest.ssa.num_components || dst.type() == RegType::sgpr) {
+ tmp_dst = bld.tmp(RegClass(RegType::vgpr, util_bitcount(dmask)));
+ }
+
+ aco_ptr<MIMG_instruction> tex;
+ if (instr->op == nir_texop_txs || instr->op == nir_texop_query_levels) {
+ if (!has_lod)
+ lod = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(0u));
+
+ bool div_by_6 = instr->op == nir_texop_txs &&
+ instr->sampler_dim == GLSL_SAMPLER_DIM_CUBE &&
+ instr->is_array &&
+ (dmask & (1 << 2));
+ if (tmp_dst.id() == dst.id() && div_by_6)
+ tmp_dst = bld.tmp(tmp_dst.regClass());
+
+ tex.reset(create_instruction<MIMG_instruction>(aco_opcode::image_get_resinfo, Format::MIMG, 2, 1));
+ tex->operands[0] = Operand(as_vgpr(ctx,lod));
+ tex->operands[1] = Operand(resource);
+ if (ctx->options->chip_class >= GFX9 &&
+ instr->op == nir_texop_txs &&
+ instr->sampler_dim == GLSL_SAMPLER_DIM_1D &&
+ instr->is_array) {
+ tex->dmask = (dmask & 0x1) | ((dmask & 0x2) << 1);
+ } else if (instr->op == nir_texop_query_levels) {
+ tex->dmask = 1 << 3;
+ } else {
+ tex->dmask = dmask;
+ }
+ tex->da = da;
+ tex->definitions[0] = Definition(tmp_dst);
+ tex->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(tex));
+
+ if (div_by_6) {
+ /* divide 3rd value by 6 by multiplying with magic number */
+ emit_split_vector(ctx, tmp_dst, tmp_dst.size());
+ Temp c = bld.copy(bld.def(s1), Operand((uint32_t) 0x2AAAAAAB));
+ Temp by_6 = bld.vop3(aco_opcode::v_mul_hi_i32, bld.def(v1), emit_extract_vector(ctx, tmp_dst, 2, v1), c);
+ assert(instr->dest.ssa.num_components == 3);
+ Temp tmp = dst.type() == RegType::vgpr ? dst : bld.tmp(v3);
+ tmp_dst = bld.pseudo(aco_opcode::p_create_vector, Definition(tmp),
+ emit_extract_vector(ctx, tmp_dst, 0, v1),
+ emit_extract_vector(ctx, tmp_dst, 1, v1),
+ by_6);
+
+ }
+
+ expand_vector(ctx, tmp_dst, dst, instr->dest.ssa.num_components, dmask);
+ return;
+ }
+
+ Temp tg4_compare_cube_wa64 = Temp();
+
+ if (tg4_integer_workarounds) {
+ tex.reset(create_instruction<MIMG_instruction>(aco_opcode::image_get_resinfo, Format::MIMG, 2, 1));
+ tex->operands[0] = bld.vop1(aco_opcode::v_mov_b32, bld.def(v1), Operand(0u));
+ tex->operands[1] = Operand(resource);
+ tex->dmask = 0x3;
+ tex->da = da;
+ Temp size = bld.tmp(v2);
+ tex->definitions[0] = Definition(size);
+ tex->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(tex));
+ emit_split_vector(ctx, size, size.size());
+
+ Temp half_texel[2];
+ for (unsigned i = 0; i < 2; i++) {
+ half_texel[i] = emit_extract_vector(ctx, size, i, v1);
+ half_texel[i] = bld.vop1(aco_opcode::v_cvt_f32_i32, bld.def(v1), half_texel[i]);
+ half_texel[i] = bld.vop1(aco_opcode::v_rcp_iflag_f32, bld.def(v1), half_texel[i]);
+ half_texel[i] = bld.vop2(aco_opcode::v_mul_f32, bld.def(v1), Operand(0xbf000000/*-0.5*/), half_texel[i]);
+ }
+
+ Temp orig_coords[2] = {
+ emit_extract_vector(ctx, coords, 0, v1),
+ emit_extract_vector(ctx, coords, 1, v1)};
+ Temp new_coords[2] = {
+ bld.vop2(aco_opcode::v_add_f32, bld.def(v1), orig_coords[0], half_texel[0]),
+ bld.vop2(aco_opcode::v_add_f32, bld.def(v1), orig_coords[1], half_texel[1])
+ };
+
+ if (tg4_integer_cube_workaround) {
+ // see comment in ac_nir_to_llvm.c's lower_gather4_integer()
+ Temp desc[resource.size()];
+ aco_ptr<Instruction> split{create_instruction<Pseudo_instruction>(aco_opcode::p_split_vector,
+ Format::PSEUDO, 1, resource.size())};
+ split->operands[0] = Operand(resource);
+ for (unsigned i = 0; i < resource.size(); i++) {
+ desc[i] = bld.tmp(s1);
+ split->definitions[i] = Definition(desc[i]);
+ }
+ ctx->block->instructions.emplace_back(std::move(split));
+
+ Temp dfmt = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), desc[1], Operand(20u | (6u << 16)));
+ Temp compare_cube_wa = bld.sopc(aco_opcode::s_cmp_eq_u32, bld.def(s1, scc), dfmt,
+ Operand((uint32_t)V_008F14_IMG_DATA_FORMAT_8_8_8_8));
+
+ Temp nfmt;
+ if (stype == GLSL_TYPE_UINT) {
+ nfmt = bld.sop2(aco_opcode::s_cselect_b32, bld.def(s1),
+ Operand((uint32_t)V_008F14_IMG_NUM_FORMAT_USCALED),
+ Operand((uint32_t)V_008F14_IMG_NUM_FORMAT_UINT),
+ bld.scc(compare_cube_wa));
+ } else {
+ nfmt = bld.sop2(aco_opcode::s_cselect_b32, bld.def(s1),
+ Operand((uint32_t)V_008F14_IMG_NUM_FORMAT_SSCALED),
+ Operand((uint32_t)V_008F14_IMG_NUM_FORMAT_SINT),
+ bld.scc(compare_cube_wa));
+ }
+ tg4_compare_cube_wa64 = as_divergent_bool(ctx, compare_cube_wa, true);
+ nfmt = bld.sop2(aco_opcode::s_lshl_b32, bld.def(s1), bld.def(s1, scc), nfmt, Operand(26u));
+
+ desc[1] = bld.sop2(aco_opcode::s_and_b32, bld.def(s1), bld.def(s1, scc), desc[1],
+ Operand((uint32_t)C_008F14_NUM_FORMAT));
+ desc[1] = bld.sop2(aco_opcode::s_or_b32, bld.def(s1), bld.def(s1, scc), desc[1], nfmt);
+
+ aco_ptr<Instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector,
+ Format::PSEUDO, resource.size(), 1)};
+ for (unsigned i = 0; i < resource.size(); i++)
+ vec->operands[i] = Operand(desc[i]);
+ resource = bld.tmp(resource.regClass());
+ vec->definitions[0] = Definition(resource);
+ ctx->block->instructions.emplace_back(std::move(vec));
+
+ new_coords[0] = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1),
+ new_coords[0], orig_coords[0], tg4_compare_cube_wa64);
+ new_coords[1] = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1),
+ new_coords[1], orig_coords[1], tg4_compare_cube_wa64);
+ }
+
+ if (coords.size() == 3) {
+ coords = bld.pseudo(aco_opcode::p_create_vector, bld.def(v3),
+ new_coords[0], new_coords[1],
+ emit_extract_vector(ctx, coords, 2, v1));
+ } else {
+ assert(coords.size() == 2);
+ coords = bld.pseudo(aco_opcode::p_create_vector, bld.def(v2),
+ new_coords[0], new_coords[1]);
+ }
+ }
+
+ if (!(has_ddx && has_ddy) && !has_lod && !level_zero &&
+ instr->sampler_dim != GLSL_SAMPLER_DIM_MS &&
+ instr->sampler_dim != GLSL_SAMPLER_DIM_SUBPASS_MS)
+ coords = emit_wqm(ctx, coords, bld.tmp(coords.regClass()), true);
+
+ std::vector<Operand> args;
+ if (has_offset)
+ args.emplace_back(Operand(offset));
+ if (has_bias)
+ args.emplace_back(Operand(bias));
+ if (has_compare)
+ args.emplace_back(Operand(compare));
+ if (has_derivs)
+ args.emplace_back(Operand(derivs));
+ args.emplace_back(Operand(coords));
+ if (has_sample_index)
+ args.emplace_back(Operand(sample_index));
+ if (has_lod)
+ args.emplace_back(lod);
+
+ Operand arg;
+ if (args.size() > 1) {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, args.size(), 1)};
+ unsigned size = 0;
+ for (unsigned i = 0; i < args.size(); i++) {
+ size += args[i].size();
+ vec->operands[i] = args[i];
+ }
+ RegClass rc = RegClass(RegType::vgpr, size);
+ Temp tmp = bld.tmp(rc);
+ vec->definitions[0] = Definition(tmp);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ arg = Operand(tmp);
+ } else {
+ assert(args[0].isTemp());
+ arg = Operand(as_vgpr(ctx, args[0].getTemp()));
+ }
+
+ if (instr->sampler_dim == GLSL_SAMPLER_DIM_BUF) {
+ //FIXME: if (ctx->abi->gfx9_stride_size_workaround) return ac_build_buffer_load_format_gfx9_safe()
+
+ assert(coords.size() == 1);
+ unsigned last_bit = util_last_bit(nir_ssa_def_components_read(&instr->dest.ssa));
+ aco_opcode op;
+ switch (last_bit) {
+ case 1:
+ op = aco_opcode::buffer_load_format_x; break;
+ case 2:
+ op = aco_opcode::buffer_load_format_xy; break;
+ case 3:
+ op = aco_opcode::buffer_load_format_xyz; break;
+ case 4:
+ op = aco_opcode::buffer_load_format_xyzw; break;
+ default:
+ unreachable("Tex instruction loads more than 4 components.");
+ }
+
+ /* if the instruction return value matches exactly the nir dest ssa, we can use it directly */
+ if (last_bit == instr->dest.ssa.num_components && dst.type() == RegType::vgpr)
+ tmp_dst = dst;
+ else
+ tmp_dst = bld.tmp(RegType::vgpr, last_bit);
+
+ aco_ptr<MUBUF_instruction> mubuf{create_instruction<MUBUF_instruction>(op, Format::MUBUF, 3, 1)};
+ mubuf->operands[0] = Operand(coords);
+ mubuf->operands[1] = Operand(resource);
+ mubuf->operands[2] = Operand((uint32_t) 0);
+ mubuf->definitions[0] = Definition(tmp_dst);
+ mubuf->idxen = true;
+ mubuf->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(mubuf));
+
+ expand_vector(ctx, tmp_dst, dst, instr->dest.ssa.num_components, (1 << last_bit) - 1);
+ return;
+ }
+
+
+ if (instr->op == nir_texop_txf ||
+ instr->op == nir_texop_txf_ms ||
+ instr->op == nir_texop_samples_identical) {
+ aco_opcode op = level_zero || instr->sampler_dim == GLSL_SAMPLER_DIM_MS ? aco_opcode::image_load : aco_opcode::image_load_mip;
+ tex.reset(create_instruction<MIMG_instruction>(op, Format::MIMG, 2, 1));
+ tex->operands[0] = Operand(arg);
+ tex->operands[1] = Operand(resource);
+ tex->dmask = dmask;
+ tex->unrm = true;
+ tex->da = da;
+ tex->definitions[0] = Definition(tmp_dst);
+ tex->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(tex));
+
+ if (instr->op == nir_texop_samples_identical) {
+ assert(dmask == 1 && dst.regClass() == v1);
+ assert(dst.id() != tmp_dst.id());
+
+ Temp tmp = bld.tmp(s2);
+ bld.vopc(aco_opcode::v_cmp_eq_u32, Definition(tmp), Operand(0u), tmp_dst).def(0).setHint(vcc);
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(dst), Operand(0u), Operand((uint32_t)-1), tmp);
+
+ } else {
+ expand_vector(ctx, tmp_dst, dst, instr->dest.ssa.num_components, dmask);
+ }
+ return;
+ }
+
+ // TODO: would be better to do this by adding offsets, but needs the opcodes ordered.
+ aco_opcode opcode = aco_opcode::image_sample;
+ if (has_offset) { /* image_sample_*_o */
+ if (has_compare) {
+ opcode = aco_opcode::image_sample_c_o;
+ if (has_derivs)
+ opcode = aco_opcode::image_sample_c_d_o;
+ if (has_bias)
+ opcode = aco_opcode::image_sample_c_b_o;
+ if (level_zero)
+ opcode = aco_opcode::image_sample_c_lz_o;
+ if (has_lod)
+ opcode = aco_opcode::image_sample_c_l_o;
+ } else {
+ opcode = aco_opcode::image_sample_o;
+ if (has_derivs)
+ opcode = aco_opcode::image_sample_d_o;
+ if (has_bias)
+ opcode = aco_opcode::image_sample_b_o;
+ if (level_zero)
+ opcode = aco_opcode::image_sample_lz_o;
+ if (has_lod)
+ opcode = aco_opcode::image_sample_l_o;
+ }
+ } else { /* no offset */
+ if (has_compare) {
+ opcode = aco_opcode::image_sample_c;
+ if (has_derivs)
+ opcode = aco_opcode::image_sample_c_d;
+ if (has_bias)
+ opcode = aco_opcode::image_sample_c_b;
+ if (level_zero)
+ opcode = aco_opcode::image_sample_c_lz;
+ if (has_lod)
+ opcode = aco_opcode::image_sample_c_l;
+ } else {
+ opcode = aco_opcode::image_sample;
+ if (has_derivs)
+ opcode = aco_opcode::image_sample_d;
+ if (has_bias)
+ opcode = aco_opcode::image_sample_b;
+ if (level_zero)
+ opcode = aco_opcode::image_sample_lz;
+ if (has_lod)
+ opcode = aco_opcode::image_sample_l;
+ }
+ }
+
+ if (instr->op == nir_texop_tg4) {
+ if (has_offset) {
+ opcode = aco_opcode::image_gather4_lz_o;
+ if (has_compare)
+ opcode = aco_opcode::image_gather4_c_lz_o;
+ } else {
+ opcode = aco_opcode::image_gather4_lz;
+ if (has_compare)
+ opcode = aco_opcode::image_gather4_c_lz;
+ }
+ } else if (instr->op == nir_texop_lod) {
+ opcode = aco_opcode::image_get_lod;
+ }
+
+ tex.reset(create_instruction<MIMG_instruction>(opcode, Format::MIMG, 3, 1));
+ tex->operands[0] = arg;
+ tex->operands[1] = Operand(resource);
+ tex->operands[2] = Operand(sampler);
+ tex->dmask = dmask;
+ tex->da = da;
+ tex->definitions[0] = Definition(tmp_dst);
+ tex->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(tex));
+
+ if (tg4_integer_cube_workaround) {
+ assert(tmp_dst.id() != dst.id());
+ assert(tmp_dst.size() == dst.size() && dst.size() == 4);
+
+ emit_split_vector(ctx, tmp_dst, tmp_dst.size());
+ Temp val[4];
+ for (unsigned i = 0; i < dst.size(); i++) {
+ val[i] = emit_extract_vector(ctx, tmp_dst, i, v1);
+ Temp cvt_val;
+ if (stype == GLSL_TYPE_UINT)
+ cvt_val = bld.vop1(aco_opcode::v_cvt_u32_f32, bld.def(v1), val[i]);
+ else
+ cvt_val = bld.vop1(aco_opcode::v_cvt_i32_f32, bld.def(v1), val[i]);
+ val[i] = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1), val[i], cvt_val, tg4_compare_cube_wa64);
+ }
+ Temp tmp = dst.regClass() == v4 ? dst : bld.tmp(v4);
+ tmp_dst = bld.pseudo(aco_opcode::p_create_vector, Definition(tmp),
+ val[0], val[1], val[2], val[3]);
+ }
+ unsigned mask = instr->op == nir_texop_tg4 ? 0xF : dmask;
+ expand_vector(ctx, tmp_dst, dst, instr->dest.ssa.num_components, mask);
+
+}
+
+
+Operand get_phi_operand(isel_context *ctx, nir_ssa_def *ssa)
+{
+ Temp tmp = get_ssa_temp(ctx, ssa);
+ if (ssa->parent_instr->type == nir_instr_type_ssa_undef)
+ return Operand(tmp.regClass());
+ else
+ return Operand(tmp);
+}
+
+void visit_phi(isel_context *ctx, nir_phi_instr *instr)
+{
+ aco_ptr<Pseudo_instruction> phi;
+ unsigned num_src = exec_list_length(&instr->srcs);
+ Temp dst = get_ssa_temp(ctx, &instr->dest.ssa);
+
+ aco_opcode opcode = !dst.is_linear() || ctx->divergent_vals[instr->dest.ssa.index] ? aco_opcode::p_phi : aco_opcode::p_linear_phi;
+
+ std::map<unsigned, nir_ssa_def*> phi_src;
+ bool all_undef = true;
+ nir_foreach_phi_src(src, instr) {
+ phi_src[src->pred->index] = src->src.ssa;
+ if (src->src.ssa->parent_instr->type != nir_instr_type_ssa_undef)
+ all_undef = false;
+ }
+ if (all_undef) {
+ Builder bld(ctx->program, ctx->block);
+ if (dst.regClass() == s1) {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(dst), Operand(0u));
+ } else if (dst.regClass() == v1) {
+ bld.vop1(aco_opcode::v_mov_b32, Definition(dst), Operand(0u));
+ } else {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size(), 1)};
+ for (unsigned i = 0; i < dst.size(); i++)
+ vec->operands[i] = Operand(0u);
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+ return;
+ }
+
+ /* try to scalarize vector phis */
+ if (dst.size() > 1) {
+ // TODO: scalarize linear phis on divergent ifs
+ bool can_scalarize = (opcode == aco_opcode::p_phi || !(ctx->block->kind & block_kind_merge));
+ std::array<Temp, 4> new_vec;
+ for (std::pair<const unsigned, nir_ssa_def*>& pair : phi_src) {
+ Operand src = get_phi_operand(ctx, pair.second);
+ if (src.isTemp() && ctx->allocated_vec.find(src.tempId()) == ctx->allocated_vec.end()) {
+ can_scalarize = false;
+ break;
+ }
+ }
+ if (can_scalarize) {
+ unsigned num_components = instr->dest.ssa.num_components;
+ assert(dst.size() % num_components == 0);
+ RegClass rc = RegClass(dst.type(), dst.size() / num_components);
+
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, num_components, 1)};
+ for (unsigned k = 0; k < num_components; k++) {
+ phi.reset(create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, num_src, 1));
+ std::map<unsigned, nir_ssa_def*>::iterator it = phi_src.begin();
+ for (unsigned i = 0; i < num_src; i++) {
+ Operand src = get_phi_operand(ctx, it->second);
+ phi->operands[i] = src.isTemp() ? Operand(ctx->allocated_vec[src.tempId()][k]) : Operand(rc);
+ ++it;
+ }
+ Temp phi_dst = {ctx->program->allocateId(), rc};
+ phi->definitions[0] = Definition(phi_dst);
+ ctx->block->instructions.emplace(ctx->block->instructions.begin(), std::move(phi));
+ new_vec[k] = phi_dst;
+ vec->operands[k] = Operand(phi_dst);
+ }
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ ctx->allocated_vec.emplace(dst.id(), new_vec);
+ return;
+ }
+ }
+
+ unsigned extra_src = 0;
+ if (opcode == aco_opcode::p_linear_phi && (ctx->block->kind & block_kind_loop_exit) &&
+ ctx->program->blocks[ctx->block->index-2].kind & block_kind_continue_or_break) {
+ extra_src++;
+ }
+
+ phi.reset(create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, num_src + extra_src, 1));
+
+ /* if we have a linear phi on a divergent if, we know that one src is undef */
+ if (opcode == aco_opcode::p_linear_phi && ctx->block->kind & block_kind_merge) {
+ assert(extra_src == 0);
+ Block* block;
+ /* we place the phi either in the invert-block or in the current block */
+ if (phi_src.begin()->second->parent_instr->type != nir_instr_type_ssa_undef) {
+ assert((++phi_src.begin())->second->parent_instr->type == nir_instr_type_ssa_undef);
+ Block& linear_else = ctx->program->blocks[ctx->block->linear_preds[1]];
+ block = &ctx->program->blocks[linear_else.linear_preds[0]];
+ assert(block->kind & block_kind_invert);
+ phi->operands[0] = get_phi_operand(ctx, phi_src.begin()->second);
+ } else {
+ assert((++phi_src.begin())->second->parent_instr->type != nir_instr_type_ssa_undef);
+ block = ctx->block;
+ phi->operands[0] = get_phi_operand(ctx, (++phi_src.begin())->second);
+ }
+ phi->operands[1] = Operand(dst.regClass());
+ phi->definitions[0] = Definition(dst);
+ block->instructions.emplace(block->instructions.begin(), std::move(phi));
+ return;
+ }
+
+ std::map<unsigned, nir_ssa_def*>::iterator it = phi_src.begin();
+ for (unsigned i = 0; i < num_src; i++) {
+ phi->operands[i] = get_phi_operand(ctx, it->second);
+ ++it;
+ }
+ for (unsigned i = 0; i < extra_src; i++)
+ phi->operands[num_src + i] = Operand(dst.regClass());
+ phi->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace(ctx->block->instructions.begin(), std::move(phi));
+}
+
+
+void visit_undef(isel_context *ctx, nir_ssa_undef_instr *instr)
+{
+ Temp dst = get_ssa_temp(ctx, &instr->def);
+
+ assert(dst.type() == RegType::sgpr);
+
+ if (dst.size() == 1) {
+ Builder(ctx->program, ctx->block).copy(Definition(dst), Operand(0u));
+ } else {
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, dst.size(), 1)};
+ for (unsigned i = 0; i < dst.size(); i++)
+ vec->operands[i] = Operand(0u);
+ vec->definitions[0] = Definition(dst);
+ ctx->block->instructions.emplace_back(std::move(vec));
+ }
+}
+
+void visit_jump(isel_context *ctx, nir_jump_instr *instr)
+{
+ Builder bld(ctx->program, ctx->block);
+ Block *logical_target;
+ append_logical_end(ctx->block);
+ unsigned idx = ctx->block->index;
+
+ switch (instr->type) {
+ case nir_jump_break:
+ logical_target = ctx->cf_info.parent_loop.exit;
+ add_logical_edge(idx, logical_target);
+ ctx->block->kind |= block_kind_break;
+
+ if (!ctx->cf_info.parent_if.is_divergent &&
+ !ctx->cf_info.parent_loop.has_divergent_continue) {
+ /* uniform break - directly jump out of the loop */
+ ctx->block->kind |= block_kind_uniform;
+ ctx->cf_info.has_branch = true;
+ bld.branch(aco_opcode::p_branch);
+ add_linear_edge(idx, logical_target);
+ return;
+ }
+ ctx->cf_info.parent_loop.has_divergent_branch = true;
+ break;
+ case nir_jump_continue:
+ logical_target = &ctx->program->blocks[ctx->cf_info.parent_loop.header_idx];
+ add_logical_edge(idx, logical_target);
+ ctx->block->kind |= block_kind_continue;
+
+ if (ctx->cf_info.parent_if.is_divergent) {
+ /* for potential uniform breaks after this continue,
+ we must ensure that they are handled correctly */
+ ctx->cf_info.parent_loop.has_divergent_continue = true;
+ ctx->cf_info.parent_loop.has_divergent_branch = true;
+ } else {
+ /* uniform continue - directly jump to the loop header */
+ ctx->block->kind |= block_kind_uniform;
+ ctx->cf_info.has_branch = true;
+ bld.branch(aco_opcode::p_branch);
+ add_linear_edge(idx, logical_target);
+ return;
+ }
+ break;
+ default:
+ fprintf(stderr, "Unknown NIR jump instr: ");
+ nir_print_instr(&instr->instr, stderr);
+ fprintf(stderr, "\n");
+ abort();
+ }
+
+ /* remove critical edges from linear CFG */
+ bld.branch(aco_opcode::p_branch);
+ Block* break_block = ctx->program->create_and_insert_block();
+ break_block->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ break_block->kind |= block_kind_uniform;
+ add_linear_edge(idx, break_block);
+ /* the loop_header pointer might be invalidated by this point */
+ if (instr->type == nir_jump_continue)
+ logical_target = &ctx->program->blocks[ctx->cf_info.parent_loop.header_idx];
+ add_linear_edge(break_block->index, logical_target);
+ bld.reset(break_block);
+ bld.branch(aco_opcode::p_branch);
+
+ Block* continue_block = ctx->program->create_and_insert_block();
+ continue_block->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_linear_edge(idx, continue_block);
+ append_logical_start(continue_block);
+ ctx->block = continue_block;
+ return;
+}
+
+void visit_block(isel_context *ctx, nir_block *block)
+{
+ nir_foreach_instr(instr, block) {
+ switch (instr->type) {
+ case nir_instr_type_alu:
+ visit_alu_instr(ctx, nir_instr_as_alu(instr));
+ break;
+ case nir_instr_type_load_const:
+ visit_load_const(ctx, nir_instr_as_load_const(instr));
+ break;
+ case nir_instr_type_intrinsic:
+ visit_intrinsic(ctx, nir_instr_as_intrinsic(instr));
+ break;
+ case nir_instr_type_tex:
+ visit_tex(ctx, nir_instr_as_tex(instr));
+ break;
+ case nir_instr_type_phi:
+ visit_phi(ctx, nir_instr_as_phi(instr));
+ break;
+ case nir_instr_type_ssa_undef:
+ visit_undef(ctx, nir_instr_as_ssa_undef(instr));
+ break;
+ case nir_instr_type_deref:
+ break;
+ case nir_instr_type_jump:
+ visit_jump(ctx, nir_instr_as_jump(instr));
+ break;
+ default:
+ fprintf(stderr, "Unknown NIR instr type: ");
+ nir_print_instr(instr, stderr);
+ fprintf(stderr, "\n");
+ //abort();
+ }
+ }
+}
+
+
+
+static void visit_loop(isel_context *ctx, nir_loop *loop)
+{
+ append_logical_end(ctx->block);
+ ctx->block->kind |= block_kind_loop_preheader | block_kind_uniform;
+ Builder bld(ctx->program, ctx->block);
+ bld.branch(aco_opcode::p_branch);
+ unsigned loop_preheader_idx = ctx->block->index;
+
+ Block loop_exit = Block();
+ loop_exit.loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ loop_exit.kind |= (block_kind_loop_exit | (ctx->block->kind & block_kind_top_level));
+
+ Block* loop_header = ctx->program->create_and_insert_block();
+ loop_header->loop_nest_depth = ctx->cf_info.loop_nest_depth + 1;
+ loop_header->kind |= block_kind_loop_header;
+ add_edge(loop_preheader_idx, loop_header);
+ ctx->block = loop_header;
+
+ /* emit loop body */
+ unsigned loop_header_idx = loop_header->index;
+ loop_info_RAII loop_raii(ctx, loop_header_idx, &loop_exit);
+ append_logical_start(ctx->block);
+ visit_cf_list(ctx, &loop->body);
+
+ //TODO: what if a loop ends with a unconditional or uniformly branched continue and this branch is never taken?
+ if (!ctx->cf_info.has_branch) {
+ append_logical_end(ctx->block);
+ if (ctx->cf_info.exec_potentially_empty) {
+ /* Discards can result in code running with an empty exec mask.
+ * This would result in divergent breaks not ever being taken. As a
+ * workaround, break the loop when the loop mask is empty instead of
+ * always continuing. */
+ ctx->block->kind |= (block_kind_continue_or_break | block_kind_uniform);
+
+ /* create "loop_almost_exit" to avoid critical edges */
+ unsigned block_idx = ctx->block->index;
+ Block *loop_almost_exit = ctx->program->create_and_insert_block();
+ loop_almost_exit->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ loop_almost_exit->kind = block_kind_uniform;
+ bld.reset(loop_almost_exit);
+ bld.branch(aco_opcode::p_branch);
+
+ add_linear_edge(block_idx, loop_almost_exit);
+ add_linear_edge(loop_almost_exit->index, &loop_exit);
+
+ ctx->block = &ctx->program->blocks[block_idx];
+ } else {
+ ctx->block->kind |= (block_kind_continue | block_kind_uniform);
+ }
+ if (!ctx->cf_info.parent_loop.has_divergent_branch)
+ add_edge(ctx->block->index, &ctx->program->blocks[loop_header_idx]);
+ else
+ add_linear_edge(ctx->block->index, &ctx->program->blocks[loop_header_idx]);
+ bld.reset(ctx->block);
+ bld.branch(aco_opcode::p_branch);
+ }
+
+ /* fixup phis in loop header from unreachable blocks */
+ if (ctx->cf_info.has_branch || ctx->cf_info.parent_loop.has_divergent_branch) {
+ bool linear = ctx->cf_info.has_branch;
+ bool logical = ctx->cf_info.has_branch || ctx->cf_info.parent_loop.has_divergent_branch;
+ for (aco_ptr<Instruction>& instr : ctx->program->blocks[loop_header_idx].instructions) {
+ if ((logical && instr->opcode == aco_opcode::p_phi) ||
+ (linear && instr->opcode == aco_opcode::p_linear_phi)) {
+ /* the last operand should be the one that needs to be removed */
+ instr->operands.pop_back();
+ } else if (!is_phi(instr)) {
+ break;
+ }
+ }
+ }
+
+ ctx->cf_info.has_branch = false;
+
+ // TODO: if the loop has not a single exit, we must add one °°
+ /* emit loop successor block */
+ ctx->block = ctx->program->insert_block(std::move(loop_exit));
+ append_logical_start(ctx->block);
+
+ #if 0
+ // TODO: check if it is beneficial to not branch on continues
+ /* trim linear phis in loop header */
+ for (auto&& instr : loop_entry->instructions) {
+ if (instr->opcode == aco_opcode::p_linear_phi) {
+ aco_ptr<Pseudo_instruction> new_phi{create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, loop_entry->linear_predecessors.size(), 1)};
+ new_phi->definitions[0] = instr->definitions[0];
+ for (unsigned i = 0; i < new_phi->operands.size(); i++)
+ new_phi->operands[i] = instr->operands[i];
+ /* check that the remaining operands are all the same */
+ for (unsigned i = new_phi->operands.size(); i < instr->operands.size(); i++)
+ assert(instr->operands[i].tempId() == instr->operands.back().tempId());
+ instr.swap(new_phi);
+ } else if (instr->opcode == aco_opcode::p_phi) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ #endif
+}
+
+static void begin_divergent_if_then(isel_context *ctx, if_context *ic, Temp cond)
+{
+ ic->cond = cond;
+
+ append_logical_end(ctx->block);
+ ctx->block->kind |= block_kind_branch;
+
+ /* branch to linear then block */
+ assert(cond.regClass() == s2);
+ aco_ptr<Pseudo_branch_instruction> branch;
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_cbranch_z, Format::PSEUDO_BRANCH, 1, 0));
+ branch->operands[0] = Operand(cond);
+ ctx->block->instructions.push_back(std::move(branch));
+
+ ic->BB_if_idx = ctx->block->index;
+ ic->BB_invert = Block();
+ ic->BB_invert.loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ /* Invert blocks are intentionally not marked as top level because they
+ * are not part of the logical cfg. */
+ ic->BB_invert.kind |= block_kind_invert;
+ ic->BB_endif = Block();
+ ic->BB_endif.loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ ic->BB_endif.kind |= (block_kind_merge | (ctx->block->kind & block_kind_top_level));
+
+ ic->exec_potentially_empty_old = ctx->cf_info.exec_potentially_empty;
+ ic->divergent_old = ctx->cf_info.parent_if.is_divergent;
+ ctx->cf_info.parent_if.is_divergent = true;
+ ctx->cf_info.exec_potentially_empty = false; /* divergent branches use cbranch_execz */
+
+ /** emit logical then block */
+ Block* BB_then_logical = ctx->program->create_and_insert_block();
+ BB_then_logical->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_edge(ic->BB_if_idx, BB_then_logical);
+ ctx->block = BB_then_logical;
+ append_logical_start(BB_then_logical);
+}
+
+static void begin_divergent_if_else(isel_context *ctx, if_context *ic)
+{
+ Block *BB_then_logical = ctx->block;
+ append_logical_end(BB_then_logical);
+ /* branch from logical then block to invert block */
+ aco_ptr<Pseudo_branch_instruction> branch;
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_then_logical->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_then_logical->index, &ic->BB_invert);
+ if (!ctx->cf_info.parent_loop.has_divergent_branch)
+ add_logical_edge(BB_then_logical->index, &ic->BB_endif);
+ BB_then_logical->kind |= block_kind_uniform;
+ assert(!ctx->cf_info.has_branch);
+ ic->then_branch_divergent = ctx->cf_info.parent_loop.has_divergent_branch;
+ ctx->cf_info.parent_loop.has_divergent_branch = false;
+
+ /** emit linear then block */
+ Block* BB_then_linear = ctx->program->create_and_insert_block();
+ BB_then_linear->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ BB_then_linear->kind |= block_kind_uniform;
+ add_linear_edge(ic->BB_if_idx, BB_then_linear);
+ /* branch from linear then block to invert block */
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_then_linear->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_then_linear->index, &ic->BB_invert);
+
+ /** emit invert merge block */
+ ctx->block = ctx->program->insert_block(std::move(ic->BB_invert));
+ ic->invert_idx = ctx->block->index;
+
+ /* branch to linear else block (skip else) */
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_cbranch_nz, Format::PSEUDO_BRANCH, 1, 0));
+ branch->operands[0] = Operand(ic->cond);
+ ctx->block->instructions.push_back(std::move(branch));
+
+ ic->exec_potentially_empty_old |= ctx->cf_info.exec_potentially_empty;
+ ctx->cf_info.exec_potentially_empty = false; /* divergent branches use cbranch_execz */
+
+ /** emit logical else block */
+ Block* BB_else_logical = ctx->program->create_and_insert_block();
+ BB_else_logical->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_logical_edge(ic->BB_if_idx, BB_else_logical);
+ add_linear_edge(ic->invert_idx, BB_else_logical);
+ ctx->block = BB_else_logical;
+ append_logical_start(BB_else_logical);
+}
+
+static void end_divergent_if(isel_context *ctx, if_context *ic)
+{
+ Block *BB_else_logical = ctx->block;
+ append_logical_end(BB_else_logical);
+
+ /* branch from logical else block to endif block */
+ aco_ptr<Pseudo_branch_instruction> branch;
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_else_logical->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_else_logical->index, &ic->BB_endif);
+ if (!ctx->cf_info.parent_loop.has_divergent_branch)
+ add_logical_edge(BB_else_logical->index, &ic->BB_endif);
+ BB_else_logical->kind |= block_kind_uniform;
+
+ assert(!ctx->cf_info.has_branch);
+ ctx->cf_info.parent_loop.has_divergent_branch &= ic->then_branch_divergent;
+
+
+ /** emit linear else block */
+ Block* BB_else_linear = ctx->program->create_and_insert_block();
+ BB_else_linear->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ BB_else_linear->kind |= block_kind_uniform;
+ add_linear_edge(ic->invert_idx, BB_else_linear);
+
+ /* branch from linear else block to endif block */
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_else_linear->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_else_linear->index, &ic->BB_endif);
+
+
+ /** emit endif merge block */
+ ctx->block = ctx->program->insert_block(std::move(ic->BB_endif));
+ append_logical_start(ctx->block);
+
+
+ ctx->cf_info.parent_if.is_divergent = ic->divergent_old;
+ ctx->cf_info.exec_potentially_empty |= ic->exec_potentially_empty_old;
+ /* uniform control flow never has an empty exec-mask */
+ if (!ctx->cf_info.loop_nest_depth && !ctx->cf_info.parent_if.is_divergent)
+ ctx->cf_info.exec_potentially_empty = false;
+}
+
+static void visit_if(isel_context *ctx, nir_if *if_stmt)
+{
+ Temp cond = get_ssa_temp(ctx, if_stmt->condition.ssa);
+ Builder bld(ctx->program, ctx->block);
+ aco_ptr<Pseudo_branch_instruction> branch;
+
+ if (!ctx->divergent_vals[if_stmt->condition.ssa->index]) { /* uniform condition */
+ /**
+ * Uniform conditionals are represented in the following way*) :
+ *
+ * The linear and logical CFG:
+ * BB_IF
+ * / \
+ * BB_THEN (logical) BB_ELSE (logical)
+ * \ /
+ * BB_ENDIF
+ *
+ * *) Exceptions may be due to break and continue statements within loops
+ * If a break/continue happens within uniform control flow, it branches
+ * to the loop exit/entry block. Otherwise, it branches to the next
+ * merge block.
+ **/
+ append_logical_end(ctx->block);
+ ctx->block->kind |= block_kind_uniform;
+
+ /* emit branch */
+ if (cond.regClass() == s2) {
+ // TODO: in a post-RA optimizer, we could check if the condition is in VCC and omit this instruction
+ cond = as_uniform_bool(ctx, cond);
+ }
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_cbranch_z, Format::PSEUDO_BRANCH, 1, 0));
+ branch->operands[0] = Operand(cond);
+ branch->operands[0].setFixed(scc);
+ ctx->block->instructions.emplace_back(std::move(branch));
+
+ unsigned BB_if_idx = ctx->block->index;
+ Block BB_endif = Block();
+ BB_endif.loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ BB_endif.kind |= ctx->block->kind & block_kind_top_level;
+
+ /** emit then block */
+ Block* BB_then = ctx->program->create_and_insert_block();
+ BB_then->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_edge(BB_if_idx, BB_then);
+ append_logical_start(BB_then);
+ ctx->block = BB_then;
+ visit_cf_list(ctx, &if_stmt->then_list);
+ BB_then = ctx->block;
+ bool then_branch = ctx->cf_info.has_branch;
+ bool then_branch_divergent = ctx->cf_info.parent_loop.has_divergent_branch;
+
+ if (!then_branch) {
+ append_logical_end(BB_then);
+ /* branch from then block to endif block */
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_then->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_then->index, &BB_endif);
+ if (!then_branch_divergent)
+ add_logical_edge(BB_then->index, &BB_endif);
+ BB_then->kind |= block_kind_uniform;
+ }
+
+ ctx->cf_info.has_branch = false;
+ ctx->cf_info.parent_loop.has_divergent_branch = false;
+
+ /** emit else block */
+ Block* BB_else = ctx->program->create_and_insert_block();
+ BB_else->loop_nest_depth = ctx->cf_info.loop_nest_depth;
+ add_edge(BB_if_idx, BB_else);
+ append_logical_start(BB_else);
+ ctx->block = BB_else;
+ visit_cf_list(ctx, &if_stmt->else_list);
+ BB_else = ctx->block;
+
+ if (!ctx->cf_info.has_branch) {
+ append_logical_end(BB_else);
+ /* branch from then block to endif block */
+ branch.reset(create_instruction<Pseudo_branch_instruction>(aco_opcode::p_branch, Format::PSEUDO_BRANCH, 0, 0));
+ BB_else->instructions.emplace_back(std::move(branch));
+ add_linear_edge(BB_else->index, &BB_endif);
+ if (!ctx->cf_info.parent_loop.has_divergent_branch)
+ add_logical_edge(BB_else->index, &BB_endif);
+ BB_else->kind |= block_kind_uniform;
+ }
+
+ ctx->cf_info.has_branch &= then_branch;
+ ctx->cf_info.parent_loop.has_divergent_branch &= then_branch_divergent;
+
+ /** emit endif merge block */
+ if (!ctx->cf_info.has_branch) {
+ ctx->block = ctx->program->insert_block(std::move(BB_endif));
+ append_logical_start(ctx->block);
+ }
+ } else { /* non-uniform condition */
+ /**
+ * To maintain a logical and linear CFG without critical edges,
+ * non-uniform conditionals are represented in the following way*) :
+ *
+ * The linear CFG:
+ * BB_IF
+ * / \
+ * BB_THEN (logical) BB_THEN (linear)
+ * \ /
+ * BB_INVERT (linear)
+ * / \
+ * BB_ELSE (logical) BB_ELSE (linear)
+ * \ /
+ * BB_ENDIF
+ *
+ * The logical CFG:
+ * BB_IF
+ * / \
+ * BB_THEN (logical) BB_ELSE (logical)
+ * \ /
+ * BB_ENDIF
+ *
+ * *) Exceptions may be due to break and continue statements within loops
+ **/
+
+ if_context ic;
+
+ begin_divergent_if_then(ctx, &ic, cond);
+ visit_cf_list(ctx, &if_stmt->then_list);
+
+ begin_divergent_if_else(ctx, &ic);
+ visit_cf_list(ctx, &if_stmt->else_list);
+
+ end_divergent_if(ctx, &ic);
+ }
+}
+
+static void visit_cf_list(isel_context *ctx,
+ struct exec_list *list)
+{
+ foreach_list_typed(nir_cf_node, node, node, list) {
+ switch (node->type) {
+ case nir_cf_node_block:
+ visit_block(ctx, nir_cf_node_as_block(node));
+ break;
+ case nir_cf_node_if:
+ visit_if(ctx, nir_cf_node_as_if(node));
+ break;
+ case nir_cf_node_loop:
+ visit_loop(ctx, nir_cf_node_as_loop(node));
+ break;
+ default:
+ unreachable("unimplemented cf list type");
+ }
+ }
+}
+
+static void export_vs_varying(isel_context *ctx, int slot, bool is_pos, int *next_pos)
+{
+ int offset = ctx->program->info->vs.outinfo.vs_output_param_offset[slot];
+ uint64_t mask = ctx->vs_output.mask[slot];
+ if (!is_pos && !mask)
+ return;
+ if (!is_pos && offset == AC_EXP_PARAM_UNDEFINED)
+ return;
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ exp->enabled_mask = mask;
+ for (unsigned i = 0; i < 4; ++i) {
+ if (mask & (1 << i))
+ exp->operands[i] = Operand(ctx->vs_output.outputs[slot][i]);
+ else
+ exp->operands[i] = Operand(v1);
+ }
+ exp->valid_mask = false;
+ exp->done = false;
+ exp->compressed = false;
+ if (is_pos)
+ exp->dest = V_008DFC_SQ_EXP_POS + (*next_pos)++;
+ else
+ exp->dest = V_008DFC_SQ_EXP_PARAM + offset;
+ ctx->block->instructions.emplace_back(std::move(exp));
+}
+
+static void export_vs_psiz_layer_viewport(isel_context *ctx, int *next_pos)
+{
+ aco_ptr<Export_instruction> exp{create_instruction<Export_instruction>(aco_opcode::exp, Format::EXP, 4, 0)};
+ exp->enabled_mask = 0;
+ for (unsigned i = 0; i < 4; ++i)
+ exp->operands[i] = Operand(v1);
+ if (ctx->vs_output.mask[VARYING_SLOT_PSIZ]) {
+ exp->operands[0] = Operand(ctx->vs_output.outputs[VARYING_SLOT_PSIZ][0]);
+ exp->enabled_mask |= 0x1;
+ }
+ if (ctx->vs_output.mask[VARYING_SLOT_LAYER]) {
+ exp->operands[2] = Operand(ctx->vs_output.outputs[VARYING_SLOT_LAYER][0]);
+ exp->enabled_mask |= 0x4;
+ }
+ if (ctx->vs_output.mask[VARYING_SLOT_VIEWPORT]) {
+ if (ctx->options->chip_class < GFX9) {
+ exp->operands[3] = Operand(ctx->vs_output.outputs[VARYING_SLOT_VIEWPORT][0]);
+ exp->enabled_mask |= 0x8;
+ } else {
+ Builder bld(ctx->program, ctx->block);
+
+ Temp out = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(16u),
+ Operand(ctx->vs_output.outputs[VARYING_SLOT_VIEWPORT][0]));
+ if (exp->operands[2].isTemp())
+ out = bld.vop2(aco_opcode::v_or_b32, bld.def(v1), Operand(out), exp->operands[2]);
+
+ exp->operands[2] = Operand(out);
+ exp->enabled_mask |= 0x4;
+ }
+ }
+ exp->valid_mask = false;
+ exp->done = false;
+ exp->compressed = false;
+ exp->dest = V_008DFC_SQ_EXP_POS + (*next_pos)++;
+ ctx->block->instructions.emplace_back(std::move(exp));
+}
+
+static void create_vs_exports(isel_context *ctx)
+{
+ radv_vs_output_info *outinfo = &ctx->program->info->vs.outinfo;
+
+ if (outinfo->export_prim_id) {
+ ctx->vs_output.mask[VARYING_SLOT_PRIMITIVE_ID] |= 0x1;
+ ctx->vs_output.outputs[VARYING_SLOT_PRIMITIVE_ID][0] = ctx->vs_prim_id;
+ }
+
+ if (ctx->options->key.has_multiview_view_index) {
+ ctx->vs_output.mask[VARYING_SLOT_LAYER] |= 0x1;
+ ctx->vs_output.outputs[VARYING_SLOT_LAYER][0] = as_vgpr(ctx, ctx->view_index);
+ }
+
+ /* the order these position exports are created is important */
+ int next_pos = 0;
+ export_vs_varying(ctx, VARYING_SLOT_POS, true, &next_pos);
+ if (outinfo->writes_pointsize || outinfo->writes_layer || outinfo->writes_viewport_index) {
+ export_vs_psiz_layer_viewport(ctx, &next_pos);
+ }
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 0)
+ export_vs_varying(ctx, VARYING_SLOT_CLIP_DIST0, true, &next_pos);
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 4)
+ export_vs_varying(ctx, VARYING_SLOT_CLIP_DIST1, true, &next_pos);
+
+ if (ctx->options->key.vs_common_out.export_clip_dists) {
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 0)
+ export_vs_varying(ctx, VARYING_SLOT_CLIP_DIST0, false, &next_pos);
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 4)
+ export_vs_varying(ctx, VARYING_SLOT_CLIP_DIST1, false, &next_pos);
+ }
+
+ for (unsigned i = 0; i <= VARYING_SLOT_VAR31; ++i) {
+ if (i < VARYING_SLOT_VAR0 && i != VARYING_SLOT_LAYER &&
+ i != VARYING_SLOT_PRIMITIVE_ID)
+ continue;
+
+ export_vs_varying(ctx, i, false, NULL);
+ }
+}
+
+static void emit_stream_output(isel_context *ctx,
+ Temp const *so_buffers,
+ Temp const *so_write_offset,
+ const struct radv_stream_output *output)
+{
+ unsigned num_comps = util_bitcount(output->component_mask);
+ unsigned loc = output->location;
+ unsigned buf = output->buffer;
+ unsigned offset = output->offset;
+
+ assert(num_comps && num_comps <= 4);
+ if (!num_comps || num_comps > 4)
+ return;
+
+ unsigned start = ffs(output->component_mask) - 1;
+
+ Temp out[4];
+ bool all_undef = true;
+ assert(ctx->stage == vertex_vs);
+ for (unsigned i = 0; i < num_comps; i++) {
+ out[i] = ctx->vs_output.outputs[loc][start + i];
+ all_undef = all_undef && !out[i].id();
+ }
+ if (all_undef)
+ return;
+
+ Temp write_data = {ctx->program->allocateId(), RegClass(RegType::vgpr, num_comps)};
+ aco_ptr<Pseudo_instruction> vec{create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, num_comps, 1)};
+ for (unsigned i = 0; i < num_comps; ++i)
+ vec->operands[i] = (ctx->vs_output.mask[loc] & 1 << i) ? Operand(out[i]) : Operand(0u);
+ vec->definitions[0] = Definition(write_data);
+ ctx->block->instructions.emplace_back(std::move(vec));
+
+ aco_opcode opcode;
+ switch (num_comps) {
+ case 1:
+ opcode = aco_opcode::buffer_store_dword;
+ break;
+ case 2:
+ opcode = aco_opcode::buffer_store_dwordx2;
+ break;
+ case 3:
+ opcode = aco_opcode::buffer_store_dwordx3;
+ break;
+ case 4:
+ opcode = aco_opcode::buffer_store_dwordx4;
+ break;
+ }
+
+ aco_ptr<MUBUF_instruction> store{create_instruction<MUBUF_instruction>(opcode, Format::MUBUF, 4, 0)};
+ store->operands[0] = Operand(so_write_offset[buf]);
+ store->operands[1] = Operand(so_buffers[buf]);
+ store->operands[2] = Operand((uint32_t) 0);
+ store->operands[3] = Operand(write_data);
+ if (offset > 4095) {
+ /* Don't think this can happen in RADV, but maybe GL? It's easy to do this anyway. */
+ Builder bld(ctx->program, ctx->block);
+ store->operands[0] = bld.vadd32(bld.def(v1), Operand(offset), Operand(so_write_offset[buf]));
+ } else {
+ store->offset = offset;
+ }
+ store->offen = true;
+ store->glc = true;
+ store->slc = true;
+ store->can_reorder = true;
+ ctx->block->instructions.emplace_back(std::move(store));
+}
+
+static void emit_streamout(isel_context *ctx, unsigned stream)
+{
+ Builder bld(ctx->program, ctx->block);
+
+ Temp so_buffers[4];
+ Temp buf_ptr = convert_pointer_to_64_bit(ctx, ctx->streamout_buffers);
+ for (unsigned i = 0; i < 4; i++) {
+ unsigned stride = ctx->program->info->so.strides[i];
+ if (!stride)
+ continue;
+
+ so_buffers[i] = bld.smem(aco_opcode::s_load_dwordx4, bld.def(s4), buf_ptr, Operand(i * 16u));
+ }
+
+ Temp so_vtx_count = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc),
+ ctx->streamout_config, Operand(0x70010u));
+
+ Temp tid = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), Operand((uint32_t) -1),
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), Operand((uint32_t) -1), Operand(0u)));
+
+ Temp can_emit = bld.vopc(aco_opcode::v_cmp_gt_i32, bld.def(s2), so_vtx_count, tid);
+
+ if_context ic;
+ begin_divergent_if_then(ctx, &ic, can_emit);
+
+ bld.reset(ctx->block);
+
+ Temp so_write_index = bld.vadd32(bld.def(v1), ctx->streamout_write_idx, tid);
+
+ Temp so_write_offset[4];
+
+ for (unsigned i = 0; i < 4; i++) {
+ unsigned stride = ctx->program->info->so.strides[i];
+ if (!stride)
+ continue;
+
+ if (stride == 1) {
+ Temp offset = bld.sop2(aco_opcode::s_add_i32, bld.def(s1), bld.def(s1, scc),
+ ctx->streamout_write_idx, ctx->streamout_offset[i]);
+ Temp new_offset = bld.vadd32(bld.def(v1), offset, tid);
+
+ so_write_offset[i] = bld.vop2(aco_opcode::v_lshlrev_b32, bld.def(v1), Operand(2u), new_offset);
+ } else {
+ Temp offset = bld.v_mul_imm(bld.def(v1), so_write_index, stride * 4u);
+ Temp offset2 = bld.sop2(aco_opcode::s_mul_i32, bld.def(s1), Operand(4u), ctx->streamout_offset[i]);
+ so_write_offset[i] = bld.vadd32(bld.def(v1), offset, offset2);
+ }
+ }
+
+ for (unsigned i = 0; i < ctx->program->info->so.num_outputs; i++) {
+ struct radv_stream_output *output =
+ &ctx->program->info->so.outputs[i];
+ if (stream != output->stream)
+ continue;
+
+ emit_stream_output(ctx, so_buffers, so_write_offset, output);
+ }
+
+ begin_divergent_if_else(ctx, &ic);
+ end_divergent_if(ctx, &ic);
+}
+
+} /* end namespace */
+
+void handle_bc_optimize(isel_context *ctx)
+{
+ /* needed when SPI_PS_IN_CONTROL.BC_OPTIMIZE_DISABLE is set to 0 */
+ Builder bld(ctx->program, ctx->block);
+ uint32_t spi_ps_input_ena = ctx->program->config->spi_ps_input_ena;
+ bool uses_center = G_0286CC_PERSP_CENTER_ENA(spi_ps_input_ena) || G_0286CC_LINEAR_CENTER_ENA(spi_ps_input_ena);
+ bool uses_centroid = G_0286CC_PERSP_CENTROID_ENA(spi_ps_input_ena) || G_0286CC_LINEAR_CENTROID_ENA(spi_ps_input_ena);
+ if (uses_center && uses_centroid) {
+ Temp sel = bld.vopc_e64(aco_opcode::v_cmp_lt_i32, bld.hint_vcc(bld.def(s2)), ctx->prim_mask, Operand(0u));
+
+ if (G_0286CC_PERSP_CENTROID_ENA(spi_ps_input_ena)) {
+ for (unsigned i = 0; i < 2; i++) {
+ Temp new_coord = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1),
+ ctx->fs_inputs[fs_input::persp_centroid_p1 + i],
+ ctx->fs_inputs[fs_input::persp_center_p1 + i],
+ sel);
+ ctx->fs_inputs[fs_input::persp_centroid_p1 + i] = new_coord;
+ }
+ }
+
+ if (G_0286CC_LINEAR_CENTROID_ENA(spi_ps_input_ena)) {
+ for (unsigned i = 0; i < 2; i++) {
+ Temp new_coord = bld.vop2(aco_opcode::v_cndmask_b32, bld.def(v1),
+ ctx->fs_inputs[fs_input::linear_centroid_p1 + i],
+ ctx->fs_inputs[fs_input::linear_center_p1 + i],
+ sel);
+ ctx->fs_inputs[fs_input::linear_centroid_p1 + i] = new_coord;
+ }
+ }
+ }
+}
+
+void select_program(Program *program,
+ unsigned shader_count,
+ struct nir_shader *const *shaders,
+ ac_shader_config* config,
+ struct radv_shader_info *info,
+ struct radv_nir_compiler_options *options)
+{
+ isel_context ctx = setup_isel_context(program, shader_count, shaders, config, info, options);
+
+ for (unsigned i = 0; i < shader_count; i++) {
+ nir_shader *nir = shaders[i];
+ init_context(&ctx, nir);
+
+ if (!i) {
+ add_startpgm(&ctx); /* needs to be after init_context() for FS */
+ append_logical_start(ctx.block);
+ }
+
+ if_context ic;
+ if (shader_count >= 2) {
+ Builder bld(ctx.program, ctx.block);
+ Temp count = bld.sop2(aco_opcode::s_bfe_u32, bld.def(s1), bld.def(s1, scc), ctx.merged_wave_info, Operand((8u << 16) | (i * 8u)));
+ Temp thread_id = bld.vop3(aco_opcode::v_mbcnt_hi_u32_b32, bld.def(v1), Operand((uint32_t) -1),
+ bld.vop3(aco_opcode::v_mbcnt_lo_u32_b32, bld.def(v1), Operand((uint32_t) -1), Operand(0u)));
+ Temp cond = bld.vopc(aco_opcode::v_cmp_gt_u32, bld.hint_vcc(bld.def(s2)), count, thread_id);
+
+ begin_divergent_if_then(&ctx, &ic, cond);
+ }
+
+ if (i) {
+ Builder bld(ctx.program, ctx.block);
+ bld.barrier(aco_opcode::p_memory_barrier_shared); //TODO: different barriers are needed for different stages
+ bld.sopp(aco_opcode::s_barrier);
+ }
+
+ if (ctx.stage == fragment_fs)
+ handle_bc_optimize(&ctx);
+
+ nir_function_impl *func = nir_shader_get_entrypoint(nir);
+ visit_cf_list(&ctx, &func->body);
+
+ if (ctx.program->info->so.num_outputs/*&& !ctx->is_gs_copy_shader */)
+ emit_streamout(&ctx, 0);
+
+ if (ctx.stage == vertex_vs)
+ create_vs_exports(&ctx);
+
+ if (shader_count >= 2) {
+ begin_divergent_if_else(&ctx, &ic);
+ end_divergent_if(&ctx, &ic);
+ }
+
+ ralloc_free(ctx.divergent_vals);
+ }
+
+ append_logical_end(ctx.block);
+ ctx.block->kind |= block_kind_uniform;
+ Builder bld(ctx.program, ctx.block);
+ if (ctx.program->wb_smem_l1_on_end)
+ bld.smem(aco_opcode::s_dcache_wb, false);
+ bld.sopp(aco_opcode::s_endpgm);
+
+ /* cleanup CFG */
+ for (Block& BB : program->blocks) {
+ for (unsigned idx : BB.linear_preds)
+ program->blocks[idx].linear_succs.emplace_back(BB.index);
+ for (unsigned idx : BB.logical_preds)
+ program->blocks[idx].logical_succs.emplace_back(BB.index);
+ }
+}
+}
diff --git a/src/amd/compiler/aco_instruction_selection_setup.cpp b/src/amd/compiler/aco_instruction_selection_setup.cpp
new file mode 100644
index 00000000000..6c4c408e659
--- /dev/null
+++ b/src/amd/compiler/aco_instruction_selection_setup.cpp
@@ -0,0 +1,1366 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <unordered_map>
+#include "aco_ir.h"
+#include "nir.h"
+#include "vulkan/radv_shader.h"
+#include "vulkan/radv_descriptor_set.h"
+#include "sid.h"
+#include "ac_exp_param.h"
+
+#include "util/u_math.h"
+
+#define MAX_INLINE_PUSH_CONSTS 8
+
+namespace aco {
+
+enum fs_input {
+ persp_sample_p1,
+ persp_sample_p2,
+ persp_center_p1,
+ persp_center_p2,
+ persp_centroid_p1,
+ persp_centroid_p2,
+ persp_pull_model,
+ linear_sample_p1,
+ linear_sample_p2,
+ linear_center_p1,
+ linear_center_p2,
+ linear_centroid_p1,
+ linear_centroid_p2,
+ line_stipple,
+ frag_pos_0,
+ frag_pos_1,
+ frag_pos_2,
+ frag_pos_3,
+ front_face,
+ ancillary,
+ sample_coverage,
+ fixed_pt,
+ max_inputs,
+};
+
+struct vs_output_state {
+ uint8_t mask[VARYING_SLOT_VAR31 + 1];
+ Temp outputs[VARYING_SLOT_VAR31 + 1][4];
+};
+
+struct isel_context {
+ struct radv_nir_compiler_options *options;
+ Program *program;
+ nir_shader *shader;
+ uint32_t constant_data_offset;
+ Block *block;
+ bool *divergent_vals;
+ std::unique_ptr<Temp[]> allocated;
+ std::unordered_map<unsigned, std::array<Temp,4>> allocated_vec;
+ Stage stage; /* Stage */
+ struct {
+ bool has_branch;
+ uint16_t loop_nest_depth = 0;
+ struct {
+ unsigned header_idx;
+ Block* exit;
+ bool has_divergent_continue = false;
+ bool has_divergent_branch = false;
+ } parent_loop;
+ struct {
+ bool is_divergent = false;
+ } parent_if;
+ bool exec_potentially_empty = false;
+ } cf_info;
+
+ /* scratch */
+ bool scratch_enabled = false;
+ Temp private_segment_buffer = Temp(0, s2); /* also the part of the scratch descriptor on compute */
+ Temp scratch_offset = Temp(0, s1);
+
+ /* inputs common for merged stages */
+ Temp merged_wave_info = Temp(0, s1);
+
+ /* FS inputs */
+ bool fs_vgpr_args[fs_input::max_inputs];
+ Temp fs_inputs[fs_input::max_inputs];
+ Temp prim_mask = Temp(0, s1);
+ Temp descriptor_sets[MAX_SETS];
+ Temp push_constants = Temp(0, s1);
+ Temp inline_push_consts[MAX_INLINE_PUSH_CONSTS];
+ unsigned num_inline_push_consts = 0;
+ unsigned base_inline_push_consts = 0;
+
+ /* VS inputs */
+ Temp vertex_buffers = Temp(0, s1);
+ Temp base_vertex = Temp(0, s1);
+ Temp start_instance = Temp(0, s1);
+ Temp draw_id = Temp(0, s1);
+ Temp view_index = Temp(0, s1);
+ Temp es2gs_offset = Temp(0, s1);
+ Temp vertex_id = Temp(0, v1);
+ Temp rel_auto_id = Temp(0, v1);
+ Temp instance_id = Temp(0, v1);
+ Temp vs_prim_id = Temp(0, v1);
+ bool needs_instance_id;
+
+ /* CS inputs */
+ Temp num_workgroups[3] = {Temp(0, s1), Temp(0, s1), Temp(0, s1)};
+ Temp workgroup_ids[3] = {Temp(0, s1), Temp(0, s1), Temp(0, s1)};
+ Temp tg_size = Temp(0, s1);
+ Temp local_invocation_ids[3] = {Temp(0, v1), Temp(0, v1), Temp(0, v1)};
+
+ /* VS output information */
+ unsigned num_clip_distances;
+ unsigned num_cull_distances;
+ vs_output_state vs_output;
+
+ /* Streamout */
+ Temp streamout_buffers = Temp(0, s1);
+ Temp streamout_write_idx = Temp(0, s1);
+ Temp streamout_config = Temp(0, s1);
+ Temp streamout_offset[4] = {Temp(0, s1), Temp(0, s1), Temp(0, s1), Temp(0, s1)};
+};
+
+fs_input get_interp_input(nir_intrinsic_op intrin, enum glsl_interp_mode interp)
+{
+ switch (interp) {
+ case INTERP_MODE_SMOOTH:
+ case INTERP_MODE_NONE:
+ if (intrin == nir_intrinsic_load_barycentric_pixel ||
+ intrin == nir_intrinsic_load_barycentric_at_sample ||
+ intrin == nir_intrinsic_load_barycentric_at_offset)
+ return fs_input::persp_center_p1;
+ else if (intrin == nir_intrinsic_load_barycentric_centroid)
+ return fs_input::persp_centroid_p1;
+ else if (intrin == nir_intrinsic_load_barycentric_sample)
+ return fs_input::persp_sample_p1;
+ break;
+ case INTERP_MODE_NOPERSPECTIVE:
+ if (intrin == nir_intrinsic_load_barycentric_pixel)
+ return fs_input::linear_center_p1;
+ else if (intrin == nir_intrinsic_load_barycentric_centroid)
+ return fs_input::linear_centroid_p1;
+ else if (intrin == nir_intrinsic_load_barycentric_sample)
+ return fs_input::linear_sample_p1;
+ break;
+ default:
+ break;
+ }
+ return fs_input::max_inputs;
+}
+
+void init_context(isel_context *ctx, nir_shader *shader)
+{
+ nir_function_impl *impl = nir_shader_get_entrypoint(shader);
+
+ ctx->shader = shader;
+ ctx->divergent_vals = nir_divergence_analysis(shader, nir_divergence_view_index_uniform);
+
+ std::unique_ptr<Temp[]> allocated{new Temp[impl->ssa_alloc]()};
+ memset(&ctx->fs_vgpr_args, false, sizeof(ctx->fs_vgpr_args));
+
+ bool done = false;
+ while (!done) {
+ done = true;
+ nir_foreach_block(block, impl) {
+ nir_foreach_instr(instr, block) {
+ switch(instr->type) {
+ case nir_instr_type_alu: {
+ nir_alu_instr *alu_instr = nir_instr_as_alu(instr);
+ unsigned size = alu_instr->dest.dest.ssa.num_components;
+ if (alu_instr->dest.dest.ssa.bit_size == 64)
+ size *= 2;
+ RegType type = RegType::sgpr;
+ switch(alu_instr->op) {
+ case nir_op_fmul:
+ case nir_op_fadd:
+ case nir_op_fsub:
+ case nir_op_fmax:
+ case nir_op_fmin:
+ case nir_op_fmax3:
+ case nir_op_fmin3:
+ case nir_op_fmed3:
+ case nir_op_fmod:
+ case nir_op_frem:
+ case nir_op_fneg:
+ case nir_op_fabs:
+ case nir_op_fsat:
+ case nir_op_fsign:
+ case nir_op_frcp:
+ case nir_op_frsq:
+ case nir_op_fsqrt:
+ case nir_op_fexp2:
+ case nir_op_flog2:
+ case nir_op_ffract:
+ case nir_op_ffloor:
+ case nir_op_fceil:
+ case nir_op_ftrunc:
+ case nir_op_fround_even:
+ case nir_op_fsin:
+ case nir_op_fcos:
+ case nir_op_f2f32:
+ case nir_op_f2f64:
+ case nir_op_u2f32:
+ case nir_op_u2f64:
+ case nir_op_i2f32:
+ case nir_op_i2f64:
+ case nir_op_pack_half_2x16:
+ case nir_op_unpack_half_2x16_split_x:
+ case nir_op_unpack_half_2x16_split_y:
+ case nir_op_fddx:
+ case nir_op_fddy:
+ case nir_op_fddx_fine:
+ case nir_op_fddy_fine:
+ case nir_op_fddx_coarse:
+ case nir_op_fddy_coarse:
+ case nir_op_fquantize2f16:
+ case nir_op_ldexp:
+ case nir_op_frexp_sig:
+ case nir_op_frexp_exp:
+ case nir_op_cube_face_index:
+ case nir_op_cube_face_coord:
+ type = RegType::vgpr;
+ break;
+ case nir_op_flt:
+ case nir_op_fge:
+ case nir_op_feq:
+ case nir_op_fne:
+ size = 2;
+ break;
+ case nir_op_ilt:
+ case nir_op_ige:
+ case nir_op_ult:
+ case nir_op_uge:
+ size = alu_instr->src[0].src.ssa->bit_size == 64 ? 2 : 1;
+ /* fallthrough */
+ case nir_op_ieq:
+ case nir_op_ine:
+ case nir_op_i2b1:
+ if (ctx->divergent_vals[alu_instr->dest.dest.ssa.index]) {
+ size = 2;
+ } else {
+ for (unsigned i = 0; i < nir_op_infos[alu_instr->op].num_inputs; i++) {
+ if (allocated[alu_instr->src[i].src.ssa->index].type() == RegType::vgpr)
+ size = 2;
+ }
+ }
+ break;
+ case nir_op_f2i64:
+ case nir_op_f2u64:
+ case nir_op_b2i32:
+ case nir_op_b2f32:
+ case nir_op_f2i32:
+ case nir_op_f2u32:
+ type = ctx->divergent_vals[alu_instr->dest.dest.ssa.index] ? RegType::vgpr : RegType::sgpr;
+ break;
+ case nir_op_bcsel:
+ if (alu_instr->dest.dest.ssa.bit_size == 1) {
+ if (ctx->divergent_vals[alu_instr->dest.dest.ssa.index])
+ size = 2;
+ else if (allocated[alu_instr->src[1].src.ssa->index].regClass() == s2 &&
+ allocated[alu_instr->src[2].src.ssa->index].regClass() == s2)
+ size = 2;
+ else
+ size = 1;
+ } else {
+ if (ctx->divergent_vals[alu_instr->dest.dest.ssa.index]) {
+ type = RegType::vgpr;
+ } else {
+ if (allocated[alu_instr->src[1].src.ssa->index].type() == RegType::vgpr ||
+ allocated[alu_instr->src[2].src.ssa->index].type() == RegType::vgpr) {
+ type = RegType::vgpr;
+ }
+ }
+ if (alu_instr->src[1].src.ssa->num_components == 1 && alu_instr->src[2].src.ssa->num_components == 1) {
+ assert(allocated[alu_instr->src[1].src.ssa->index].size() == allocated[alu_instr->src[2].src.ssa->index].size());
+ size = allocated[alu_instr->src[1].src.ssa->index].size();
+ }
+ }
+ break;
+ case nir_op_mov:
+ if (alu_instr->dest.dest.ssa.bit_size == 1) {
+ size = allocated[alu_instr->src[0].src.ssa->index].size();
+ } else {
+ type = ctx->divergent_vals[alu_instr->dest.dest.ssa.index] ? RegType::vgpr : RegType::sgpr;
+ }
+ break;
+ case nir_op_inot:
+ case nir_op_ixor:
+ if (alu_instr->dest.dest.ssa.bit_size == 1) {
+ size = ctx->divergent_vals[alu_instr->dest.dest.ssa.index] ? 2 : 1;
+ break;
+ } else {
+ /* fallthrough */
+ }
+ default:
+ if (alu_instr->dest.dest.ssa.bit_size == 1) {
+ if (ctx->divergent_vals[alu_instr->dest.dest.ssa.index]) {
+ size = 2;
+ } else {
+ size = 2;
+ for (unsigned i = 0; i < nir_op_infos[alu_instr->op].num_inputs; i++) {
+ if (allocated[alu_instr->src[i].src.ssa->index].regClass() == s1) {
+ size = 1;
+ break;
+ }
+ }
+ }
+ } else {
+ for (unsigned i = 0; i < nir_op_infos[alu_instr->op].num_inputs; i++) {
+ if (allocated[alu_instr->src[i].src.ssa->index].type() == RegType::vgpr)
+ type = RegType::vgpr;
+ }
+ }
+ break;
+ }
+ allocated[alu_instr->dest.dest.ssa.index] = Temp(0, RegClass(type, size));
+ break;
+ }
+ case nir_instr_type_load_const: {
+ unsigned size = nir_instr_as_load_const(instr)->def.num_components;
+ if (nir_instr_as_load_const(instr)->def.bit_size == 64)
+ size *= 2;
+ allocated[nir_instr_as_load_const(instr)->def.index] = Temp(0, RegClass(RegType::sgpr, size));
+ break;
+ }
+ case nir_instr_type_intrinsic: {
+ nir_intrinsic_instr *intrinsic = nir_instr_as_intrinsic(instr);
+ if (!nir_intrinsic_infos[intrinsic->intrinsic].has_dest)
+ break;
+ unsigned size = intrinsic->dest.ssa.num_components;
+ if (intrinsic->dest.ssa.bit_size == 64)
+ size *= 2;
+ RegType type = RegType::sgpr;
+ switch(intrinsic->intrinsic) {
+ case nir_intrinsic_load_push_constant:
+ case nir_intrinsic_load_work_group_id:
+ case nir_intrinsic_load_num_work_groups:
+ case nir_intrinsic_load_subgroup_id:
+ case nir_intrinsic_load_num_subgroups:
+ case nir_intrinsic_load_first_vertex:
+ case nir_intrinsic_load_base_instance:
+ case nir_intrinsic_get_buffer_size:
+ case nir_intrinsic_vote_all:
+ case nir_intrinsic_vote_any:
+ case nir_intrinsic_read_first_invocation:
+ case nir_intrinsic_read_invocation:
+ case nir_intrinsic_first_invocation:
+ case nir_intrinsic_vulkan_resource_index:
+ type = RegType::sgpr;
+ break;
+ case nir_intrinsic_ballot:
+ type = RegType::sgpr;
+ size = 2;
+ break;
+ case nir_intrinsic_load_sample_id:
+ case nir_intrinsic_load_sample_mask_in:
+ case nir_intrinsic_load_input:
+ case nir_intrinsic_load_vertex_id:
+ case nir_intrinsic_load_vertex_id_zero_base:
+ case nir_intrinsic_load_barycentric_sample:
+ case nir_intrinsic_load_barycentric_pixel:
+ case nir_intrinsic_load_barycentric_centroid:
+ case nir_intrinsic_load_barycentric_at_sample:
+ case nir_intrinsic_load_barycentric_at_offset:
+ case nir_intrinsic_load_interpolated_input:
+ case nir_intrinsic_load_frag_coord:
+ case nir_intrinsic_load_sample_pos:
+ case nir_intrinsic_load_layer_id:
+ case nir_intrinsic_load_local_invocation_id:
+ case nir_intrinsic_load_local_invocation_index:
+ case nir_intrinsic_load_subgroup_invocation:
+ case nir_intrinsic_write_invocation_amd:
+ case nir_intrinsic_mbcnt_amd:
+ case nir_intrinsic_load_instance_id:
+ case nir_intrinsic_ssbo_atomic_add:
+ case nir_intrinsic_ssbo_atomic_imin:
+ case nir_intrinsic_ssbo_atomic_umin:
+ case nir_intrinsic_ssbo_atomic_imax:
+ case nir_intrinsic_ssbo_atomic_umax:
+ case nir_intrinsic_ssbo_atomic_and:
+ case nir_intrinsic_ssbo_atomic_or:
+ case nir_intrinsic_ssbo_atomic_xor:
+ case nir_intrinsic_ssbo_atomic_exchange:
+ case nir_intrinsic_ssbo_atomic_comp_swap:
+ case nir_intrinsic_image_deref_atomic_add:
+ case nir_intrinsic_image_deref_atomic_umin:
+ case nir_intrinsic_image_deref_atomic_imin:
+ case nir_intrinsic_image_deref_atomic_umax:
+ case nir_intrinsic_image_deref_atomic_imax:
+ case nir_intrinsic_image_deref_atomic_and:
+ case nir_intrinsic_image_deref_atomic_or:
+ case nir_intrinsic_image_deref_atomic_xor:
+ case nir_intrinsic_image_deref_atomic_exchange:
+ case nir_intrinsic_image_deref_atomic_comp_swap:
+ case nir_intrinsic_image_deref_size:
+ case nir_intrinsic_shared_atomic_add:
+ case nir_intrinsic_shared_atomic_imin:
+ case nir_intrinsic_shared_atomic_umin:
+ case nir_intrinsic_shared_atomic_imax:
+ case nir_intrinsic_shared_atomic_umax:
+ case nir_intrinsic_shared_atomic_and:
+ case nir_intrinsic_shared_atomic_or:
+ case nir_intrinsic_shared_atomic_xor:
+ case nir_intrinsic_shared_atomic_exchange:
+ case nir_intrinsic_shared_atomic_comp_swap:
+ case nir_intrinsic_load_scratch:
+ type = RegType::vgpr;
+ break;
+ case nir_intrinsic_shuffle:
+ case nir_intrinsic_quad_broadcast:
+ case nir_intrinsic_quad_swap_horizontal:
+ case nir_intrinsic_quad_swap_vertical:
+ case nir_intrinsic_quad_swap_diagonal:
+ case nir_intrinsic_quad_swizzle_amd:
+ case nir_intrinsic_masked_swizzle_amd:
+ case nir_intrinsic_inclusive_scan:
+ case nir_intrinsic_exclusive_scan:
+ if (!ctx->divergent_vals[intrinsic->dest.ssa.index]) {
+ type = RegType::sgpr;
+ } else if (intrinsic->src[0].ssa->bit_size == 1) {
+ type = RegType::sgpr;
+ size = 2;
+ } else {
+ type = RegType::vgpr;
+ }
+ break;
+ case nir_intrinsic_load_view_index:
+ type = ctx->stage == fragment_fs ? RegType::vgpr : RegType::sgpr;
+ break;
+ case nir_intrinsic_load_front_face:
+ case nir_intrinsic_load_helper_invocation:
+ case nir_intrinsic_is_helper_invocation:
+ type = RegType::sgpr;
+ size = 2;
+ break;
+ case nir_intrinsic_reduce:
+ if (nir_intrinsic_cluster_size(intrinsic) == 0 ||
+ !ctx->divergent_vals[intrinsic->dest.ssa.index]) {
+ type = RegType::sgpr;
+ } else if (intrinsic->src[0].ssa->bit_size == 1) {
+ type = RegType::sgpr;
+ size = 2;
+ } else {
+ type = RegType::vgpr;
+ }
+ break;
+ case nir_intrinsic_load_ubo:
+ case nir_intrinsic_load_ssbo:
+ case nir_intrinsic_load_global:
+ type = ctx->divergent_vals[intrinsic->dest.ssa.index] ? RegType::vgpr : RegType::sgpr;
+ break;
+ /* due to copy propagation, the swizzled imov is removed if num dest components == 1 */
+ case nir_intrinsic_load_shared:
+ if (ctx->divergent_vals[intrinsic->dest.ssa.index])
+ type = RegType::vgpr;
+ else
+ type = RegType::sgpr;
+ break;
+ default:
+ for (unsigned i = 0; i < nir_intrinsic_infos[intrinsic->intrinsic].num_srcs; i++) {
+ if (allocated[intrinsic->src[i].ssa->index].type() == RegType::vgpr)
+ type = RegType::vgpr;
+ }
+ break;
+ }
+ allocated[intrinsic->dest.ssa.index] = Temp(0, RegClass(type, size));
+
+ switch(intrinsic->intrinsic) {
+ case nir_intrinsic_load_barycentric_sample:
+ case nir_intrinsic_load_barycentric_pixel:
+ case nir_intrinsic_load_barycentric_centroid:
+ case nir_intrinsic_load_barycentric_at_sample:
+ case nir_intrinsic_load_barycentric_at_offset: {
+ glsl_interp_mode mode = (glsl_interp_mode)nir_intrinsic_interp_mode(intrinsic);
+ ctx->fs_vgpr_args[get_interp_input(intrinsic->intrinsic, mode)] = true;
+ break;
+ }
+ case nir_intrinsic_load_front_face:
+ ctx->fs_vgpr_args[fs_input::front_face] = true;
+ break;
+ case nir_intrinsic_load_frag_coord:
+ case nir_intrinsic_load_sample_pos: {
+ uint8_t mask = nir_ssa_def_components_read(&intrinsic->dest.ssa);
+ for (unsigned i = 0; i < 4; i++) {
+ if (mask & (1 << i))
+ ctx->fs_vgpr_args[fs_input::frag_pos_0 + i] = true;
+
+ }
+ break;
+ }
+ case nir_intrinsic_load_sample_id:
+ ctx->fs_vgpr_args[fs_input::ancillary] = true;
+ break;
+ case nir_intrinsic_load_sample_mask_in:
+ ctx->fs_vgpr_args[fs_input::ancillary] = true;
+ ctx->fs_vgpr_args[fs_input::sample_coverage] = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case nir_instr_type_tex: {
+ nir_tex_instr* tex = nir_instr_as_tex(instr);
+ unsigned size = tex->dest.ssa.num_components;
+
+ if (tex->dest.ssa.bit_size == 64)
+ size *= 2;
+ if (tex->op == nir_texop_texture_samples)
+ assert(!ctx->divergent_vals[tex->dest.ssa.index]);
+ if (ctx->divergent_vals[tex->dest.ssa.index])
+ allocated[tex->dest.ssa.index] = Temp(0, RegClass(RegType::vgpr, size));
+ else
+ allocated[tex->dest.ssa.index] = Temp(0, RegClass(RegType::sgpr, size));
+ break;
+ }
+ case nir_instr_type_parallel_copy: {
+ nir_foreach_parallel_copy_entry(entry, nir_instr_as_parallel_copy(instr)) {
+ allocated[entry->dest.ssa.index] = allocated[entry->src.ssa->index];
+ }
+ break;
+ }
+ case nir_instr_type_ssa_undef: {
+ unsigned size = nir_instr_as_ssa_undef(instr)->def.num_components;
+ if (nir_instr_as_ssa_undef(instr)->def.bit_size == 64)
+ size *= 2;
+ allocated[nir_instr_as_ssa_undef(instr)->def.index] = Temp(0, RegClass(RegType::sgpr, size));
+ break;
+ }
+ case nir_instr_type_phi: {
+ nir_phi_instr* phi = nir_instr_as_phi(instr);
+ RegType type;
+ unsigned size = phi->dest.ssa.num_components;
+
+ if (phi->dest.ssa.bit_size == 1) {
+ assert(size == 1 && "multiple components not yet supported on boolean phis.");
+ type = RegType::sgpr;
+ size *= ctx->divergent_vals[phi->dest.ssa.index] ? 2 : 1;
+ allocated[phi->dest.ssa.index] = Temp(0, RegClass(type, size));
+ break;
+ }
+
+ if (ctx->divergent_vals[phi->dest.ssa.index]) {
+ type = RegType::vgpr;
+ } else {
+ type = RegType::sgpr;
+ nir_foreach_phi_src (src, phi) {
+ if (allocated[src->src.ssa->index].type() == RegType::vgpr)
+ type = RegType::vgpr;
+ if (allocated[src->src.ssa->index].type() == RegType::none)
+ done = false;
+ }
+ }
+
+ size *= phi->dest.ssa.bit_size == 64 ? 2 : 1;
+ RegClass rc = RegClass(type, size);
+ if (rc != allocated[phi->dest.ssa.index].regClass()) {
+ done = false;
+ } else {
+ nir_foreach_phi_src(src, phi)
+ assert(allocated[src->src.ssa->index].size() == rc.size());
+ }
+ allocated[phi->dest.ssa.index] = Temp(0, rc);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ for (unsigned i = 0; i < impl->ssa_alloc; i++)
+ allocated[i] = Temp(ctx->program->allocateId(), allocated[i].regClass());
+
+ ctx->allocated.reset(allocated.release());
+}
+
+struct user_sgpr_info {
+ uint8_t num_sgpr;
+ uint8_t remaining_sgprs;
+ uint8_t user_sgpr_idx;
+ bool need_ring_offsets;
+ bool indirect_all_descriptor_sets;
+};
+
+static void allocate_inline_push_consts(isel_context *ctx,
+ user_sgpr_info& user_sgpr_info)
+{
+ uint8_t remaining_sgprs = user_sgpr_info.remaining_sgprs;
+
+ /* Only supported if shaders use push constants. */
+ if (ctx->program->info->min_push_constant_used == UINT8_MAX)
+ return;
+
+ /* Only supported if shaders don't have indirect push constants. */
+ if (ctx->program->info->has_indirect_push_constants)
+ return;
+
+ /* Only supported for 32-bit push constants. */
+ //TODO: it's possible that some day, the load/store vectorization could make this inaccurate
+ if (!ctx->program->info->has_only_32bit_push_constants)
+ return;
+
+ uint8_t num_push_consts =
+ (ctx->program->info->max_push_constant_used -
+ ctx->program->info->min_push_constant_used) / 4;
+
+ /* Check if the number of user SGPRs is large enough. */
+ if (num_push_consts < remaining_sgprs) {
+ ctx->program->info->num_inline_push_consts = num_push_consts;
+ } else {
+ ctx->program->info->num_inline_push_consts = remaining_sgprs;
+ }
+
+ /* Clamp to the maximum number of allowed inlined push constants. */
+ if (ctx->program->info->num_inline_push_consts > MAX_INLINE_PUSH_CONSTS)
+ ctx->program->info->num_inline_push_consts = MAX_INLINE_PUSH_CONSTS;
+
+ if (ctx->program->info->num_inline_push_consts == num_push_consts &&
+ !ctx->program->info->loads_dynamic_offsets) {
+ /* Disable the default push constants path if all constants are
+ * inlined and if shaders don't use dynamic descriptors.
+ */
+ ctx->program->info->loads_push_constants = false;
+ user_sgpr_info.num_sgpr--;
+ user_sgpr_info.remaining_sgprs++;
+ }
+
+ ctx->program->info->base_inline_push_consts =
+ ctx->program->info->min_push_constant_used / 4;
+
+ user_sgpr_info.num_sgpr += ctx->program->info->num_inline_push_consts;
+ user_sgpr_info.remaining_sgprs -= ctx->program->info->num_inline_push_consts;
+}
+
+static void allocate_user_sgprs(isel_context *ctx,
+ bool needs_view_index, user_sgpr_info& user_sgpr_info)
+{
+ memset(&user_sgpr_info, 0, sizeof(struct user_sgpr_info));
+ uint32_t user_sgpr_count = 0;
+
+ /* until we sort out scratch/global buffers always assign ring offsets for gs/vs/es */
+ if (ctx->stage != fragment_fs &&
+ ctx->stage != compute_cs
+ /*|| ctx->is_gs_copy_shader */)
+ user_sgpr_info.need_ring_offsets = true;
+
+ if (ctx->stage == fragment_fs &&
+ ctx->program->info->ps.needs_sample_positions)
+ user_sgpr_info.need_ring_offsets = true;
+
+ /* 2 user sgprs will nearly always be allocated for scratch/rings */
+ if (ctx->options->supports_spill || user_sgpr_info.need_ring_offsets || ctx->scratch_enabled)
+ user_sgpr_count += 2;
+
+ switch (ctx->stage) {
+ case vertex_vs:
+ /* if (!ctx->is_gs_copy_shader) */ {
+ if (ctx->program->info->vs.has_vertex_buffers)
+ user_sgpr_count++;
+ user_sgpr_count += ctx->program->info->vs.needs_draw_id ? 3 : 2;
+ }
+ break;
+ case fragment_fs:
+ //user_sgpr_count += ctx->program->info->ps.needs_sample_positions;
+ break;
+ case compute_cs:
+ if (ctx->program->info->cs.uses_grid_size)
+ user_sgpr_count += 3;
+ break;
+ default:
+ unreachable("Shader stage not implemented");
+ }
+
+ if (needs_view_index)
+ user_sgpr_count++;
+
+ if (ctx->program->info->loads_push_constants)
+ user_sgpr_count += 1; /* we use 32bit pointers */
+
+ if (ctx->program->info->so.num_outputs)
+ user_sgpr_count += 1; /* we use 32bit pointers */
+
+ uint32_t available_sgprs = ctx->options->chip_class >= GFX9 && !(ctx->stage & hw_cs) ? 32 : 16;
+ uint32_t remaining_sgprs = available_sgprs - user_sgpr_count;
+ uint32_t num_desc_set = util_bitcount(ctx->program->info->desc_set_used_mask);
+
+ if (available_sgprs < user_sgpr_count + num_desc_set) {
+ user_sgpr_info.indirect_all_descriptor_sets = true;
+ user_sgpr_info.num_sgpr = user_sgpr_count + 1;
+ user_sgpr_info.remaining_sgprs = remaining_sgprs - 1;
+ } else {
+ user_sgpr_info.num_sgpr = user_sgpr_count + num_desc_set;
+ user_sgpr_info.remaining_sgprs = remaining_sgprs - num_desc_set;
+ }
+
+ allocate_inline_push_consts(ctx, user_sgpr_info);
+}
+
+#define MAX_ARGS 64
+struct arg_info {
+ RegClass types[MAX_ARGS];
+ Temp *assign[MAX_ARGS];
+ PhysReg reg[MAX_ARGS];
+ unsigned array_params_mask;
+ uint8_t count;
+ uint8_t sgpr_count;
+ uint8_t num_sgprs_used;
+ uint8_t num_vgprs_used;
+};
+
+static void
+add_arg(arg_info *info, RegClass rc, Temp *param_ptr, unsigned reg)
+{
+ assert(info->count < MAX_ARGS);
+
+ info->assign[info->count] = param_ptr;
+ info->types[info->count] = rc;
+
+ if (rc.type() == RegType::sgpr) {
+ info->num_sgprs_used += rc.size();
+ info->sgpr_count++;
+ info->reg[info->count] = PhysReg{reg};
+ } else {
+ assert(rc.type() == RegType::vgpr);
+ info->num_vgprs_used += rc.size();
+ info->reg[info->count] = PhysReg{reg + 256};
+ }
+ info->count++;
+}
+
+static void
+set_loc(struct radv_userdata_info *ud_info, uint8_t *sgpr_idx, uint8_t num_sgprs)
+{
+ ud_info->sgpr_idx = *sgpr_idx;
+ ud_info->num_sgprs = num_sgprs;
+ *sgpr_idx += num_sgprs;
+}
+
+static void
+set_loc_shader(isel_context *ctx, int idx, uint8_t *sgpr_idx,
+ uint8_t num_sgprs)
+{
+ struct radv_userdata_info *ud_info = &ctx->program->info->user_sgprs_locs.shader_data[idx];
+ assert(ud_info);
+
+ set_loc(ud_info, sgpr_idx, num_sgprs);
+}
+
+static void
+set_loc_shader_ptr(isel_context *ctx, int idx, uint8_t *sgpr_idx)
+{
+ bool use_32bit_pointers = idx != AC_UD_SCRATCH_RING_OFFSETS;
+
+ set_loc_shader(ctx, idx, sgpr_idx, use_32bit_pointers ? 1 : 2);
+}
+
+static void
+set_loc_desc(isel_context *ctx, int idx, uint8_t *sgpr_idx)
+{
+ struct radv_userdata_locations *locs = &ctx->program->info->user_sgprs_locs;
+ struct radv_userdata_info *ud_info = &locs->descriptor_sets[idx];
+ assert(ud_info);
+
+ set_loc(ud_info, sgpr_idx, 1);
+ locs->descriptor_sets_enabled |= 1 << idx;
+}
+
+static void
+declare_global_input_sgprs(isel_context *ctx,
+ /* bool has_previous_stage, gl_shader_stage previous_stage, */
+ user_sgpr_info *user_sgpr_info,
+ struct arg_info *args,
+ Temp *desc_sets)
+{
+ /* 1 for each descriptor set */
+ if (!user_sgpr_info->indirect_all_descriptor_sets) {
+ uint32_t mask = ctx->program->info->desc_set_used_mask;
+ while (mask) {
+ int i = u_bit_scan(&mask);
+ add_arg(args, s1, &desc_sets[i], user_sgpr_info->user_sgpr_idx);
+ set_loc_desc(ctx, i, &user_sgpr_info->user_sgpr_idx);
+ }
+ /* NIR->LLVM might have set this to true if RADV_DEBUG=compiletime */
+ ctx->program->info->need_indirect_descriptor_sets = false;
+ } else {
+ add_arg(args, s1, desc_sets, user_sgpr_info->user_sgpr_idx);
+ set_loc_shader_ptr(ctx, AC_UD_INDIRECT_DESCRIPTOR_SETS, &user_sgpr_info->user_sgpr_idx);
+ ctx->program->info->need_indirect_descriptor_sets = true;
+ }
+
+ if (ctx->program->info->loads_push_constants) {
+ /* 1 for push constants and dynamic descriptors */
+ add_arg(args, s1, &ctx->push_constants, user_sgpr_info->user_sgpr_idx);
+ set_loc_shader_ptr(ctx, AC_UD_PUSH_CONSTANTS, &user_sgpr_info->user_sgpr_idx);
+ }
+
+ if (ctx->program->info->num_inline_push_consts) {
+ unsigned count = ctx->program->info->num_inline_push_consts;
+ for (unsigned i = 0; i < count; i++)
+ add_arg(args, s1, &ctx->inline_push_consts[i], user_sgpr_info->user_sgpr_idx + i);
+ set_loc_shader(ctx, AC_UD_INLINE_PUSH_CONSTANTS, &user_sgpr_info->user_sgpr_idx, count);
+
+ ctx->num_inline_push_consts = ctx->program->info->num_inline_push_consts;
+ ctx->base_inline_push_consts = ctx->program->info->base_inline_push_consts;
+ }
+
+ if (ctx->program->info->so.num_outputs) {
+ add_arg(args, s1, &ctx->streamout_buffers, user_sgpr_info->user_sgpr_idx);
+ set_loc_shader_ptr(ctx, AC_UD_STREAMOUT_BUFFERS, &user_sgpr_info->user_sgpr_idx);
+ }
+}
+
+static void
+declare_vs_input_vgprs(isel_context *ctx, struct arg_info *args)
+{
+ unsigned vgpr_idx = 0;
+ add_arg(args, v1, &ctx->vertex_id, vgpr_idx++);
+/* if (!ctx->is_gs_copy_shader) */ {
+ if (ctx->options->key.vs.out.as_ls) {
+ add_arg(args, v1, &ctx->rel_auto_id, vgpr_idx++);
+ add_arg(args, v1, &ctx->instance_id, vgpr_idx++);
+ } else {
+ add_arg(args, v1, &ctx->instance_id, vgpr_idx++);
+ add_arg(args, v1, &ctx->vs_prim_id, vgpr_idx++);
+ }
+ add_arg(args, v1, NULL, vgpr_idx); /* unused */
+ }
+}
+
+static void
+declare_streamout_sgprs(isel_context *ctx, struct arg_info *args, unsigned *idx)
+{
+ /* Streamout SGPRs. */
+ if (ctx->program->info->so.num_outputs) {
+ assert(ctx->stage & hw_vs);
+
+ if (ctx->stage != tess_eval_vs) {
+ add_arg(args, s1, &ctx->streamout_config, (*idx)++);
+ } else {
+ args->assign[args->count - 1] = &ctx->streamout_config;
+ args->types[args->count - 1] = s1;
+ }
+
+ add_arg(args, s1, &ctx->streamout_write_idx, (*idx)++);
+ }
+
+ /* A streamout buffer offset is loaded if the stride is non-zero. */
+ for (unsigned i = 0; i < 4; i++) {
+ if (!ctx->program->info->so.strides[i])
+ continue;
+
+ add_arg(args, s1, &ctx->streamout_offset[i], (*idx)++);
+ }
+}
+
+static bool needs_view_index_sgpr(isel_context *ctx)
+{
+ switch (ctx->stage) {
+ case vertex_vs:
+ return ctx->program->info->needs_multiview_view_index || ctx->options->key.has_multiview_view_index;
+ case tess_eval_vs:
+ return ctx->program->info->needs_multiview_view_index && ctx->options->key.has_multiview_view_index;
+ case vertex_ls:
+ case vertex_tess_control_ls:
+ case vertex_geometry_es:
+ case tess_control_hs:
+ case tess_eval_es:
+ case tess_eval_geometry_es:
+ case geometry_gs:
+ return ctx->program->info->needs_multiview_view_index;
+ default:
+ return false;
+ }
+}
+
+static inline bool
+add_fs_arg(isel_context *ctx, arg_info *args, unsigned &vgpr_idx, fs_input input, unsigned value, bool enable_next = false, RegClass rc = v1)
+{
+ if (!ctx->fs_vgpr_args[input])
+ return false;
+
+ add_arg(args, rc, &ctx->fs_inputs[input], vgpr_idx);
+ vgpr_idx += rc.size();
+
+ if (enable_next) {
+ add_arg(args, rc, &ctx->fs_inputs[input + 1], vgpr_idx);
+ vgpr_idx += rc.size();
+ }
+
+ ctx->program->config->spi_ps_input_addr |= value;
+ ctx->program->config->spi_ps_input_ena |= value;
+ return true;
+}
+
+void add_startpgm(struct isel_context *ctx)
+{
+ user_sgpr_info user_sgpr_info;
+ bool needs_view_index = needs_view_index_sgpr(ctx);
+ allocate_user_sgprs(ctx, needs_view_index, user_sgpr_info);
+ arg_info args = {};
+
+ /* this needs to be in sgprs 0 and 1 */
+ if (ctx->options->supports_spill || user_sgpr_info.need_ring_offsets || ctx->scratch_enabled) {
+ add_arg(&args, s2, &ctx->private_segment_buffer, 0);
+ set_loc_shader_ptr(ctx, AC_UD_SCRATCH_RING_OFFSETS, &user_sgpr_info.user_sgpr_idx);
+ }
+
+ unsigned vgpr_idx = 0;
+ switch (ctx->stage) {
+ case vertex_vs: {
+ declare_global_input_sgprs(ctx, &user_sgpr_info, &args, ctx->descriptor_sets);
+ if (ctx->program->info->vs.has_vertex_buffers) {
+ add_arg(&args, s1, &ctx->vertex_buffers, user_sgpr_info.user_sgpr_idx);
+ set_loc_shader_ptr(ctx, AC_UD_VS_VERTEX_BUFFERS, &user_sgpr_info.user_sgpr_idx);
+ }
+ add_arg(&args, s1, &ctx->base_vertex, user_sgpr_info.user_sgpr_idx);
+ add_arg(&args, s1, &ctx->start_instance, user_sgpr_info.user_sgpr_idx + 1);
+ if (ctx->program->info->vs.needs_draw_id) {
+ add_arg(&args, s1, &ctx->draw_id, user_sgpr_info.user_sgpr_idx + 2);
+ set_loc_shader(ctx, AC_UD_VS_BASE_VERTEX_START_INSTANCE, &user_sgpr_info.user_sgpr_idx, 3);
+ } else
+ set_loc_shader(ctx, AC_UD_VS_BASE_VERTEX_START_INSTANCE, &user_sgpr_info.user_sgpr_idx, 2);
+
+ if (needs_view_index) {
+ add_arg(&args, s1, &ctx->view_index, user_sgpr_info.user_sgpr_idx);
+ set_loc_shader(ctx, AC_UD_VIEW_INDEX, &user_sgpr_info.user_sgpr_idx, 1);
+ }
+
+ assert(user_sgpr_info.user_sgpr_idx == user_sgpr_info.num_sgpr);
+ unsigned idx = user_sgpr_info.user_sgpr_idx;
+ if (ctx->options->key.vs.out.as_es)
+ add_arg(&args, s1, &ctx->es2gs_offset, idx++);
+ else
+ declare_streamout_sgprs(ctx, &args, &idx);
+
+ if (ctx->scratch_enabled)
+ add_arg(&args, s1, &ctx->scratch_offset, idx++);
+
+ declare_vs_input_vgprs(ctx, &args);
+ break;
+ }
+ case fragment_fs: {
+ declare_global_input_sgprs(ctx, &user_sgpr_info, &args, ctx->descriptor_sets);
+
+ assert(user_sgpr_info.user_sgpr_idx == user_sgpr_info.num_sgpr);
+ add_arg(&args, s1, &ctx->prim_mask, user_sgpr_info.user_sgpr_idx);
+
+ if (ctx->scratch_enabled)
+ add_arg(&args, s1, &ctx->scratch_offset, user_sgpr_info.user_sgpr_idx + 1);
+
+ ctx->program->config->spi_ps_input_addr = 0;
+ ctx->program->config->spi_ps_input_ena = 0;
+
+ bool has_interp_mode = false;
+
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_sample_p1, S_0286CC_PERSP_SAMPLE_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_center_p1, S_0286CC_PERSP_CENTER_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_centroid_p1, S_0286CC_PERSP_CENTROID_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_pull_model, S_0286CC_PERSP_PULL_MODEL_ENA(1), false, v3);
+
+ if (!has_interp_mode && ctx->fs_vgpr_args[fs_input::frag_pos_3]) {
+ /* If POS_W_FLOAT (11) is enabled, at least one of PERSP_* must be enabled too */
+ ctx->fs_vgpr_args[fs_input::persp_center_p1] = true;
+ has_interp_mode = add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_center_p1, S_0286CC_PERSP_CENTER_ENA(1), true);
+ }
+
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::linear_sample_p1, S_0286CC_LINEAR_SAMPLE_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::linear_center_p1, S_0286CC_LINEAR_CENTER_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::linear_centroid_p1, S_0286CC_LINEAR_CENTROID_ENA(1), true);
+ has_interp_mode |= add_fs_arg(ctx, &args, vgpr_idx, fs_input::line_stipple, S_0286CC_LINE_STIPPLE_TEX_ENA(1));
+
+ if (!has_interp_mode) {
+ /* At least one of PERSP_* (0xF) or LINEAR_* (0x70) must be enabled */
+ ctx->fs_vgpr_args[fs_input::persp_center_p1] = true;
+ has_interp_mode = add_fs_arg(ctx, &args, vgpr_idx, fs_input::persp_center_p1, S_0286CC_PERSP_CENTER_ENA(1), true);
+ }
+
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::frag_pos_0, S_0286CC_POS_X_FLOAT_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::frag_pos_1, S_0286CC_POS_Y_FLOAT_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::frag_pos_2, S_0286CC_POS_Z_FLOAT_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::frag_pos_3, S_0286CC_POS_W_FLOAT_ENA(1));
+
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::front_face, S_0286CC_FRONT_FACE_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::ancillary, S_0286CC_ANCILLARY_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::sample_coverage, S_0286CC_SAMPLE_COVERAGE_ENA(1));
+ add_fs_arg(ctx, &args, vgpr_idx, fs_input::fixed_pt, S_0286CC_POS_FIXED_PT_ENA(1));
+
+ ASSERTED bool unset_interp_mode = !(ctx->program->config->spi_ps_input_addr & 0x7F) ||
+ (G_0286CC_POS_W_FLOAT_ENA(ctx->program->config->spi_ps_input_addr)
+ && !(ctx->program->config->spi_ps_input_addr & 0xF));
+
+ assert(has_interp_mode);
+ assert(!unset_interp_mode);
+ break;
+ }
+ case compute_cs: {
+ declare_global_input_sgprs(ctx, &user_sgpr_info, &args, ctx->descriptor_sets);
+
+ if (ctx->program->info->cs.uses_grid_size) {
+ add_arg(&args, s1, &ctx->num_workgroups[0], user_sgpr_info.user_sgpr_idx);
+ add_arg(&args, s1, &ctx->num_workgroups[1], user_sgpr_info.user_sgpr_idx + 1);
+ add_arg(&args, s1, &ctx->num_workgroups[2], user_sgpr_info.user_sgpr_idx + 2);
+ set_loc_shader(ctx, AC_UD_CS_GRID_SIZE, &user_sgpr_info.user_sgpr_idx, 3);
+ }
+ assert(user_sgpr_info.user_sgpr_idx == user_sgpr_info.num_sgpr);
+ unsigned idx = user_sgpr_info.user_sgpr_idx;
+ for (unsigned i = 0; i < 3; i++) {
+ if (ctx->program->info->cs.uses_block_id[i])
+ add_arg(&args, s1, &ctx->workgroup_ids[i], idx++);
+ }
+
+ if (ctx->program->info->cs.uses_local_invocation_idx)
+ add_arg(&args, s1, &ctx->tg_size, idx++);
+ if (ctx->scratch_enabled)
+ add_arg(&args, s1, &ctx->scratch_offset, idx++);
+
+ add_arg(&args, v1, &ctx->local_invocation_ids[0], vgpr_idx++);
+ add_arg(&args, v1, &ctx->local_invocation_ids[1], vgpr_idx++);
+ add_arg(&args, v1, &ctx->local_invocation_ids[2], vgpr_idx++);
+ break;
+ }
+ default:
+ unreachable("Shader stage not implemented");
+ }
+
+ ctx->program->info->num_input_vgprs = 0;
+ ctx->program->info->num_input_sgprs = args.num_sgprs_used;
+ ctx->program->info->num_user_sgprs = user_sgpr_info.num_sgpr;
+ ctx->program->info->num_input_vgprs = args.num_vgprs_used;
+
+ aco_ptr<Pseudo_instruction> startpgm{create_instruction<Pseudo_instruction>(aco_opcode::p_startpgm, Format::PSEUDO, 0, args.count + 1)};
+ for (unsigned i = 0; i < args.count; i++) {
+ if (args.assign[i]) {
+ *args.assign[i] = Temp{ctx->program->allocateId(), args.types[i]};
+ startpgm->definitions[i] = Definition(*args.assign[i]);
+ startpgm->definitions[i].setFixed(args.reg[i]);
+ }
+ }
+ startpgm->definitions[args.count] = Definition{ctx->program->allocateId(), exec, s2};
+ ctx->block->instructions.push_back(std::move(startpgm));
+}
+
+int
+type_size(const struct glsl_type *type, bool bindless)
+{
+ // TODO: don't we need type->std430_base_alignment() here?
+ return glsl_count_attribute_slots(type, false);
+}
+
+void
+shared_var_info(const struct glsl_type *type, unsigned *size, unsigned *align)
+{
+ assert(glsl_type_is_vector_or_scalar(type));
+
+ uint32_t comp_size = glsl_type_is_boolean(type)
+ ? 4 : glsl_get_bit_size(type) / 8;
+ unsigned length = glsl_get_vector_elements(type);
+ *size = comp_size * length,
+ *align = comp_size;
+}
+
+int
+get_align(nir_variable_mode mode, bool is_store, unsigned bit_size, unsigned num_components)
+{
+ /* TODO: ACO doesn't have good support for non-32-bit reads/writes yet */
+ if (bit_size != 32)
+ return -1;
+
+ switch (mode) {
+ case nir_var_mem_ubo:
+ case nir_var_mem_ssbo:
+ //case nir_var_mem_push_const: enable with 1240!
+ case nir_var_mem_shared:
+ /* TODO: what are the alignment requirements for LDS? */
+ return num_components <= 4 ? 4 : -1;
+ default:
+ return -1;
+ }
+}
+
+void
+setup_vs_variables(isel_context *ctx, nir_shader *nir)
+{
+ nir_foreach_variable(variable, &nir->inputs)
+ {
+ variable->data.driver_location = variable->data.location * 4;
+ }
+ nir_foreach_variable(variable, &nir->outputs)
+ {
+ variable->data.driver_location = variable->data.location * 4;
+ }
+
+ radv_vs_output_info *outinfo = &ctx->program->info->vs.outinfo;
+
+ memset(outinfo->vs_output_param_offset, AC_EXP_PARAM_UNDEFINED,
+ sizeof(outinfo->vs_output_param_offset));
+
+ ctx->needs_instance_id = ctx->program->info->vs.needs_instance_id;
+
+ bool export_clip_dists = ctx->options->key.vs_common_out.export_clip_dists;
+
+ outinfo->param_exports = 0;
+ int pos_written = 0x1;
+ if (outinfo->writes_pointsize || outinfo->writes_viewport_index || outinfo->writes_layer)
+ pos_written |= 1 << 1;
+
+ nir_foreach_variable(variable, &nir->outputs)
+ {
+ int idx = variable->data.location;
+ unsigned slots = variable->type->count_attribute_slots(false);
+ if (variable->data.compact) {
+ unsigned component_count = variable->data.location_frac + variable->type->length;
+ slots = (component_count + 3) / 4;
+ }
+
+ if (idx >= VARYING_SLOT_VAR0 || idx == VARYING_SLOT_LAYER || idx == VARYING_SLOT_PRIMITIVE_ID ||
+ ((idx == VARYING_SLOT_CLIP_DIST0 || idx == VARYING_SLOT_CLIP_DIST1) && export_clip_dists)) {
+ for (unsigned i = 0; i < slots; i++) {
+ if (outinfo->vs_output_param_offset[idx + i] == AC_EXP_PARAM_UNDEFINED)
+ outinfo->vs_output_param_offset[idx + i] = outinfo->param_exports++;
+ }
+ }
+ }
+ if (outinfo->writes_layer &&
+ outinfo->vs_output_param_offset[VARYING_SLOT_LAYER] == AC_EXP_PARAM_UNDEFINED) {
+ /* when ctx->options->key.has_multiview_view_index = true, the layer
+ * variable isn't declared in NIR and it's isel's job to get the layer */
+ outinfo->vs_output_param_offset[VARYING_SLOT_LAYER] = outinfo->param_exports++;
+ }
+
+ if (outinfo->export_prim_id) {
+ assert(outinfo->vs_output_param_offset[VARYING_SLOT_PRIMITIVE_ID] == AC_EXP_PARAM_UNDEFINED);
+ outinfo->vs_output_param_offset[VARYING_SLOT_PRIMITIVE_ID] = outinfo->param_exports++;
+ }
+
+ ctx->num_clip_distances = util_bitcount(outinfo->clip_dist_mask);
+ ctx->num_cull_distances = util_bitcount(outinfo->cull_dist_mask);
+
+ assert(ctx->num_clip_distances + ctx->num_cull_distances <= 8);
+
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 0)
+ pos_written |= 1 << 2;
+ if (ctx->num_clip_distances + ctx->num_cull_distances > 4)
+ pos_written |= 1 << 3;
+
+ outinfo->pos_exports = util_bitcount(pos_written);
+}
+
+void
+setup_variables(isel_context *ctx, nir_shader *nir)
+{
+ switch (nir->info.stage) {
+ case MESA_SHADER_FRAGMENT: {
+ nir_foreach_variable(variable, &nir->outputs)
+ {
+ int idx = variable->data.location + variable->data.index;
+ variable->data.driver_location = idx * 4;
+ }
+ break;
+ }
+ case MESA_SHADER_COMPUTE: {
+ unsigned lds_allocation_size_unit = 4 * 64;
+ if (ctx->program->chip_class >= GFX7)
+ lds_allocation_size_unit = 4 * 128;
+ ctx->program->config->lds_size = (nir->info.cs.shared_size + lds_allocation_size_unit - 1) / lds_allocation_size_unit;
+ break;
+ }
+ case MESA_SHADER_VERTEX: {
+ setup_vs_variables(ctx, nir);
+ break;
+ }
+ default:
+ unreachable("Unhandled shader stage.");
+ }
+}
+
+isel_context
+setup_isel_context(Program* program,
+ unsigned shader_count,
+ struct nir_shader *const *shaders,
+ ac_shader_config* config,
+ radv_shader_info *info,
+ radv_nir_compiler_options *options)
+{
+ program->stage = 0;
+ for (unsigned i = 0; i < shader_count; i++) {
+ switch (shaders[i]->info.stage) {
+ case MESA_SHADER_VERTEX:
+ program->stage |= sw_vs;
+ break;
+ case MESA_SHADER_TESS_CTRL:
+ program->stage |= sw_tcs;
+ break;
+ case MESA_SHADER_TESS_EVAL:
+ program->stage |= sw_tes;
+ break;
+ case MESA_SHADER_GEOMETRY:
+ program->stage |= sw_gs;
+ break;
+ case MESA_SHADER_FRAGMENT:
+ program->stage |= sw_fs;
+ break;
+ case MESA_SHADER_COMPUTE:
+ program->stage |= sw_cs;
+ break;
+ default:
+ unreachable("Shader stage not implemented");
+ }
+ }
+ if (program->stage == sw_vs)
+ program->stage |= hw_vs;
+ else if (program->stage == sw_fs)
+ program->stage |= hw_fs;
+ else if (program->stage == sw_cs)
+ program->stage |= hw_cs;
+ else
+ unreachable("Shader stage not implemented");
+
+ program->config = config;
+ program->info = info;
+ program->chip_class = options->chip_class;
+ program->family = options->family;
+ program->sgpr_limit = options->chip_class >= GFX8 ? 102 : 104;
+ if (options->family == CHIP_TONGA || options->family == CHIP_ICELAND)
+ program->sgpr_limit = 94; /* workaround hardware bug */
+
+ for (unsigned i = 0; i < MAX_SETS; ++i)
+ program->info->user_sgprs_locs.descriptor_sets[i].sgpr_idx = -1;
+ for (unsigned i = 0; i < AC_UD_MAX_UD; ++i)
+ program->info->user_sgprs_locs.shader_data[i].sgpr_idx = -1;
+
+ isel_context ctx = {};
+ ctx.program = program;
+ ctx.options = options;
+ ctx.stage = program->stage;
+
+ for (unsigned i = 0; i < fs_input::max_inputs; ++i)
+ ctx.fs_inputs[i] = Temp(0, v1);
+ ctx.fs_inputs[fs_input::persp_pull_model] = Temp(0, v3);
+ for (unsigned i = 0; i < MAX_SETS; ++i)
+ ctx.descriptor_sets[i] = Temp(0, s1);
+ for (unsigned i = 0; i < MAX_INLINE_PUSH_CONSTS; ++i)
+ ctx.inline_push_consts[i] = Temp(0, s1);
+ for (unsigned i = 0; i <= VARYING_SLOT_VAR31; ++i) {
+ for (unsigned j = 0; j < 4; ++j)
+ ctx.vs_output.outputs[i][j] = Temp(0, v1);
+ }
+
+ for (unsigned i = 0; i < shader_count; i++) {
+ nir_shader *nir = shaders[i];
+
+ /* align and copy constant data */
+ while (program->constant_data.size() % 4u)
+ program->constant_data.push_back(0);
+ ctx.constant_data_offset = program->constant_data.size();
+ program->constant_data.insert(program->constant_data.end(),
+ (uint8_t*)nir->constant_data,
+ (uint8_t*)nir->constant_data + nir->constant_data_size);
+
+ /* the variable setup has to be done before lower_io / CSE */
+ if (nir->info.stage == MESA_SHADER_COMPUTE)
+ nir_lower_vars_to_explicit_types(nir, nir_var_mem_shared, shared_var_info);
+ setup_variables(&ctx, nir);
+
+ /* optimize and lower memory operations */
+ bool lower_to_scalar = false;
+ bool lower_pack = false;
+ // TODO: uncomment this once !1240 is merged
+ /*if (nir_opt_load_store_vectorize(nir,
+ (nir_variable_mode)(nir_var_mem_ssbo | nir_var_mem_ubo |
+ nir_var_mem_push_const | nir_var_mem_shared),
+ get_align)) {
+ lower_to_scalar = true;
+ lower_pack = true;
+ }*/
+ if (nir->info.stage == MESA_SHADER_COMPUTE)
+ lower_to_scalar |= nir_lower_explicit_io(nir, nir_var_mem_shared, nir_address_format_32bit_offset);
+ else
+ nir_lower_io(nir, (nir_variable_mode)(nir_var_shader_in | nir_var_shader_out), type_size, (nir_lower_io_options)0);
+ nir_lower_explicit_io(nir, nir_var_mem_global, nir_address_format_64bit_global);
+
+ if (lower_to_scalar)
+ nir_lower_alu_to_scalar(nir, NULL, NULL);
+ if (lower_pack)
+ nir_lower_pack(nir);
+
+ /* lower ALU operations */
+ nir_opt_idiv_const(nir, 32);
+ nir_lower_idiv(nir); // TODO: use the LLVM path once !1239 is merged
+
+ // TODO: implement logic64 in aco, it's more effective for sgprs
+ nir_lower_int64(nir, (nir_lower_int64_options) (nir_lower_imul64 |
+ nir_lower_imul_high64 |
+ nir_lower_imul_2x32_64 |
+ nir_lower_divmod64 |
+ nir_lower_logic64 |
+ nir_lower_minmax64 |
+ nir_lower_iabs64 |
+ nir_lower_ineg64));
+
+ /* optimize the lowered ALU operations */
+ nir_copy_prop(nir);
+ nir_opt_constant_folding(nir);
+ nir_opt_algebraic(nir);
+ nir_opt_algebraic_late(nir);
+ nir_opt_constant_folding(nir);
+
+ /* cleanup passes */
+ nir_lower_load_const_to_scalar(nir);
+ nir_opt_cse(nir);
+ nir_opt_dce(nir);
+ nir_opt_shrink_load(nir);
+ nir_move_options move_opts = (nir_move_options)(
+ nir_move_const_undef | nir_move_load_ubo | nir_move_load_input | nir_move_comparisons);
+ //nir_opt_sink(nir, move_opts); // TODO: enable this once !1664 is merged
+ nir_opt_move(nir, move_opts);
+ nir_convert_to_lcssa(nir, true, false);
+ nir_lower_phis_to_scalar(nir);
+
+ nir_function_impl *func = nir_shader_get_entrypoint(nir);
+ nir_index_ssa_defs(func);
+
+ if (options->dump_preoptir) {
+ fprintf(stderr, "NIR shader before instruction selection:\n");
+ nir_print_shader(nir, stderr);
+ }
+ }
+
+ unsigned scratch_size = 0;
+ for (unsigned i = 0; i < shader_count; i++)
+ scratch_size = std::max(scratch_size, shaders[i]->scratch_size);
+ ctx.scratch_enabled = scratch_size > 0;
+ ctx.program->config->scratch_bytes_per_wave = align(scratch_size * ctx.options->wave_size, 1024);
+ ctx.program->config->float_mode = V_00B028_FP_64_DENORMS;
+ ctx.program->info->wave_size = ctx.options->wave_size;
+
+ ctx.block = ctx.program->create_and_insert_block();
+ ctx.block->loop_nest_depth = 0;
+ ctx.block->kind = block_kind_top_level;
+
+ return ctx;
+}
+
+}
diff --git a/src/amd/compiler/aco_interface.cpp b/src/amd/compiler/aco_interface.cpp
new file mode 100644
index 00000000000..6adb911e4b3
--- /dev/null
+++ b/src/amd/compiler/aco_interface.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "aco_interface.h"
+#include "aco_ir.h"
+#include "vulkan/radv_shader.h"
+#include "c11/threads.h"
+#include "util/debug.h"
+
+#include <iostream>
+#include <sstream>
+
+namespace aco {
+uint64_t debug_flags = 0;
+
+static const struct debug_control aco_debug_options[] = {
+ {"validateir", DEBUG_VALIDATE},
+ {"validatera", DEBUG_VALIDATE_RA},
+ {"perfwarn", DEBUG_PERFWARN},
+ {NULL, 0}
+};
+
+static once_flag init_once_flag = ONCE_FLAG_INIT;
+
+static void init()
+{
+ debug_flags = parse_debug_string(getenv("ACO_DEBUG"), aco_debug_options);
+
+ #ifndef NDEBUG
+ /* enable some flags by default on debug builds */
+ debug_flags |= aco::DEBUG_VALIDATE;
+ #endif
+}
+}
+
+void aco_compile_shader(unsigned shader_count,
+ struct nir_shader *const *shaders,
+ struct radv_shader_binary **binary,
+ struct radv_shader_info *info,
+ struct radv_nir_compiler_options *options)
+{
+ call_once(&aco::init_once_flag, aco::init);
+
+ ac_shader_config config = {0};
+ std::unique_ptr<aco::Program> program{new aco::Program};
+
+ /* Instruction Selection */
+ aco::select_program(program.get(), shader_count, shaders, &config, info, options);
+ if (options->dump_preoptir) {
+ std::cerr << "After Instruction Selection:\n";
+ aco_print_program(program.get(), stderr);
+ }
+ aco::validate(program.get(), stderr);
+
+ /* Boolean phi lowering */
+ aco::lower_bool_phis(program.get());
+ //std::cerr << "After Boolean Phi Lowering:\n";
+ //aco_print_program(program.get(), stderr);
+
+ aco::dominator_tree(program.get());
+
+ /* Optimization */
+ aco::value_numbering(program.get());
+ aco::optimize(program.get());
+ aco::validate(program.get(), stderr);
+
+ aco::setup_reduce_temp(program.get());
+ aco::insert_exec_mask(program.get());
+ aco::validate(program.get(), stderr);
+
+ aco::live live_vars = aco::live_var_analysis(program.get(), options);
+ aco::spill(program.get(), live_vars, options);
+
+ //std::cerr << "Before Schedule:\n";
+ //aco_print_program(program.get(), stderr);
+ aco::schedule_program(program.get(), live_vars);
+
+ /* Register Allocation */
+ aco::register_allocation(program.get(), live_vars.live_out);
+ if (options->dump_shader) {
+ std::cerr << "After RA:\n";
+ aco_print_program(program.get(), stderr);
+ }
+
+ if (aco::validate_ra(program.get(), options, stderr)) {
+ std::cerr << "Program after RA validation failure:\n";
+ aco_print_program(program.get(), stderr);
+ abort();
+ }
+
+ aco::ssa_elimination(program.get());
+ /* Lower to HW Instructions */
+ aco::lower_to_hw_instr(program.get());
+ //std::cerr << "After Eliminate Pseudo Instr:\n";
+ //aco_print_program(program.get(), stderr);
+
+ /* Insert Waitcnt */
+ aco::insert_wait_states(program.get());
+ aco::insert_NOPs(program.get());
+
+ //std::cerr << "After Insert-Waitcnt:\n";
+ //aco_print_program(program.get(), stderr);
+
+ /* Assembly */
+ std::vector<uint32_t> code;
+ unsigned exec_size = aco::emit_program(program.get(), code);
+
+ bool get_disasm = options->dump_shader;
+#ifndef NDEBUG
+ get_disasm |= options->record_llvm_ir;
+#endif
+
+ size_t size = 0;
+
+ std::string disasm;
+ if (get_disasm) {
+ std::ostringstream stream;
+ aco::print_asm(program.get(), code, exec_size / 4u, options->family, stream);
+ stream << '\0';
+ disasm = stream.str();
+ size += disasm.size();
+ }
+
+ size += code.size() * sizeof(uint32_t) + sizeof(radv_shader_binary_legacy);
+ radv_shader_binary_legacy* legacy_binary = (radv_shader_binary_legacy*) malloc(size);
+
+ legacy_binary->base.type = RADV_BINARY_TYPE_LEGACY;
+ legacy_binary->base.stage = shaders[shader_count-1]->info.stage;
+ legacy_binary->base.is_gs_copy_shader = false;
+ legacy_binary->base.total_size = size;
+
+ memcpy(legacy_binary->data, code.data(), code.size() * sizeof(uint32_t));
+ legacy_binary->exec_size = exec_size;
+ legacy_binary->code_size = code.size() * sizeof(uint32_t);
+
+ legacy_binary->config = config;
+ legacy_binary->disasm_size = 0;
+ legacy_binary->llvm_ir_size = 0;
+
+ if (get_disasm) {
+ disasm.copy((char*) legacy_binary->data + legacy_binary->code_size, disasm.size());
+ legacy_binary->disasm_size = disasm.size() - 1;
+ }
+
+ *binary = (radv_shader_binary*) legacy_binary;
+}
diff --git a/src/amd/compiler/aco_interface.h b/src/amd/compiler/aco_interface.h
new file mode 100644
index 00000000000..1425a0997a0
--- /dev/null
+++ b/src/amd/compiler/aco_interface.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef ACO_INTERFACE_H
+#define ACO_INTERFACE_H
+
+#include "nir.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ac_shader_config;
+
+void aco_compile_shader(unsigned shader_count,
+ struct nir_shader *const *shaders,
+ struct radv_shader_binary** binary,
+ struct radv_shader_info *info,
+ struct radv_nir_compiler_options *options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/amd/compiler/aco_ir.h b/src/amd/compiler/aco_ir.h
new file mode 100644
index 00000000000..663635e5b93
--- /dev/null
+++ b/src/amd/compiler/aco_ir.h
@@ -0,0 +1,1169 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef ACO_IR_H
+#define ACO_IR_H
+
+#include <vector>
+#include <set>
+#include <bitset>
+#include <memory>
+
+#include "nir.h"
+#include "ac_binary.h"
+#include "amd_family.h"
+#include "aco_opcodes.h"
+#include "aco_util.h"
+
+struct radv_nir_compiler_options;
+struct radv_shader_info;
+
+namespace aco {
+
+extern uint64_t debug_flags;
+
+enum {
+ DEBUG_VALIDATE = 0x1,
+ DEBUG_VALIDATE_RA = 0x2,
+ DEBUG_PERFWARN = 0x4,
+};
+
+/**
+ * Representation of the instruction's microcode encoding format
+ * Note: Some Vector ALU Formats can be combined, such that:
+ * - VOP2* | VOP3A represents a VOP2 instruction in VOP3A encoding
+ * - VOP2* | DPP represents a VOP2 instruction with data parallel primitive.
+ * - VOP2* | SDWA represents a VOP2 instruction with sub-dword addressing.
+ *
+ * (*) The same is applicable for VOP1 and VOPC instructions.
+ */
+enum class Format : std::uint16_t {
+ /* Pseudo Instruction Format */
+ PSEUDO = 0,
+ /* Scalar ALU & Control Formats */
+ SOP1 = 1,
+ SOP2 = 2,
+ SOPK = 3,
+ SOPP = 4,
+ SOPC = 5,
+ /* Scalar Memory Format */
+ SMEM = 6,
+ /* LDS/GDS Format */
+ DS = 8,
+ /* Vector Memory Buffer Formats */
+ MTBUF = 9,
+ MUBUF = 10,
+ /* Vector Memory Image Format */
+ MIMG = 11,
+ /* Export Format */
+ EXP = 12,
+ /* Flat Formats */
+ FLAT = 13,
+ GLOBAL = 14,
+ SCRATCH = 15,
+
+ PSEUDO_BRANCH = 16,
+ PSEUDO_BARRIER = 17,
+ PSEUDO_REDUCTION = 18,
+
+ /* Vector ALU Formats */
+ VOP1 = 1 << 8,
+ VOP2 = 1 << 9,
+ VOPC = 1 << 10,
+ VOP3 = 1 << 11,
+ VOP3A = 1 << 11,
+ VOP3B = 1 << 11,
+ VOP3P = 1 << 12,
+ /* Vector Parameter Interpolation Format */
+ VINTRP = 1 << 13,
+ DPP = 1 << 14,
+ SDWA = 1 << 15,
+};
+
+enum barrier_interaction {
+ barrier_none = 0,
+ barrier_buffer = 0x1,
+ barrier_image = 0x2,
+ barrier_atomic = 0x4,
+ barrier_shared = 0x8,
+ barrier_count = 4,
+};
+
+constexpr Format asVOP3(Format format) {
+ return (Format) ((uint32_t) Format::VOP3 | (uint32_t) format);
+};
+
+enum class RegType {
+ none = 0,
+ sgpr,
+ vgpr,
+ linear_vgpr,
+};
+
+struct RegClass {
+
+ enum RC : uint8_t {
+ s1 = 1,
+ s2 = 2,
+ s3 = 3,
+ s4 = 4,
+ s6 = 6,
+ s8 = 8,
+ s16 = 16,
+ v1 = s1 | (1 << 5),
+ v2 = s2 | (1 << 5),
+ v3 = s3 | (1 << 5),
+ v4 = s4 | (1 << 5),
+ v5 = 5 | (1 << 5),
+ v6 = 6 | (1 << 5),
+ v7 = 7 | (1 << 5),
+ v8 = 8 | (1 << 5),
+ /* these are used for WWM and spills to vgpr */
+ v1_linear = v1 | (1 << 6),
+ v2_linear = v2 | (1 << 6),
+ };
+
+ RegClass() = default;
+ constexpr RegClass(RC rc)
+ : rc(rc) {}
+ constexpr RegClass(RegType type, unsigned size)
+ : rc((RC) ((type == RegType::vgpr ? 1 << 5 : 0) | size)) {}
+
+ constexpr operator RC() const { return rc; }
+ explicit operator bool() = delete;
+
+ constexpr RegType type() const { return rc <= RC::s16 ? RegType::sgpr : RegType::vgpr; }
+ constexpr unsigned size() const { return (unsigned) rc & 0x1F; }
+ constexpr bool is_linear() const { return rc <= RC::s16 || rc & (1 << 6); }
+ constexpr RegClass as_linear() const { return RegClass((RC) (rc | (1 << 6))); }
+
+private:
+ RC rc;
+};
+
+/* transitional helper expressions */
+static constexpr RegClass s1{RegClass::s1};
+static constexpr RegClass s2{RegClass::s2};
+static constexpr RegClass s3{RegClass::s3};
+static constexpr RegClass s4{RegClass::s4};
+static constexpr RegClass s8{RegClass::s8};
+static constexpr RegClass s16{RegClass::s16};
+static constexpr RegClass v1{RegClass::v1};
+static constexpr RegClass v2{RegClass::v2};
+static constexpr RegClass v3{RegClass::v3};
+static constexpr RegClass v4{RegClass::v4};
+static constexpr RegClass v5{RegClass::v5};
+static constexpr RegClass v6{RegClass::v6};
+static constexpr RegClass v7{RegClass::v7};
+static constexpr RegClass v8{RegClass::v8};
+
+/**
+ * Temp Class
+ * Each temporary virtual register has a
+ * register class (i.e. size and type)
+ * and SSA id.
+ */
+struct Temp {
+ Temp() = default;
+ constexpr Temp(uint32_t id, RegClass cls) noexcept
+ : id_(id), reg_class(cls) {}
+
+ constexpr uint32_t id() const noexcept { return id_; }
+ constexpr RegClass regClass() const noexcept { return reg_class; }
+
+ constexpr unsigned size() const noexcept { return reg_class.size(); }
+ constexpr RegType type() const noexcept { return reg_class.type(); }
+ constexpr bool is_linear() const noexcept { return reg_class.is_linear(); }
+
+ constexpr bool operator <(Temp other) const noexcept { return id() < other.id(); }
+ constexpr bool operator==(Temp other) const noexcept { return id() == other.id(); }
+ constexpr bool operator!=(Temp other) const noexcept { return id() != other.id(); }
+
+private:
+ uint32_t id_:24;
+ RegClass reg_class;
+};
+
+/**
+ * PhysReg
+ * Represents the physical register for each
+ * Operand and Definition.
+ */
+struct PhysReg {
+ constexpr PhysReg() = default;
+ explicit constexpr PhysReg(unsigned r) : reg(r) {}
+ constexpr operator unsigned() const { return reg; }
+
+ uint16_t reg = 0;
+};
+
+/* helper expressions for special registers */
+static constexpr PhysReg m0{124};
+static constexpr PhysReg vcc{106};
+static constexpr PhysReg exec{126};
+static constexpr PhysReg exec_lo{126};
+static constexpr PhysReg exec_hi{127};
+static constexpr PhysReg scc{253};
+
+/**
+ * Operand Class
+ * Initially, each Operand refers to either
+ * a temporary virtual register
+ * or to a constant value
+ * Temporary registers get mapped to physical register during RA
+ * Constant values are inlined into the instruction sequence.
+ */
+class Operand final
+{
+public:
+ constexpr Operand()
+ : reg_(PhysReg{128}), isTemp_(false), isFixed_(true), isConstant_(false),
+ isKill_(false), isUndef_(true), isFirstKill_(false), is64BitConst_(false) {}
+
+ explicit Operand(Temp r) noexcept
+ {
+ data_.temp = r;
+ if (r.id()) {
+ isTemp_ = true;
+ } else {
+ isUndef_ = true;
+ setFixed(PhysReg{128});
+ }
+ };
+ explicit Operand(uint32_t v) noexcept
+ {
+ data_.i = v;
+ isConstant_ = true;
+ if (v <= 64)
+ setFixed(PhysReg{128 + v});
+ else if (v >= 0xFFFFFFF0) /* [-16 .. -1] */
+ setFixed(PhysReg{192 - v});
+ else if (v == 0x3f000000) /* 0.5 */
+ setFixed(PhysReg{240});
+ else if (v == 0xbf000000) /* -0.5 */
+ setFixed(PhysReg{241});
+ else if (v == 0x3f800000) /* 1.0 */
+ setFixed(PhysReg{242});
+ else if (v == 0xbf800000) /* -1.0 */
+ setFixed(PhysReg{243});
+ else if (v == 0x40000000) /* 2.0 */
+ setFixed(PhysReg{244});
+ else if (v == 0xc0000000) /* -2.0 */
+ setFixed(PhysReg{245});
+ else if (v == 0x40800000) /* 4.0 */
+ setFixed(PhysReg{246});
+ else if (v == 0xc0800000) /* -4.0 */
+ setFixed(PhysReg{247});
+ else if (v == 0x3e22f983) /* 1/(2*PI) */
+ setFixed(PhysReg{248});
+ else /* Literal Constant */
+ setFixed(PhysReg{255});
+ };
+ explicit Operand(uint64_t v) noexcept
+ {
+ isConstant_ = true;
+ is64BitConst_ = true;
+ if (v <= 64)
+ setFixed(PhysReg{128 + (uint32_t) v});
+ else if (v >= 0xFFFFFFFFFFFFFFF0) /* [-16 .. -1] */
+ setFixed(PhysReg{192 - (uint32_t) v});
+ else if (v == 0x3FE0000000000000) /* 0.5 */
+ setFixed(PhysReg{240});
+ else if (v == 0xBFE0000000000000) /* -0.5 */
+ setFixed(PhysReg{241});
+ else if (v == 0x3FF0000000000000) /* 1.0 */
+ setFixed(PhysReg{242});
+ else if (v == 0xBFF0000000000000) /* -1.0 */
+ setFixed(PhysReg{243});
+ else if (v == 0x4000000000000000) /* 2.0 */
+ setFixed(PhysReg{244});
+ else if (v == 0xC000000000000000) /* -2.0 */
+ setFixed(PhysReg{245});
+ else if (v == 0x4010000000000000) /* 4.0 */
+ setFixed(PhysReg{246});
+ else if (v == 0xC010000000000000) /* -4.0 */
+ setFixed(PhysReg{247});
+ else if (v == 0x3fc45f306dc9c882) /* 1/(2*PI) */
+ setFixed(PhysReg{248});
+ else { /* Literal Constant: we don't know if it is a long or double.*/
+ isConstant_ = 0;
+ assert(false && "attempt to create a 64-bit literal constant");
+ }
+ };
+ explicit Operand(RegClass type) noexcept
+ {
+ isUndef_ = true;
+ data_.temp = Temp(0, type);
+ setFixed(PhysReg{128});
+ };
+ explicit Operand(PhysReg reg, RegClass type) noexcept
+ {
+ data_.temp = Temp(0, type);
+ setFixed(reg);
+ }
+
+ constexpr bool isTemp() const noexcept
+ {
+ return isTemp_;
+ }
+
+ constexpr void setTemp(Temp t) noexcept {
+ assert(!isConstant_);
+ isTemp_ = true;
+ data_.temp = t;
+ }
+
+ constexpr Temp getTemp() const noexcept
+ {
+ return data_.temp;
+ }
+
+ constexpr uint32_t tempId() const noexcept
+ {
+ return data_.temp.id();
+ }
+
+ constexpr bool hasRegClass() const noexcept
+ {
+ return isTemp() || isUndefined();
+ }
+
+ constexpr RegClass regClass() const noexcept
+ {
+ return data_.temp.regClass();
+ }
+
+ constexpr unsigned size() const noexcept
+ {
+ if (isConstant())
+ return is64BitConst_ ? 2 : 1;
+ else
+ return data_.temp.size();
+ }
+
+ constexpr bool isFixed() const noexcept
+ {
+ return isFixed_;
+ }
+
+ constexpr PhysReg physReg() const noexcept
+ {
+ return reg_;
+ }
+
+ constexpr void setFixed(PhysReg reg) noexcept
+ {
+ isFixed_ = reg != unsigned(-1);
+ reg_ = reg;
+ }
+
+ constexpr bool isConstant() const noexcept
+ {
+ return isConstant_;
+ }
+
+ constexpr bool isLiteral() const noexcept
+ {
+ return isConstant() && reg_ == 255;
+ }
+
+ constexpr bool isUndefined() const noexcept
+ {
+ return isUndef_;
+ }
+
+ constexpr uint32_t constantValue() const noexcept
+ {
+ return data_.i;
+ }
+
+ constexpr bool constantEquals(uint32_t cmp) const noexcept
+ {
+ return isConstant() && constantValue() == cmp;
+ }
+
+ constexpr void setKill(bool flag) noexcept
+ {
+ isKill_ = flag;
+ if (!flag)
+ setFirstKill(false);
+ }
+
+ constexpr bool isKill() const noexcept
+ {
+ return isKill_ || isFirstKill();
+ }
+
+ constexpr void setFirstKill(bool flag) noexcept
+ {
+ isFirstKill_ = flag;
+ if (flag)
+ setKill(flag);
+ }
+
+ /* When there are multiple operands killing the same temporary,
+ * isFirstKill() is only returns true for the first one. */
+ constexpr bool isFirstKill() const noexcept
+ {
+ return isFirstKill_;
+ }
+
+private:
+ union {
+ uint32_t i;
+ float f;
+ Temp temp = Temp(0, s1);
+ } data_;
+ PhysReg reg_;
+ union {
+ struct {
+ uint8_t isTemp_:1;
+ uint8_t isFixed_:1;
+ uint8_t isConstant_:1;
+ uint8_t isKill_:1;
+ uint8_t isUndef_:1;
+ uint8_t isFirstKill_:1;
+ uint8_t is64BitConst_:1;
+ };
+ /* can't initialize bit-fields in c++11, so work around using a union */
+ uint8_t control_ = 0;
+ };
+};
+
+/**
+ * Definition Class
+ * Definitions are the results of Instructions
+ * and refer to temporary virtual registers
+ * which are later mapped to physical registers
+ */
+class Definition final
+{
+public:
+ constexpr Definition() : temp(Temp(0, s1)), reg_(0), isFixed_(0), hasHint_(0), isKill_(0) {}
+ Definition(uint32_t index, RegClass type) noexcept
+ : temp(index, type) {}
+ explicit Definition(Temp tmp) noexcept
+ : temp(tmp) {}
+ Definition(PhysReg reg, RegClass type) noexcept
+ : temp(Temp(0, type))
+ {
+ setFixed(reg);
+ }
+ Definition(uint32_t tmpId, PhysReg reg, RegClass type) noexcept
+ : temp(Temp(tmpId, type))
+ {
+ setFixed(reg);
+ }
+
+ constexpr bool isTemp() const noexcept
+ {
+ return tempId() > 0;
+ }
+
+ constexpr Temp getTemp() const noexcept
+ {
+ return temp;
+ }
+
+ constexpr uint32_t tempId() const noexcept
+ {
+ return temp.id();
+ }
+
+ constexpr void setTemp(Temp t) noexcept {
+ temp = t;
+ }
+
+ constexpr RegClass regClass() const noexcept
+ {
+ return temp.regClass();
+ }
+
+ constexpr unsigned size() const noexcept
+ {
+ return temp.size();
+ }
+
+ constexpr bool isFixed() const noexcept
+ {
+ return isFixed_;
+ }
+
+ constexpr PhysReg physReg() const noexcept
+ {
+ return reg_;
+ }
+
+ constexpr void setFixed(PhysReg reg) noexcept
+ {
+ isFixed_ = 1;
+ reg_ = reg;
+ }
+
+ constexpr void setHint(PhysReg reg) noexcept
+ {
+ hasHint_ = 1;
+ reg_ = reg;
+ }
+
+ constexpr bool hasHint() const noexcept
+ {
+ return hasHint_;
+ }
+
+ constexpr void setKill(bool flag) noexcept
+ {
+ isKill_ = flag;
+ }
+
+ constexpr bool isKill() const noexcept
+ {
+ return isKill_;
+ }
+
+private:
+ Temp temp = Temp(0, s1);
+ PhysReg reg_;
+ union {
+ struct {
+ uint8_t isFixed_:1;
+ uint8_t hasHint_:1;
+ uint8_t isKill_:1;
+ };
+ /* can't initialize bit-fields in c++11, so work around using a union */
+ uint8_t control_ = 0;
+ };
+};
+
+class Block;
+
+struct Instruction {
+ aco_opcode opcode;
+ Format format;
+
+ aco::span<Operand> operands;
+ aco::span<Definition> definitions;
+
+ constexpr bool isVALU() const noexcept
+ {
+ return ((uint16_t) format & (uint16_t) Format::VOP1) == (uint16_t) Format::VOP1
+ || ((uint16_t) format & (uint16_t) Format::VOP2) == (uint16_t) Format::VOP2
+ || ((uint16_t) format & (uint16_t) Format::VOPC) == (uint16_t) Format::VOPC
+ || ((uint16_t) format & (uint16_t) Format::VOP3A) == (uint16_t) Format::VOP3A
+ || ((uint16_t) format & (uint16_t) Format::VOP3B) == (uint16_t) Format::VOP3B
+ || ((uint16_t) format & (uint16_t) Format::VOP3P) == (uint16_t) Format::VOP3P;
+ }
+
+ constexpr bool isSALU() const noexcept
+ {
+ return format == Format::SOP1 ||
+ format == Format::SOP2 ||
+ format == Format::SOPC ||
+ format == Format::SOPK ||
+ format == Format::SOPP;
+ }
+
+ constexpr bool isVMEM() const noexcept
+ {
+ return format == Format::MTBUF ||
+ format == Format::MUBUF ||
+ format == Format::MIMG;
+ }
+
+ constexpr bool isDPP() const noexcept
+ {
+ return (uint16_t) format & (uint16_t) Format::DPP;
+ }
+
+ constexpr bool isVOP3() const noexcept
+ {
+ return ((uint16_t) format & (uint16_t) Format::VOP3A) ||
+ ((uint16_t) format & (uint16_t) Format::VOP3B) ||
+ format == Format::VOP3P;
+ }
+
+ constexpr bool isSDWA() const noexcept
+ {
+ return (uint16_t) format & (uint16_t) Format::SDWA;
+ }
+
+ constexpr bool isFlatOrGlobal() const noexcept
+ {
+ return format == Format::FLAT || format == Format::GLOBAL;
+ }
+};
+
+struct SOPK_instruction : public Instruction {
+ uint16_t imm;
+};
+
+struct SOPP_instruction : public Instruction {
+ uint32_t imm;
+ int block;
+};
+
+struct SOPC_instruction : public Instruction {
+};
+
+struct SOP1_instruction : public Instruction {
+};
+
+struct SOP2_instruction : public Instruction {
+};
+
+/**
+ * Scalar Memory Format:
+ * For s_(buffer_)load_dword*:
+ * Operand(0): SBASE - SGPR-pair which provides base address
+ * Operand(1): Offset - immediate (un)signed offset or SGPR
+ * Operand(2) / Definition(0): SDATA - SGPR for read / write result
+ * Operand(n-1): SOffset - SGPR offset (Vega only)
+ *
+ * Having no operands is also valid for instructions such as s_dcache_inv.
+ *
+ */
+struct SMEM_instruction : public Instruction {
+ bool glc; /* VI+: globally coherent */
+ bool dlc; /* NAVI: device level coherent */
+ bool nv; /* VEGA only: Non-volatile */
+ bool can_reorder;
+ bool disable_wqm;
+ barrier_interaction barrier;
+};
+
+struct VOP1_instruction : public Instruction {
+};
+
+struct VOP2_instruction : public Instruction {
+};
+
+struct VOPC_instruction : public Instruction {
+};
+
+struct VOP3A_instruction : public Instruction {
+ bool abs[3];
+ bool opsel[3];
+ bool clamp;
+ unsigned omod;
+ bool neg[3];
+};
+
+/**
+ * Data Parallel Primitives Format:
+ * This format can be used for VOP1, VOP2 or VOPC instructions.
+ * The swizzle applies to the src0 operand.
+ *
+ */
+struct DPP_instruction : public Instruction {
+ uint16_t dpp_ctrl;
+ uint8_t row_mask;
+ uint8_t bank_mask;
+ bool abs[2];
+ bool neg[2];
+ bool bound_ctrl;
+};
+
+struct Interp_instruction : public Instruction {
+ unsigned attribute;
+ unsigned component;
+};
+
+/**
+ * Local and Global Data Sharing instructions
+ * Operand(0): ADDR - VGPR which supplies the address.
+ * Operand(1): DATA0 - First data VGPR.
+ * Operand(2): DATA1 - Second data VGPR.
+ * Operand(n-1): M0 - LDS size.
+ * Definition(0): VDST - Destination VGPR when results returned to VGPRs.
+ *
+ */
+struct DS_instruction : public Instruction {
+ int16_t offset0;
+ int8_t offset1;
+ bool gds;
+};
+
+/**
+ * Vector Memory Untyped-buffer Instructions
+ * Operand(0): VADDR - Address source. Can carry an index and/or offset
+ * Operand(1): SRSRC - Specifies which SGPR supplies T# (resource constant)
+ * Operand(2): SOFFSET - SGPR to supply unsigned byte offset. (SGPR, M0, or inline constant)
+ * Operand(3) / Definition(0): VDATA - Vector GPR for write result / read data
+ *
+ */
+struct MUBUF_instruction : public Instruction {
+ unsigned offset; /* Unsigned byte offset - 12 bit */
+ bool offen; /* Supply an offset from VGPR (VADDR) */
+ bool idxen; /* Supply an index from VGPR (VADDR) */
+ bool glc; /* globally coherent */
+ bool dlc; /* NAVI: device level coherent */
+ bool slc; /* system level coherent */
+ bool tfe; /* texture fail enable */
+ bool lds; /* Return read-data to LDS instead of VGPRs */
+ bool disable_wqm; /* Require an exec mask without helper invocations */
+ bool can_reorder;
+ barrier_interaction barrier;
+};
+
+/**
+ * Vector Memory Typed-buffer Instructions
+ * Operand(0): VADDR - Address source. Can carry an index and/or offset
+ * Operand(1): SRSRC - Specifies which SGPR supplies T# (resource constant)
+ * Operand(2): SOFFSET - SGPR to supply unsigned byte offset. (SGPR, M0, or inline constant)
+ * Operand(3) / Definition(0): VDATA - Vector GPR for write result / read data
+ *
+ */
+struct MTBUF_instruction : public Instruction {
+ union {
+ struct {
+ uint8_t dfmt : 4; /* Data Format of data in memory buffer */
+ uint8_t nfmt : 3; /* Numeric format of data in memory */
+ };
+ uint8_t img_format; /* Buffer or image format as used by GFX10 */
+ };
+ unsigned offset; /* Unsigned byte offset - 12 bit */
+ bool offen; /* Supply an offset from VGPR (VADDR) */
+ bool idxen; /* Supply an index from VGPR (VADDR) */
+ bool glc; /* globally coherent */
+ bool dlc; /* NAVI: device level coherent */
+ bool slc; /* system level coherent */
+ bool tfe; /* texture fail enable */
+ bool disable_wqm; /* Require an exec mask without helper invocations */
+ bool can_reorder;
+ barrier_interaction barrier;
+};
+
+/**
+ * Vector Memory Image Instructions
+ * Operand(0): VADDR - Address source. Can carry an offset or an index.
+ * Operand(1): SRSRC - Scalar GPR that specifies the resource constant.
+ * Operand(2): SSAMP - Scalar GPR that specifies sampler constant.
+ * Operand(3) / Definition(0): VDATA - Vector GPR for read / write result.
+ *
+ */
+struct MIMG_instruction : public Instruction {
+ unsigned dmask; /* Data VGPR enable mask */
+ bool unrm; /* Force address to be un-normalized */
+ bool dlc; /* NAVI: device level coherent */
+ bool glc; /* globally coherent */
+ bool slc; /* system level coherent */
+ bool tfe; /* texture fail enable */
+ bool da; /* declare an array */
+ bool lwe; /* Force data to be un-normalized */
+ bool r128; /* NAVI: Texture resource size */
+ bool a16; /* VEGA, NAVI: Address components are 16-bits */
+ bool d16; /* Convert 32-bit data to 16-bit data */
+ bool disable_wqm; /* Require an exec mask without helper invocations */
+ bool can_reorder;
+ barrier_interaction barrier;
+};
+
+/**
+ * Flat/Scratch/Global Instructions
+ * Operand(0): ADDR
+ * Operand(1): SADDR
+ * Operand(2) / Definition(0): DATA/VDST
+ *
+ */
+struct FLAT_instruction : public Instruction {
+ uint16_t offset; /* Vega only */
+ bool slc;
+ bool glc;
+ bool lds;
+ bool nv;
+};
+
+struct Export_instruction : public Instruction {
+ unsigned enabled_mask;
+ unsigned dest;
+ bool compressed;
+ bool done;
+ bool valid_mask;
+};
+
+struct Pseudo_instruction : public Instruction {
+ bool tmp_in_scc;
+ PhysReg scratch_sgpr; /* might not be valid if it's not needed */
+};
+
+struct Pseudo_branch_instruction : public Instruction {
+ /* target[0] is the block index of the branch target.
+ * For conditional branches, target[1] contains the fall-through alternative.
+ * A value of 0 means the target has not been initialized (BB0 cannot be a branch target).
+ */
+ uint32_t target[2];
+};
+
+struct Pseudo_barrier_instruction : public Instruction {
+};
+
+enum ReduceOp {
+ iadd32, iadd64,
+ imul32, imul64,
+ fadd32, fadd64,
+ fmul32, fmul64,
+ imin32, imin64,
+ imax32, imax64,
+ umin32, umin64,
+ umax32, umax64,
+ fmin32, fmin64,
+ fmax32, fmax64,
+ iand32, iand64,
+ ior32, ior64,
+ ixor32, ixor64,
+};
+
+/**
+ * Subgroup Reduction Instructions, everything except for the data to be
+ * reduced and the result as inserted by setup_reduce_temp().
+ * Operand(0): data to be reduced
+ * Operand(1): reduce temporary
+ * Operand(2): vector temporary
+ * Definition(0): result
+ * Definition(1): scalar temporary
+ * Definition(2): scalar identity temporary
+ * Definition(3): scc clobber
+ * Definition(4): vcc clobber
+ *
+ */
+struct Pseudo_reduction_instruction : public Instruction {
+ ReduceOp reduce_op;
+ unsigned cluster_size; // must be 0 for scans
+};
+
+struct instr_deleter_functor {
+ void operator()(void* p) {
+ free(p);
+ }
+};
+
+template<typename T>
+using aco_ptr = std::unique_ptr<T, instr_deleter_functor>;
+
+template<typename T>
+T* create_instruction(aco_opcode opcode, Format format, uint32_t num_operands, uint32_t num_definitions)
+{
+ std::size_t size = sizeof(T) + num_operands * sizeof(Operand) + num_definitions * sizeof(Definition);
+ char *data = (char*) calloc(1, size);
+ T* inst = (T*) data;
+
+ inst->opcode = opcode;
+ inst->format = format;
+
+ inst->operands = aco::span<Operand>((Operand*)(data + sizeof(T)), num_operands);
+ inst->definitions = aco::span<Definition>((Definition*)inst->operands.end(), num_definitions);
+
+ return inst;
+}
+
+constexpr bool is_phi(Instruction* instr)
+{
+ return instr->opcode == aco_opcode::p_phi || instr->opcode == aco_opcode::p_linear_phi;
+}
+
+static inline bool is_phi(aco_ptr<Instruction>& instr)
+{
+ return is_phi(instr.get());
+}
+
+constexpr barrier_interaction get_barrier_interaction(Instruction* instr)
+{
+ switch (instr->format) {
+ case Format::SMEM:
+ return static_cast<SMEM_instruction*>(instr)->barrier;
+ case Format::MUBUF:
+ return static_cast<MUBUF_instruction*>(instr)->barrier;
+ case Format::MIMG:
+ return static_cast<MIMG_instruction*>(instr)->barrier;
+ case Format::FLAT:
+ case Format::GLOBAL:
+ return barrier_buffer;
+ case Format::DS:
+ return barrier_shared;
+ default:
+ return barrier_none;
+ }
+}
+
+enum block_kind {
+ /* uniform indicates that leaving this block,
+ * all actives lanes stay active */
+ block_kind_uniform = 1 << 0,
+ block_kind_top_level = 1 << 1,
+ block_kind_loop_preheader = 1 << 2,
+ block_kind_loop_header = 1 << 3,
+ block_kind_loop_exit = 1 << 4,
+ block_kind_continue = 1 << 5,
+ block_kind_break = 1 << 6,
+ block_kind_continue_or_break = 1 << 7,
+ block_kind_discard = 1 << 8,
+ block_kind_branch = 1 << 9,
+ block_kind_merge = 1 << 10,
+ block_kind_invert = 1 << 11,
+ block_kind_uses_discard_if = 1 << 12,
+ block_kind_needs_lowering = 1 << 13,
+};
+
+
+struct RegisterDemand {
+ constexpr RegisterDemand() = default;
+ constexpr RegisterDemand(const int16_t v, const int16_t s) noexcept
+ : vgpr{v}, sgpr{s} {}
+ int16_t vgpr = 0;
+ int16_t sgpr = 0;
+
+ constexpr friend bool operator==(const RegisterDemand a, const RegisterDemand b) noexcept {
+ return a.vgpr == b.vgpr && a.sgpr == b.sgpr;
+ }
+
+ constexpr bool exceeds(const RegisterDemand other) const noexcept {
+ return vgpr > other.vgpr || sgpr > other.sgpr;
+ }
+
+ constexpr RegisterDemand operator+(const Temp t) const noexcept {
+ if (t.type() == RegType::sgpr)
+ return RegisterDemand( vgpr, sgpr + t.size() );
+ else
+ return RegisterDemand( vgpr + t.size(), sgpr );
+ }
+
+ constexpr RegisterDemand operator+(const RegisterDemand other) const noexcept {
+ return RegisterDemand(vgpr + other.vgpr, sgpr + other.sgpr);
+ }
+
+ constexpr RegisterDemand operator-(const RegisterDemand other) const noexcept {
+ return RegisterDemand(vgpr - other.vgpr, sgpr - other.sgpr);
+ }
+
+ constexpr RegisterDemand& operator+=(const RegisterDemand other) noexcept {
+ vgpr += other.vgpr;
+ sgpr += other.sgpr;
+ return *this;
+ }
+
+ constexpr RegisterDemand& operator-=(const RegisterDemand other) noexcept {
+ vgpr -= other.vgpr;
+ sgpr -= other.sgpr;
+ return *this;
+ }
+
+ constexpr RegisterDemand& operator+=(const Temp t) noexcept {
+ if (t.type() == RegType::sgpr)
+ sgpr += t.size();
+ else
+ vgpr += t.size();
+ return *this;
+ }
+
+ constexpr RegisterDemand& operator-=(const Temp t) noexcept {
+ if (t.type() == RegType::sgpr)
+ sgpr -= t.size();
+ else
+ vgpr -= t.size();
+ return *this;
+ }
+
+ constexpr void update(const RegisterDemand other) noexcept {
+ vgpr = std::max(vgpr, other.vgpr);
+ sgpr = std::max(sgpr, other.sgpr);
+ }
+
+};
+
+/* CFG */
+struct Block {
+ unsigned index;
+ unsigned offset = 0;
+ std::vector<aco_ptr<Instruction>> instructions;
+ std::vector<unsigned> logical_preds;
+ std::vector<unsigned> linear_preds;
+ std::vector<unsigned> logical_succs;
+ std::vector<unsigned> linear_succs;
+ RegisterDemand register_demand = RegisterDemand();
+ uint16_t loop_nest_depth = 0;
+ uint16_t kind = 0;
+ int logical_idom = -1;
+ int linear_idom = -1;
+ Temp live_out_exec = Temp();
+
+ /* this information is needed for predecessors to blocks with phis when
+ * moving out of ssa */
+ bool scc_live_out = false;
+ PhysReg scratch_sgpr = PhysReg(); /* only needs to be valid if scc_live_out != false */
+
+ Block(unsigned idx) : index(idx) {}
+ Block() : index(0) {}
+};
+
+using Stage = uint16_t;
+
+/* software stages */
+static constexpr Stage sw_vs = 1 << 0;
+static constexpr Stage sw_gs = 1 << 1;
+static constexpr Stage sw_tcs = 1 << 2;
+static constexpr Stage sw_tes = 1 << 3;
+static constexpr Stage sw_fs = 1 << 4;
+static constexpr Stage sw_cs = 1 << 5;
+static constexpr Stage sw_mask = 0x3f;
+
+/* hardware stages (can't be OR'd, just a mask for convenience when testing multiple) */
+static constexpr Stage hw_vs = 1 << 6;
+static constexpr Stage hw_es = 1 << 7;
+static constexpr Stage hw_gs = 1 << 8; /* not on GFX9. combined into ES on GFX9 (and GFX10/legacy). */
+static constexpr Stage hw_ls = 1 << 9;
+static constexpr Stage hw_hs = 1 << 10; /* not on GFX9. combined into LS on GFX9 (and GFX10/legacy). */
+static constexpr Stage hw_fs = 1 << 11;
+static constexpr Stage hw_cs = 1 << 12;
+static constexpr Stage hw_mask = 0x7f << 6;
+
+/* possible settings of Program::stage */
+static constexpr Stage vertex_vs = sw_vs | hw_vs;
+static constexpr Stage fragment_fs = sw_fs | hw_fs;
+static constexpr Stage compute_cs = sw_cs | hw_cs;
+static constexpr Stage tess_eval_vs = sw_tes | hw_vs;
+/* GFX10/NGG */
+static constexpr Stage ngg_vertex_gs = sw_vs | hw_gs;
+static constexpr Stage ngg_vertex_geometry_gs = sw_vs | sw_gs | hw_gs;
+static constexpr Stage ngg_tess_eval_geometry_gs = sw_tes | sw_gs | hw_gs;
+static constexpr Stage ngg_vertex_tess_control_hs = sw_vs | sw_tcs | hw_hs;
+/* GFX9 (and GFX10 if NGG isn't used) */
+static constexpr Stage vertex_geometry_es = sw_vs | sw_gs | hw_es;
+static constexpr Stage vertex_tess_control_ls = sw_vs | sw_tcs | hw_ls;
+static constexpr Stage tess_eval_geometry_es = sw_tes | sw_gs | hw_es;
+/* pre-GFX9 */
+static constexpr Stage vertex_ls = sw_vs | hw_ls; /* vertex before tesselation control */
+static constexpr Stage tess_control_hs = sw_tcs | hw_hs;
+static constexpr Stage tess_eval_es = sw_tes | hw_gs; /* tesselation evaluation before GS */
+static constexpr Stage geometry_gs = sw_gs | hw_gs;
+
+class Program final {
+public:
+ std::vector<Block> blocks;
+ RegisterDemand max_reg_demand = RegisterDemand();
+ uint16_t sgpr_limit = 0;
+ uint16_t num_waves = 0;
+ ac_shader_config* config;
+ struct radv_shader_info *info;
+ enum chip_class chip_class;
+ enum radeon_family family;
+ Stage stage; /* Stage */
+ bool needs_exact = false; /* there exists an instruction with disable_wqm = true */
+ bool needs_wqm = false; /* there exists a p_wqm instruction */
+ bool wb_smem_l1_on_end = false;
+
+ std::vector<uint8_t> constant_data;
+
+ uint32_t allocateId()
+ {
+ assert(allocationID <= 16777215);
+ return allocationID++;
+ }
+
+ uint32_t peekAllocationId()
+ {
+ return allocationID;
+ }
+
+ void setAllocationId(uint32_t id)
+ {
+ allocationID = id;
+ }
+
+ Block* create_and_insert_block() {
+ blocks.emplace_back(blocks.size());
+ return &blocks.back();
+ }
+
+ Block* insert_block(Block&& block) {
+ block.index = blocks.size();
+ blocks.emplace_back(std::move(block));
+ return &blocks.back();
+ }
+
+private:
+ uint32_t allocationID = 1;
+};
+
+struct live {
+ /* live temps out per block */
+ std::vector<std::set<Temp>> live_out;
+ /* register demand (sgpr/vgpr) per instruction per block */
+ std::vector<std::vector<RegisterDemand>> register_demand;
+};
+
+void select_program(Program *program,
+ unsigned shader_count,
+ struct nir_shader *const *shaders,
+ ac_shader_config* config,
+ struct radv_shader_info *info,
+ struct radv_nir_compiler_options *options);
+
+void lower_wqm(Program* program, live& live_vars,
+ const struct radv_nir_compiler_options *options);
+void lower_bool_phis(Program* program);
+void update_vgpr_sgpr_demand(Program* program, const RegisterDemand new_demand);
+live live_var_analysis(Program* program, const struct radv_nir_compiler_options *options);
+std::vector<uint16_t> dead_code_analysis(Program *program);
+void dominator_tree(Program* program);
+void insert_exec_mask(Program *program);
+void value_numbering(Program* program);
+void optimize(Program* program);
+void setup_reduce_temp(Program* program);
+void lower_to_cssa(Program* program, live& live_vars, const struct radv_nir_compiler_options *options);
+void register_allocation(Program *program, std::vector<std::set<Temp>> live_out_per_block);
+void ssa_elimination(Program* program);
+void lower_to_hw_instr(Program* program);
+void schedule_program(Program* program, live& live_vars);
+void spill(Program* program, live& live_vars, const struct radv_nir_compiler_options *options);
+void insert_wait_states(Program* program);
+void insert_NOPs(Program* program);
+unsigned emit_program(Program* program, std::vector<uint32_t>& code);
+void print_asm(Program *program, std::vector<uint32_t>& binary, unsigned exec_size,
+ enum radeon_family family, std::ostream& out);
+void validate(Program* program, FILE *output);
+bool validate_ra(Program* program, const struct radv_nir_compiler_options *options, FILE *output);
+#ifndef NDEBUG
+void perfwarn(bool cond, const char *msg, Instruction *instr=NULL);
+#else
+#define perfwarn(program, cond, msg, ...)
+#endif
+
+void aco_print_instr(Instruction *instr, FILE *output);
+void aco_print_program(Program *program, FILE *output);
+
+typedef struct {
+ const int16_t opcode_gfx9[static_cast<int>(aco_opcode::num_opcodes)];
+ const int16_t opcode_gfx10[static_cast<int>(aco_opcode::num_opcodes)];
+ const std::bitset<static_cast<int>(aco_opcode::num_opcodes)> can_use_input_modifiers;
+ const std::bitset<static_cast<int>(aco_opcode::num_opcodes)> can_use_output_modifiers;
+ const char *name[static_cast<int>(aco_opcode::num_opcodes)];
+ const aco::Format format[static_cast<int>(aco_opcode::num_opcodes)];
+} Info;
+
+extern const Info instr_info;
+
+}
+
+#endif /* ACO_IR_H */
+
diff --git a/src/amd/compiler/aco_live_var_analysis.cpp b/src/amd/compiler/aco_live_var_analysis.cpp
new file mode 100644
index 00000000000..f99e57c8b3a
--- /dev/null
+++ b/src/amd/compiler/aco_live_var_analysis.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schürmann ([email protected])
+ * Bas Nieuwenhuizen ([email protected])
+ *
+ */
+
+#include "aco_ir.h"
+
+#include <set>
+#include <vector>
+
+#include "vulkan/radv_shader.h"
+
+namespace aco {
+namespace {
+
+void process_live_temps_per_block(Program *program, live& lives, Block* block,
+ std::set<unsigned>& worklist, std::vector<uint16_t>& phi_sgpr_ops)
+{
+ std::vector<RegisterDemand>& register_demand = lives.register_demand[block->index];
+ RegisterDemand new_demand;
+
+ register_demand.resize(block->instructions.size());
+ block->register_demand = RegisterDemand();
+
+ std::set<Temp> live_sgprs;
+ std::set<Temp> live_vgprs;
+
+ /* add the live_out_exec to live */
+ bool exec_live = false;
+ if (block->live_out_exec != Temp()) {
+ live_sgprs.insert(block->live_out_exec);
+ new_demand.sgpr += 2;
+ exec_live = true;
+ }
+
+ /* split the live-outs from this block into the temporary sets */
+ std::vector<std::set<Temp>>& live_temps = lives.live_out;
+ for (const Temp temp : live_temps[block->index]) {
+ const bool inserted = temp.is_linear()
+ ? live_sgprs.insert(temp).second
+ : live_vgprs.insert(temp).second;
+ if (inserted) {
+ new_demand += temp;
+ }
+ }
+ new_demand.sgpr -= phi_sgpr_ops[block->index];
+
+ /* traverse the instructions backwards */
+ for (int idx = block->instructions.size() -1; idx >= 0; idx--)
+ {
+ /* substract the 2 sgprs from exec */
+ if (exec_live)
+ assert(new_demand.sgpr >= 2);
+ register_demand[idx] = RegisterDemand(new_demand.vgpr, new_demand.sgpr - (exec_live ? 2 : 0));
+
+ Instruction *insn = block->instructions[idx].get();
+ /* KILL */
+ for (Definition& definition : insn->definitions) {
+ if (!definition.isTemp()) {
+ continue;
+ }
+
+ const Temp temp = definition.getTemp();
+ size_t n = 0;
+ if (temp.is_linear())
+ n = live_sgprs.erase(temp);
+ else
+ n = live_vgprs.erase(temp);
+
+ if (n) {
+ new_demand -= temp;
+ definition.setKill(false);
+ } else {
+ register_demand[idx] += temp;
+ definition.setKill(true);
+ }
+
+ if (definition.isFixed() && definition.physReg() == exec)
+ exec_live = false;
+ }
+
+ /* GEN */
+ if (insn->opcode == aco_opcode::p_phi ||
+ insn->opcode == aco_opcode::p_linear_phi) {
+ /* directly insert into the predecessors live-out set */
+ std::vector<unsigned>& preds = insn->opcode == aco_opcode::p_phi
+ ? block->logical_preds
+ : block->linear_preds;
+ for (unsigned i = 0; i < preds.size(); ++i)
+ {
+ Operand &operand = insn->operands[i];
+ if (!operand.isTemp()) {
+ continue;
+ }
+ /* check if we changed an already processed block */
+ const bool inserted = live_temps[preds[i]].insert(operand.getTemp()).second;
+ if (inserted) {
+ operand.setFirstKill(true);
+ worklist.insert(preds[i]);
+ if (insn->opcode == aco_opcode::p_phi && operand.getTemp().type() == RegType::sgpr)
+ phi_sgpr_ops[preds[i]] += operand.size();
+ }
+ }
+ } else if (insn->opcode == aco_opcode::p_logical_end) {
+ new_demand.sgpr += phi_sgpr_ops[block->index];
+ } else {
+ for (unsigned i = 0; i < insn->operands.size(); ++i)
+ {
+ Operand& operand = insn->operands[i];
+ if (!operand.isTemp()) {
+ continue;
+ }
+ const Temp temp = operand.getTemp();
+ const bool inserted = temp.is_linear()
+ ? live_sgprs.insert(temp).second
+ : live_vgprs.insert(temp).second;
+ if (inserted) {
+ operand.setFirstKill(true);
+ for (unsigned j = i + 1; j < insn->operands.size(); ++j) {
+ if (insn->operands[j].isTemp() && insn->operands[j].tempId() == operand.tempId()) {
+ insn->operands[j].setFirstKill(false);
+ insn->operands[j].setKill(true);
+ }
+ }
+ new_demand += temp;
+ } else {
+ operand.setKill(false);
+ }
+
+ if (operand.isFixed() && operand.physReg() == exec)
+ exec_live = true;
+ }
+ }
+
+ block->register_demand.update(register_demand[idx]);
+ }
+
+ /* now, we have the live-in sets and need to merge them into the live-out sets */
+ for (unsigned pred_idx : block->logical_preds) {
+ for (Temp vgpr : live_vgprs) {
+ auto it = live_temps[pred_idx].insert(vgpr);
+ if (it.second)
+ worklist.insert(pred_idx);
+ }
+ }
+
+ for (unsigned pred_idx : block->linear_preds) {
+ for (Temp sgpr : live_sgprs) {
+ auto it = live_temps[pred_idx].insert(sgpr);
+ if (it.second)
+ worklist.insert(pred_idx);
+ }
+ }
+
+ if (!(block->index != 0 || (live_vgprs.empty() && live_sgprs.empty()))) {
+ aco_print_program(program, stderr);
+ fprintf(stderr, "These temporaries are never defined or are defined after use:\n");
+ for (Temp vgpr : live_vgprs)
+ fprintf(stderr, "%%%d\n", vgpr.id());
+ for (Temp sgpr : live_sgprs)
+ fprintf(stderr, "%%%d\n", sgpr.id());
+ abort();
+ }
+
+ assert(block->index != 0 || new_demand == RegisterDemand());
+}
+} /* end namespace */
+
+void update_vgpr_sgpr_demand(Program* program, const RegisterDemand new_demand)
+{
+ // TODO: also take shared mem into account
+ const int16_t total_sgpr_regs = program->chip_class >= GFX8 ? 800 : 512;
+ const int16_t max_addressible_sgpr = program->sgpr_limit;
+ /* VGPRs are allocated in chunks of 4 */
+ const int16_t rounded_vgpr_demand = std::max<int16_t>(4, (new_demand.vgpr + 3) & ~3);
+ /* SGPRs are allocated in chunks of 16 between 8 and 104. VCC occupies the last 2 registers */
+ const int16_t rounded_sgpr_demand = std::min(std::max<int16_t>(8, (new_demand.sgpr + 2 + 7) & ~7), max_addressible_sgpr);
+ /* this won't compile, register pressure reduction necessary */
+ if (new_demand.vgpr > 256 || new_demand.sgpr > max_addressible_sgpr) {
+ program->num_waves = 0;
+ program->max_reg_demand = new_demand;
+ } else {
+ program->num_waves = std::min<uint16_t>(10,
+ std::min<uint16_t>(256 / rounded_vgpr_demand,
+ total_sgpr_regs / rounded_sgpr_demand));
+
+ program->max_reg_demand = { int16_t((256 / program->num_waves) & ~3), std::min<int16_t>(((total_sgpr_regs / program->num_waves) & ~7) - 2, max_addressible_sgpr)};
+ }
+}
+
+live live_var_analysis(Program* program,
+ const struct radv_nir_compiler_options *options)
+{
+ live result;
+ result.live_out.resize(program->blocks.size());
+ result.register_demand.resize(program->blocks.size());
+ std::set<unsigned> worklist;
+ std::vector<uint16_t> phi_sgpr_ops(program->blocks.size());
+ RegisterDemand new_demand;
+
+ /* this implementation assumes that the block idx corresponds to the block's position in program->blocks vector */
+ for (Block& block : program->blocks)
+ worklist.insert(block.index);
+ while (!worklist.empty()) {
+ std::set<unsigned>::reverse_iterator b_it = worklist.rbegin();
+ unsigned block_idx = *b_it;
+ worklist.erase(block_idx);
+ process_live_temps_per_block(program, result, &program->blocks[block_idx], worklist, phi_sgpr_ops);
+ new_demand.update(program->blocks[block_idx].register_demand);
+ }
+
+ /* calculate the program's register demand and number of waves */
+ update_vgpr_sgpr_demand(program, new_demand);
+
+ return result;
+}
+
+}
+
diff --git a/src/amd/compiler/aco_lower_bool_phis.cpp b/src/amd/compiler/aco_lower_bool_phis.cpp
new file mode 100644
index 00000000000..0c56ca07214
--- /dev/null
+++ b/src/amd/compiler/aco_lower_bool_phis.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright © 2019 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rhys Perry ([email protected])
+ *
+ */
+
+#include <map>
+
+#include "aco_ir.h"
+#include "aco_builder.h"
+
+
+namespace aco {
+
+struct phi_use {
+ Block *block;
+ unsigned phi_def;
+
+ bool operator<(const phi_use& other) const {
+ return std::make_tuple(block, phi_def) <
+ std::make_tuple(other.block, other.phi_def);
+ }
+};
+
+struct ssa_state {
+ std::map<unsigned, unsigned> latest;
+ std::map<unsigned, std::map<phi_use, uint64_t>> phis;
+};
+
+Operand get_ssa(Program *program, unsigned block_idx, ssa_state *state)
+{
+ while (true) {
+ auto pos = state->latest.find(block_idx);
+ if (pos != state->latest.end())
+ return Operand({pos->second, s2});
+
+ Block& block = program->blocks[block_idx];
+ size_t pred = block.linear_preds.size();
+ if (pred == 0) {
+ return Operand(s2);
+ } else if (pred == 1) {
+ block_idx = block.linear_preds[0];
+ continue;
+ } else {
+ unsigned res = program->allocateId();
+ state->latest[block_idx] = res;
+
+ aco_ptr<Pseudo_instruction> phi{create_instruction<Pseudo_instruction>(aco_opcode::p_linear_phi, Format::PSEUDO, pred, 1)};
+ for (unsigned i = 0; i < pred; i++) {
+ phi->operands[i] = get_ssa(program, block.linear_preds[i], state);
+ if (phi->operands[i].isTemp()) {
+ assert(i < 64);
+ state->phis[phi->operands[i].tempId()][(phi_use){&block, res}] |= (uint64_t)1 << i;
+ }
+ }
+ phi->definitions[0] = Definition(Temp{res, s2});
+ block.instructions.emplace(block.instructions.begin(), std::move(phi));
+
+ return Operand({res, s2});
+ }
+ }
+}
+
+void update_phi(Program *program, ssa_state *state, Block *block, unsigned phi_def, uint64_t operand_mask) {
+ for (auto& phi : block->instructions) {
+ if (phi->opcode != aco_opcode::p_phi && phi->opcode != aco_opcode::p_linear_phi)
+ break;
+ if (phi->opcode != aco_opcode::p_linear_phi)
+ continue;
+ if (phi->definitions[0].tempId() != phi_def)
+ continue;
+ assert(ffsll(operand_mask) <= phi->operands.size());
+
+ uint64_t operands = operand_mask;
+ while (operands) {
+ unsigned operand = u_bit_scan64(&operands);
+ Operand new_operand = get_ssa(program, block->linear_preds[operand], state);
+ phi->operands[operand] = new_operand;
+ if (!new_operand.isUndefined())
+ state->phis[new_operand.tempId()][(phi_use){block, phi_def}] |= (uint64_t)1 << operand;
+ }
+ return;
+ }
+ assert(false);
+}
+
+Temp write_ssa(Program *program, Block *block, ssa_state *state, unsigned previous) {
+ unsigned id = program->allocateId();
+ state->latest[block->index] = id;
+
+ /* update phis */
+ if (previous) {
+ std::map<phi_use, uint64_t> phis;
+ phis.swap(state->phis[previous]);
+ for (auto& phi : phis)
+ update_phi(program, state, phi.first.block, phi.first.phi_def, phi.second);
+ }
+
+ return {id, s2};
+}
+
+void insert_before_branch(Block *block, aco_ptr<Instruction> instr)
+{
+ int end = block->instructions.size() - 1;
+ if (block->instructions[end]->format == Format::PSEUDO_BRANCH)
+ block->instructions.emplace(std::prev(block->instructions.end()), std::move(instr));
+ else
+ block->instructions.emplace_back(std::move(instr));
+}
+
+void insert_before_logical_end(Block *block, aco_ptr<Instruction> instr)
+{
+ for (int i = block->instructions.size() - 1; i >= 0; --i) {
+ if (block->instructions[i]->opcode == aco_opcode::p_logical_end) {
+ block->instructions.emplace(std::next(block->instructions.begin(), i), std::move(instr));
+ return;
+ }
+ }
+ insert_before_branch(block, std::move(instr));
+}
+
+aco_ptr<Instruction> lower_divergent_bool_phi(Program *program, Block *block, aco_ptr<Instruction>& phi)
+{
+ Builder bld(program);
+
+ ssa_state state;
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ Block *pred = &program->blocks[block->logical_preds[i]];
+
+ if (phi->operands[i].isUndefined())
+ continue;
+
+ assert(phi->operands[i].isTemp());
+ Temp phi_src = phi->operands[i].getTemp();
+ if (phi_src.regClass() == s1) {
+ Temp new_phi_src = bld.tmp(s2);
+ insert_before_logical_end(pred,
+ bld.sop2(aco_opcode::s_cselect_b64, Definition(new_phi_src),
+ Operand((uint32_t)-1), Operand(0u), bld.scc(phi_src)).get_ptr());
+ phi_src = new_phi_src;
+ }
+ assert(phi_src.regClass() == s2);
+
+ Operand cur = get_ssa(program, pred->index, &state);
+ Temp new_cur = write_ssa(program, pred, &state, cur.isTemp() ? cur.tempId() : 0);
+
+ if (cur.isUndefined()) {
+ insert_before_logical_end(pred, bld.sop1(aco_opcode::s_mov_b64, Definition(new_cur), phi_src).get_ptr());
+ } else {
+ Temp tmp1 = bld.tmp(s2), tmp2 = bld.tmp(s2);
+ insert_before_logical_end(pred,
+ bld.sop2(aco_opcode::s_andn2_b64, Definition(tmp1), bld.def(s1, scc),
+ cur, Operand(exec, s2)).get_ptr());
+ insert_before_logical_end(pred,
+ bld.sop2(aco_opcode::s_and_b64, Definition(tmp2), bld.def(s1, scc),
+ phi_src, Operand(exec, s2)).get_ptr());
+ insert_before_logical_end(pred,
+ bld.sop2(aco_opcode::s_or_b64, Definition(new_cur), bld.def(s1, scc),
+ tmp1, tmp2).get_ptr());
+ }
+ }
+
+ return bld.sop1(aco_opcode::s_mov_b64, phi->definitions[0], get_ssa(program, block->index, &state)).get_ptr();
+}
+
+void lower_linear_bool_phi(Program *program, Block *block, aco_ptr<Instruction>& phi)
+{
+ Builder bld(program);
+
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ if (!phi->operands[i].isTemp())
+ continue;
+
+ Temp phi_src = phi->operands[i].getTemp();
+ if (phi_src.regClass() == s2) {
+ Temp new_phi_src = bld.tmp(s1);
+ insert_before_logical_end(&program->blocks[block->linear_preds[i]],
+ bld.sopc(aco_opcode::s_cmp_lg_u64, bld.scc(Definition(new_phi_src)),
+ Operand(0u), phi_src).get_ptr());
+ phi->operands[i].setTemp(new_phi_src);
+ }
+ }
+}
+
+void lower_bool_phis(Program* program)
+{
+ for (Block& block : program->blocks) {
+ std::vector<aco_ptr<Instruction>> instructions;
+ std::vector<aco_ptr<Instruction>> non_phi;
+ instructions.swap(block.instructions);
+ block.instructions.reserve(instructions.size());
+ unsigned i = 0;
+ for (; i < instructions.size(); i++)
+ {
+ aco_ptr<Instruction>& phi = instructions[i];
+ if (phi->opcode != aco_opcode::p_phi && phi->opcode != aco_opcode::p_linear_phi)
+ break;
+ if (phi->opcode == aco_opcode::p_phi && phi->definitions[0].regClass() == s2) {
+ non_phi.emplace_back(std::move(lower_divergent_bool_phi(program, &block, phi)));
+ } else if (phi->opcode == aco_opcode::p_linear_phi && phi->definitions[0].regClass() == s1) {
+ /* if it's a valid non-boolean phi, this should be a no-op */
+ lower_linear_bool_phi(program, &block, phi);
+ block.instructions.emplace_back(std::move(phi));
+ } else {
+ block.instructions.emplace_back(std::move(phi));
+ }
+ }
+ for (auto&& instr : non_phi) {
+ assert(instr->opcode != aco_opcode::p_phi && instr->opcode != aco_opcode::p_linear_phi);
+ block.instructions.emplace_back(std::move(instr));
+ }
+ for (; i < instructions.size(); i++) {
+ aco_ptr<Instruction> instr = std::move(instructions[i]);
+ assert(instr->opcode != aco_opcode::p_phi && instr->opcode != aco_opcode::p_linear_phi);
+ block.instructions.emplace_back(std::move(instr));
+ }
+ }
+}
+
+}
diff --git a/src/amd/compiler/aco_lower_to_hw_instr.cpp b/src/amd/compiler/aco_lower_to_hw_instr.cpp
new file mode 100644
index 00000000000..8fd33e47d92
--- /dev/null
+++ b/src/amd/compiler/aco_lower_to_hw_instr.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schürmann ([email protected])
+ *
+ */
+
+#include <map>
+
+#include "aco_ir.h"
+#include "aco_builder.h"
+#include "util/u_math.h"
+#include "sid.h"
+
+
+namespace aco {
+
+struct lower_context {
+ Program *program;
+ std::vector<aco_ptr<Instruction>> instructions;
+};
+
+void emit_dpp_op(lower_context *ctx, PhysReg dst, PhysReg src0, PhysReg src1, PhysReg vtmp, PhysReg wrtmp,
+ aco_opcode op, Format format, bool clobber_vcc, unsigned dpp_ctrl,
+ unsigned row_mask, unsigned bank_mask, bool bound_ctrl_zero, unsigned size,
+ Operand *identity=NULL) /* for VOP3 with sparse writes */
+{
+ RegClass rc = RegClass(RegType::vgpr, size);
+ if (format == Format::VOP3) {
+ Builder bld(ctx->program, &ctx->instructions);
+
+ if (identity)
+ bld.vop1(aco_opcode::v_mov_b32, Definition(vtmp, v1), identity[0]);
+ if (identity && size >= 2)
+ bld.vop1(aco_opcode::v_mov_b32, Definition(PhysReg{vtmp+1}, v1), identity[1]);
+
+ for (unsigned i = 0; i < size; i++)
+ bld.vop1_dpp(aco_opcode::v_mov_b32, Definition(PhysReg{vtmp+i}, v1), Operand(PhysReg{src0+i}, v1),
+ dpp_ctrl, row_mask, bank_mask, bound_ctrl_zero);
+
+ if (clobber_vcc)
+ bld.vop3(op, Definition(dst, rc), Definition(vcc, s2), Operand(vtmp, rc), Operand(src1, rc));
+ else
+ bld.vop3(op, Definition(dst, rc), Operand(vtmp, rc), Operand(src1, rc));
+ } else {
+ assert(format == Format::VOP2 || format == Format::VOP1);
+ assert(size == 1 || (op == aco_opcode::v_mov_b32));
+
+ for (unsigned i = 0; i < size; i++) {
+ aco_ptr<DPP_instruction> dpp{create_instruction<DPP_instruction>(
+ op, (Format) ((uint32_t) format | (uint32_t) Format::DPP),
+ format == Format::VOP2 ? 2 : 1, clobber_vcc ? 2 : 1)};
+ dpp->operands[0] = Operand(PhysReg{src0+i}, rc);
+ if (format == Format::VOP2)
+ dpp->operands[1] = Operand(PhysReg{src1+i}, rc);
+ dpp->definitions[0] = Definition(PhysReg{dst+i}, rc);
+ if (clobber_vcc)
+ dpp->definitions[1] = Definition(vcc, s2);
+ dpp->dpp_ctrl = dpp_ctrl;
+ dpp->row_mask = row_mask;
+ dpp->bank_mask = bank_mask;
+ dpp->bound_ctrl = bound_ctrl_zero;
+ ctx->instructions.emplace_back(std::move(dpp));
+ }
+ }
+}
+
+uint32_t get_reduction_identity(ReduceOp op, unsigned idx)
+{
+ switch (op) {
+ case iadd32:
+ case iadd64:
+ case fadd32:
+ case fadd64:
+ case ior32:
+ case ior64:
+ case ixor32:
+ case ixor64:
+ case umax32:
+ case umax64:
+ return 0;
+ case imul32:
+ case imul64:
+ return idx ? 0 : 1;
+ case fmul32:
+ return 0x3f800000u; /* 1.0 */
+ case fmul64:
+ return idx ? 0x3ff00000u : 0u; /* 1.0 */
+ case imin32:
+ return INT32_MAX;
+ case imin64:
+ return idx ? 0x7fffffffu : 0xffffffffu;
+ case imax32:
+ return INT32_MIN;
+ case imax64:
+ return idx ? 0x80000000u : 0;
+ case umin32:
+ case umin64:
+ case iand32:
+ case iand64:
+ return 0xffffffffu;
+ case fmin32:
+ return 0x7f800000u; /* infinity */
+ case fmin64:
+ return idx ? 0x7ff00000u : 0u; /* infinity */
+ case fmax32:
+ return 0xff800000u; /* negative infinity */
+ case fmax64:
+ return idx ? 0xfff00000u : 0u; /* negative infinity */
+ }
+ unreachable("Invalid reduction operation");
+}
+
+aco_opcode get_reduction_opcode(lower_context *ctx, ReduceOp op, bool *clobber_vcc, Format *format)
+{
+ *clobber_vcc = false;
+ *format = Format::VOP2;
+ switch (op) {
+ case iadd32:
+ *clobber_vcc = ctx->program->chip_class < GFX9;
+ return ctx->program->chip_class < GFX9 ? aco_opcode::v_add_co_u32 : aco_opcode::v_add_u32;
+ case imul32:
+ *format = Format::VOP3;
+ return aco_opcode::v_mul_lo_u32;
+ case fadd32:
+ return aco_opcode::v_add_f32;
+ case fmul32:
+ return aco_opcode::v_mul_f32;
+ case imax32:
+ return aco_opcode::v_max_i32;
+ case imin32:
+ return aco_opcode::v_min_i32;
+ case umin32:
+ return aco_opcode::v_min_u32;
+ case umax32:
+ return aco_opcode::v_max_u32;
+ case fmin32:
+ return aco_opcode::v_min_f32;
+ case fmax32:
+ return aco_opcode::v_max_f32;
+ case iand32:
+ return aco_opcode::v_and_b32;
+ case ixor32:
+ return aco_opcode::v_xor_b32;
+ case ior32:
+ return aco_opcode::v_or_b32;
+ case iadd64:
+ case imul64:
+ assert(false);
+ break;
+ case fadd64:
+ *format = Format::VOP3;
+ return aco_opcode::v_add_f64;
+ case fmul64:
+ *format = Format::VOP3;
+ return aco_opcode::v_mul_f64;
+ case imin64:
+ case imax64:
+ case umin64:
+ case umax64:
+ assert(false);
+ break;
+ case fmin64:
+ *format = Format::VOP3;
+ return aco_opcode::v_min_f64;
+ case fmax64:
+ *format = Format::VOP3;
+ return aco_opcode::v_max_f64;
+ case iand64:
+ case ior64:
+ case ixor64:
+ assert(false);
+ break;
+ }
+ unreachable("Invalid reduction operation");
+ return aco_opcode::v_min_u32;
+}
+
+void emit_vopn(lower_context *ctx, PhysReg dst, PhysReg src0, PhysReg src1,
+ RegClass rc, aco_opcode op, Format format, bool clobber_vcc)
+{
+ aco_ptr<Instruction> instr;
+ switch (format) {
+ case Format::VOP2:
+ instr.reset(create_instruction<VOP2_instruction>(op, format, 2, clobber_vcc ? 2 : 1));
+ break;
+ case Format::VOP3:
+ instr.reset(create_instruction<VOP3A_instruction>(op, format, 2, clobber_vcc ? 2 : 1));
+ break;
+ default:
+ assert(false);
+ }
+ instr->operands[0] = Operand(src0, rc);
+ instr->operands[1] = Operand(src1, rc);
+ instr->definitions[0] = Definition(dst, rc);
+ if (clobber_vcc)
+ instr->definitions[1] = Definition(vcc, s2);
+ ctx->instructions.emplace_back(std::move(instr));
+}
+
+void emit_reduction(lower_context *ctx, aco_opcode op, ReduceOp reduce_op, unsigned cluster_size, PhysReg tmp,
+ PhysReg stmp, PhysReg vtmp, PhysReg sitmp, Operand src, Definition dst)
+{
+ assert(cluster_size == 64 || op == aco_opcode::p_reduce);
+
+ Builder bld(ctx->program, &ctx->instructions);
+
+ PhysReg wrtmp{0}; /* should never be needed */
+
+ Format format;
+ bool should_clobber_vcc;
+ aco_opcode reduce_opcode = get_reduction_opcode(ctx, reduce_op, &should_clobber_vcc, &format);
+ Operand identity[2];
+ identity[0] = Operand(get_reduction_identity(reduce_op, 0));
+ identity[1] = Operand(get_reduction_identity(reduce_op, 1));
+ Operand vcndmask_identity[2] = {identity[0], identity[1]};
+
+ /* First, copy the source to tmp and set inactive lanes to the identity */
+ // note: this clobbers SCC!
+ bld.sop1(aco_opcode::s_or_saveexec_b64, Definition(stmp, s2), Definition(scc, s1), Definition(exec, s2), Operand(UINT64_MAX), Operand(exec, s2));
+
+ for (unsigned i = 0; i < src.size(); i++) {
+ /* p_exclusive_scan needs it to be a sgpr or inline constant for the v_writelane_b32 */
+ if (identity[i].isLiteral() && op == aco_opcode::p_exclusive_scan) {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(PhysReg{sitmp+i}, s1), identity[i]);
+ identity[i] = Operand(PhysReg{sitmp+i}, s1);
+
+ bld.vop1(aco_opcode::v_mov_b32, Definition(PhysReg{tmp+i}, v1), identity[i]);
+ vcndmask_identity[i] = Operand(PhysReg{tmp+i}, v1);
+ } else if (identity[i].isLiteral()) {
+ bld.vop1(aco_opcode::v_mov_b32, Definition(PhysReg{tmp+i}, v1), identity[i]);
+ vcndmask_identity[i] = Operand(PhysReg{tmp+i}, v1);
+ }
+ }
+
+ for (unsigned i = 0; i < src.size(); i++) {
+ bld.vop2_e64(aco_opcode::v_cndmask_b32, Definition(PhysReg{tmp + i}, v1),
+ vcndmask_identity[i], Operand(PhysReg{src.physReg() + i}, v1),
+ Operand(stmp, s2));
+ }
+
+ bool exec_restored = false;
+ bool dst_written = false;
+ switch (op) {
+ case aco_opcode::p_reduce:
+ if (cluster_size == 1) break;
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_quad_perm(1, 0, 3, 2), 0xf, 0xf, false, src.size());
+ if (cluster_size == 2) break;
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_quad_perm(2, 3, 0, 1), 0xf, 0xf, false, src.size());
+ if (cluster_size == 4) break;
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_half_mirror, 0xf, 0xf, false, src.size());
+ if (cluster_size == 8) break;
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_mirror, 0xf, 0xf, false, src.size());
+ if (cluster_size == 16) break;
+ if (cluster_size == 32) {
+ for (unsigned i = 0; i < src.size(); i++)
+ bld.ds(aco_opcode::ds_swizzle_b32, Definition(PhysReg{vtmp+i}, v1), Operand(PhysReg{tmp+i}, s1), ds_pattern_bitmode(0x1f, 0, 0x10));
+ bld.sop1(aco_opcode::s_mov_b64, Definition(exec, s2), Operand(stmp, s2));
+ exec_restored = true;
+ emit_vopn(ctx, dst.physReg(), vtmp, tmp, src.regClass(), reduce_opcode, format, should_clobber_vcc);
+ dst_written = true;
+ } else {
+ assert(cluster_size == 64);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_bcast15, 0xa, 0xf, false, src.size());
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_bcast31, 0xc, 0xf, false, src.size());
+ }
+ break;
+ case aco_opcode::p_exclusive_scan:
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, aco_opcode::v_mov_b32, Format::VOP1, false,
+ dpp_wf_sr1, 0xf, 0xf, true, src.size());
+ for (unsigned i = 0; i < src.size(); i++) {
+ if (!identity[i].isConstant() || identity[i].constantValue()) { /* bound_ctrl should take case of this overwise */
+ assert((identity[i].isConstant() && !identity[i].isLiteral()) || identity[i].physReg() == PhysReg{sitmp+i});
+ bld.vop3(aco_opcode::v_writelane_b32, Definition(PhysReg{tmp+i}, v1),
+ identity[i], Operand(0u));
+ }
+ }
+ /* fall through */
+ case aco_opcode::p_inclusive_scan:
+ assert(cluster_size == 64);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_sr(1), 0xf, 0xf, false, src.size(), identity);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_sr(2), 0xf, 0xf, false, src.size(), identity);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_sr(4), 0xf, 0xf, false, src.size(), identity);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_sr(8), 0xf, 0xf, false, src.size(), identity);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_bcast15, 0xa, 0xf, false, src.size(), identity);
+ emit_dpp_op(ctx, tmp, tmp, tmp, vtmp, wrtmp, reduce_opcode, format, should_clobber_vcc,
+ dpp_row_bcast31, 0xc, 0xf, false, src.size(), identity);
+ break;
+ default:
+ unreachable("Invalid reduction mode");
+ }
+
+ if (!exec_restored)
+ bld.sop1(aco_opcode::s_mov_b64, Definition(exec, s2), Operand(stmp, s2));
+
+ if (op == aco_opcode::p_reduce && cluster_size == 64) {
+ for (unsigned k = 0; k < src.size(); k++) {
+ bld.vop3(aco_opcode::v_readlane_b32, Definition(PhysReg{dst.physReg() + k}, s1),
+ Operand(PhysReg{tmp + k}, v1), Operand(63u));
+ }
+ } else if (!(dst.physReg() == tmp) && !dst_written) {
+ for (unsigned k = 0; k < src.size(); k++) {
+ bld.vop1(aco_opcode::v_mov_b32, Definition(PhysReg{dst.physReg() + k}, s1),
+ Operand(PhysReg{tmp + k}, v1));
+ }
+ }
+}
+
+struct copy_operation {
+ Operand op;
+ Definition def;
+ unsigned uses;
+ unsigned size;
+};
+
+void handle_operands(std::map<PhysReg, copy_operation>& copy_map, lower_context* ctx, chip_class chip_class, Pseudo_instruction *pi)
+{
+ Builder bld(ctx->program, &ctx->instructions);
+ aco_ptr<Instruction> mov;
+ std::map<PhysReg, copy_operation>::iterator it = copy_map.begin();
+ std::map<PhysReg, copy_operation>::iterator target;
+ bool writes_scc = false;
+
+ /* count the number of uses for each dst reg */
+ while (it != copy_map.end()) {
+ if (it->second.op.isConstant()) {
+ ++it;
+ continue;
+ }
+
+ if (it->second.def.physReg() == scc)
+ writes_scc = true;
+
+ assert(!pi->tmp_in_scc || !(it->second.def.physReg() == pi->scratch_sgpr));
+
+ /* if src and dst reg are the same, remove operation */
+ if (it->first == it->second.op.physReg()) {
+ it = copy_map.erase(it);
+ continue;
+ }
+ /* check if the operand reg may be overwritten by another copy operation */
+ target = copy_map.find(it->second.op.physReg());
+ if (target != copy_map.end()) {
+ target->second.uses++;
+ }
+
+ ++it;
+ }
+
+ /* first, handle paths in the location transfer graph */
+ bool preserve_scc = pi->tmp_in_scc && !writes_scc;
+ it = copy_map.begin();
+ while (it != copy_map.end()) {
+
+ /* the target reg is not used as operand for any other copy */
+ if (it->second.uses == 0) {
+
+ /* try to coalesce 32-bit sgpr copies to 64-bit copies */
+ if (it->second.def.getTemp().type() == RegType::sgpr && it->second.size == 1 &&
+ !it->second.op.isConstant() && it->first % 2 == it->second.op.physReg() % 2) {
+
+ PhysReg other_def_reg = PhysReg{it->first % 2 ? it->first - 1 : it->first + 1};
+ PhysReg other_op_reg = PhysReg{it->first % 2 ? it->second.op.physReg() - 1 : it->second.op.physReg() + 1};
+ std::map<PhysReg, copy_operation>::iterator other = copy_map.find(other_def_reg);
+
+ if (other != copy_map.end() && !other->second.uses && other->second.size == 1 &&
+ other->second.op.physReg() == other_op_reg && !other->second.op.isConstant()) {
+ std::map<PhysReg, copy_operation>::iterator to_erase = it->first % 2 ? it : other;
+ it = it->first % 2 ? other : it;
+ copy_map.erase(to_erase);
+ it->second.size = 2;
+ }
+ }
+
+ if (it->second.def.physReg() == scc) {
+ bld.sopc(aco_opcode::s_cmp_lg_i32, it->second.def, it->second.op, Operand(0u));
+ preserve_scc = true;
+ } else if (it->second.size == 2 && it->second.def.getTemp().type() == RegType::sgpr) {
+ bld.sop1(aco_opcode::s_mov_b64, it->second.def, Operand(it->second.op.physReg(), s2));
+ } else {
+ bld.copy(it->second.def, it->second.op);
+ }
+
+ /* reduce the number of uses of the operand reg by one */
+ if (!it->second.op.isConstant()) {
+ for (unsigned i = 0; i < it->second.size; i++) {
+ target = copy_map.find(PhysReg{it->second.op.physReg() + i});
+ if (target != copy_map.end())
+ target->second.uses--;
+ }
+ }
+
+ copy_map.erase(it);
+ it = copy_map.begin();
+ continue;
+ } else {
+ /* the target reg is used as operand, check the next entry */
+ ++it;
+ }
+ }
+
+ if (copy_map.empty())
+ return;
+
+ /* all target regs are needed as operand somewhere which means, all entries are part of a cycle */
+ bool constants = false;
+ for (it = copy_map.begin(); it != copy_map.end(); ++it) {
+ assert(it->second.op.isFixed());
+ if (it->first == it->second.op.physReg())
+ continue;
+ /* do constants later */
+ if (it->second.op.isConstant()) {
+ constants = true;
+ continue;
+ }
+
+ if (preserve_scc && it->second.def.getTemp().type() == RegType::sgpr)
+ assert(!(it->second.def.physReg() == pi->scratch_sgpr));
+
+ /* to resolve the cycle, we have to swap the src reg with the dst reg */
+ copy_operation swap = it->second;
+ assert(swap.op.regClass() == swap.def.regClass());
+ Operand def_as_op = Operand(swap.def.physReg(), swap.def.regClass());
+ Definition op_as_def = Definition(swap.op.physReg(), swap.op.regClass());
+ if (chip_class >= GFX9 && swap.def.getTemp().type() == RegType::vgpr) {
+ bld.vop1(aco_opcode::v_swap_b32, swap.def, op_as_def, swap.op, def_as_op);
+ } else if (swap.op.physReg() == scc || swap.def.physReg() == scc) {
+ /* we need to swap scc and another sgpr */
+ assert(!preserve_scc);
+
+ PhysReg other = swap.op.physReg() == scc ? swap.def.physReg() : swap.op.physReg();
+
+ bld.sop1(aco_opcode::s_mov_b32, Definition(pi->scratch_sgpr, s1), Operand(scc, s1));
+ bld.sopc(aco_opcode::s_cmp_lg_i32, Definition(scc, s1), Operand(other, s1), Operand(0u));
+ bld.sop1(aco_opcode::s_mov_b32, Definition(other, s1), Operand(pi->scratch_sgpr, s1));
+ } else if (swap.def.getTemp().type() == RegType::sgpr) {
+ if (preserve_scc) {
+ bld.sop1(aco_opcode::s_mov_b32, Definition(pi->scratch_sgpr, s1), swap.op);
+ bld.sop1(aco_opcode::s_mov_b32, op_as_def, def_as_op);
+ bld.sop1(aco_opcode::s_mov_b32, swap.def, Operand(pi->scratch_sgpr, s1));
+ } else {
+ bld.sop2(aco_opcode::s_xor_b32, op_as_def, Definition(scc, s1), swap.op, def_as_op);
+ bld.sop2(aco_opcode::s_xor_b32, swap.def, Definition(scc, s1), swap.op, def_as_op);
+ bld.sop2(aco_opcode::s_xor_b32, op_as_def, Definition(scc, s1), swap.op, def_as_op);
+ }
+ } else {
+ bld.vop2(aco_opcode::v_xor_b32, op_as_def, swap.op, def_as_op);
+ bld.vop2(aco_opcode::v_xor_b32, swap.def, swap.op, def_as_op);
+ bld.vop2(aco_opcode::v_xor_b32, op_as_def, swap.op, def_as_op);
+ }
+
+ /* change the operand reg of the target's use */
+ assert(swap.uses == 1);
+ target = it;
+ for (++target; target != copy_map.end(); ++target) {
+ if (target->second.op.physReg() == it->first) {
+ target->second.op.setFixed(swap.op.physReg());
+ break;
+ }
+ }
+ }
+
+ /* copy constants into a registers which were operands */
+ if (constants) {
+ for (it = copy_map.begin(); it != copy_map.end(); ++it) {
+ if (!it->second.op.isConstant())
+ continue;
+ if (it->second.def.physReg() == scc) {
+ bld.sopc(aco_opcode::s_cmp_lg_i32, Definition(scc, s1), Operand(0u), Operand(it->second.op.constantValue() ? 1u : 0u));
+ } else {
+ bld.copy(it->second.def, it->second.op);
+ }
+ }
+ }
+}
+
+void lower_to_hw_instr(Program* program)
+{
+ Block *discard_block = NULL;
+
+ for (size_t i = 0; i < program->blocks.size(); i++)
+ {
+ Block *block = &program->blocks[i];
+ lower_context ctx;
+ ctx.program = program;
+ Builder bld(program, &ctx.instructions);
+
+ for (size_t j = 0; j < block->instructions.size(); j++) {
+ aco_ptr<Instruction>& instr = block->instructions[j];
+ aco_ptr<Instruction> mov;
+ if (instr->format == Format::PSEUDO) {
+ Pseudo_instruction *pi = (Pseudo_instruction*)instr.get();
+
+ switch (instr->opcode)
+ {
+ case aco_opcode::p_extract_vector:
+ {
+ unsigned reg = instr->operands[0].physReg() + instr->operands[1].constantValue() * instr->definitions[0].size();
+ RegClass rc = RegClass(instr->operands[0].getTemp().type(), 1);
+ RegClass rc_def = RegClass(instr->definitions[0].getTemp().type(), 1);
+ if (reg == instr->definitions[0].physReg())
+ break;
+
+ std::map<PhysReg, copy_operation> copy_operations;
+ for (unsigned i = 0; i < instr->definitions[0].size(); i++) {
+ Definition def = Definition(PhysReg{instr->definitions[0].physReg() + i}, rc_def);
+ copy_operations[def.physReg()] = {Operand(PhysReg{reg + i}, rc), def, 0, 1};
+ }
+ handle_operands(copy_operations, &ctx, program->chip_class, pi);
+ break;
+ }
+ case aco_opcode::p_create_vector:
+ {
+ std::map<PhysReg, copy_operation> copy_operations;
+ RegClass rc_def = RegClass(instr->definitions[0].getTemp().type(), 1);
+ unsigned reg_idx = 0;
+ for (const Operand& op : instr->operands) {
+ if (op.isConstant()) {
+ const PhysReg reg = PhysReg{instr->definitions[0].physReg() + reg_idx};
+ const Definition def = Definition(reg, rc_def);
+ copy_operations[reg] = {op, def, 0, 1};
+ reg_idx++;
+ continue;
+ }
+
+ RegClass rc_op = RegClass(op.getTemp().type(), 1);
+ for (unsigned j = 0; j < op.size(); j++)
+ {
+ const Operand copy_op = Operand(PhysReg{op.physReg() + j}, rc_op);
+ const Definition def = Definition(PhysReg{instr->definitions[0].physReg() + reg_idx}, rc_def);
+ copy_operations[def.physReg()] = {copy_op, def, 0, 1};
+ reg_idx++;
+ }
+ }
+ handle_operands(copy_operations, &ctx, program->chip_class, pi);
+ break;
+ }
+ case aco_opcode::p_split_vector:
+ {
+ std::map<PhysReg, copy_operation> copy_operations;
+ RegClass rc_op = instr->operands[0].isConstant() ? s1 : RegClass(instr->operands[0].regClass().type(), 1);
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ unsigned k = instr->definitions[i].size();
+ RegClass rc_def = RegClass(instr->definitions[i].getTemp().type(), 1);
+ for (unsigned j = 0; j < k; j++) {
+ Operand op = Operand(PhysReg{instr->operands[0].physReg() + (i*k+j)}, rc_op);
+ Definition def = Definition(PhysReg{instr->definitions[i].physReg() + j}, rc_def);
+ copy_operations[def.physReg()] = {op, def, 0, 1};
+ }
+ }
+ handle_operands(copy_operations, &ctx, program->chip_class, pi);
+ break;
+ }
+ case aco_opcode::p_parallelcopy:
+ case aco_opcode::p_wqm:
+ {
+ std::map<PhysReg, copy_operation> copy_operations;
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ Operand operand = instr->operands[i];
+ if (operand.isConstant() || operand.size() == 1) {
+ assert(instr->definitions[i].size() == 1);
+ copy_operations[instr->definitions[i].physReg()] = {operand, instr->definitions[i], 0, 1};
+ } else {
+ RegClass def_rc = RegClass(instr->definitions[i].regClass().type(), 1);
+ RegClass op_rc = RegClass(operand.getTemp().type(), 1);
+ for (unsigned j = 0; j < operand.size(); j++)
+ {
+ Operand op = Operand(PhysReg{instr->operands[i].physReg() + j}, op_rc);
+ Definition def = Definition(PhysReg{instr->definitions[i].physReg() + j}, def_rc);
+ copy_operations[def.physReg()] = {op, def, 0, 1};
+ }
+ }
+ }
+ handle_operands(copy_operations, &ctx, program->chip_class, pi);
+ break;
+ }
+ case aco_opcode::p_discard_if:
+ {
+ bool early_exit = false;
+ if (block->instructions[j + 1]->opcode != aco_opcode::p_logical_end ||
+ block->instructions[j + 2]->opcode != aco_opcode::s_endpgm) {
+ early_exit = true;
+ }
+
+ if (early_exit && !discard_block) {
+ discard_block = program->create_and_insert_block();
+ block = &program->blocks[i];
+
+ bld.reset(discard_block);
+ bld.exp(aco_opcode::exp, Operand(v1), Operand(v1), Operand(v1), Operand(v1),
+ 0, V_008DFC_SQ_EXP_NULL, false, true, true);
+ if (program->wb_smem_l1_on_end)
+ bld.smem(aco_opcode::s_dcache_wb);
+ bld.sopp(aco_opcode::s_endpgm);
+
+ bld.reset(&ctx.instructions);
+ }
+
+ // TODO: optimize uniform conditions
+ Definition branch_cond = instr->definitions.back();
+ Operand discard_cond = instr->operands.back();
+ aco_ptr<Instruction> sop2;
+ /* backwards, to finally branch on the global exec mask */
+ for (int i = instr->operands.size() - 2; i >= 0; i--) {
+ bld.sop2(aco_opcode::s_andn2_b64,
+ instr->definitions[i], /* new mask */
+ branch_cond, /* scc */
+ instr->operands[i], /* old mask */
+ discard_cond);
+ }
+
+ if (early_exit) {
+ bld.sopp(aco_opcode::s_cbranch_scc0, bld.scc(branch_cond.getTemp()), discard_block->index);
+
+ discard_block->linear_preds.push_back(block->index);
+ block->linear_succs.push_back(discard_block->index);
+ }
+
+ break;
+ }
+ case aco_opcode::p_spill:
+ {
+ assert(instr->operands[0].regClass() == v1.as_linear());
+ for (unsigned i = 0; i < instr->operands[2].size(); i++) {
+ bld.vop3(aco_opcode::v_writelane_b32, bld.def(v1, instr->operands[0].physReg()),
+ Operand(PhysReg{instr->operands[2].physReg() + i}, s1),
+ Operand(instr->operands[1].constantValue() + i));
+ }
+ break;
+ }
+ case aco_opcode::p_reload:
+ {
+ assert(instr->operands[0].regClass() == v1.as_linear());
+ for (unsigned i = 0; i < instr->definitions[0].size(); i++) {
+ bld.vop3(aco_opcode::v_readlane_b32,
+ bld.def(s1, PhysReg{instr->definitions[0].physReg() + i}),
+ instr->operands[0], Operand(instr->operands[1].constantValue() + i));
+ }
+ break;
+ }
+ case aco_opcode::p_as_uniform:
+ {
+ if (instr->operands[0].isConstant() || instr->operands[0].regClass().type() == RegType::sgpr) {
+ std::map<PhysReg, copy_operation> copy_operations;
+ Operand operand = instr->operands[0];
+ if (operand.isConstant() || operand.size() == 1) {
+ assert(instr->definitions[0].size() == 1);
+ copy_operations[instr->definitions[0].physReg()] = {operand, instr->definitions[0], 0, 1};
+ } else {
+ for (unsigned i = 0; i < operand.size(); i++)
+ {
+ Operand op = Operand(PhysReg{operand.physReg() + i}, s1);
+ Definition def = Definition(PhysReg{instr->definitions[0].physReg() + i}, s1);
+ copy_operations[def.physReg()] = {op, def, 0, 1};
+ }
+ }
+
+ handle_operands(copy_operations, &ctx, program->chip_class, pi);
+ } else {
+ assert(instr->operands[0].regClass().type() == RegType::vgpr);
+ assert(instr->definitions[0].regClass().type() == RegType::sgpr);
+ assert(instr->operands[0].size() == instr->definitions[0].size());
+ for (unsigned i = 0; i < instr->definitions[0].size(); i++) {
+ bld.vop1(aco_opcode::v_readfirstlane_b32,
+ bld.def(s1, PhysReg{instr->definitions[0].physReg() + i}),
+ Operand(PhysReg{instr->operands[0].physReg() + i}, v1));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (instr->format == Format::PSEUDO_BRANCH) {
+ Pseudo_branch_instruction* branch = static_cast<Pseudo_branch_instruction*>(instr.get());
+ /* check if all blocks from current to target are empty */
+ bool can_remove = block->index < branch->target[0];
+ for (unsigned i = block->index + 1; can_remove && i < branch->target[0]; i++) {
+ if (program->blocks[i].instructions.size())
+ can_remove = false;
+ }
+ if (can_remove)
+ continue;
+
+ switch (instr->opcode) {
+ case aco_opcode::p_branch:
+ assert(block->linear_succs[0] == branch->target[0]);
+ bld.sopp(aco_opcode::s_branch, branch->target[0]);
+ break;
+ case aco_opcode::p_cbranch_nz:
+ assert(block->linear_succs[1] == branch->target[0]);
+ if (branch->operands[0].physReg() == exec)
+ bld.sopp(aco_opcode::s_cbranch_execnz, branch->target[0]);
+ else if (branch->operands[0].physReg() == vcc)
+ bld.sopp(aco_opcode::s_cbranch_vccnz, branch->target[0]);
+ else {
+ assert(branch->operands[0].physReg() == scc);
+ bld.sopp(aco_opcode::s_cbranch_scc1, branch->target[0]);
+ }
+ break;
+ case aco_opcode::p_cbranch_z:
+ assert(block->linear_succs[1] == branch->target[0]);
+ if (branch->operands[0].physReg() == exec)
+ bld.sopp(aco_opcode::s_cbranch_execz, branch->target[0]);
+ else if (branch->operands[0].physReg() == vcc)
+ bld.sopp(aco_opcode::s_cbranch_vccz, branch->target[0]);
+ else {
+ assert(branch->operands[0].physReg() == scc);
+ bld.sopp(aco_opcode::s_cbranch_scc0, branch->target[0]);
+ }
+ break;
+ default:
+ unreachable("Unknown Pseudo branch instruction!");
+ }
+
+ } else if (instr->format == Format::PSEUDO_REDUCTION) {
+ Pseudo_reduction_instruction* reduce = static_cast<Pseudo_reduction_instruction*>(instr.get());
+ emit_reduction(&ctx, reduce->opcode, reduce->reduce_op, reduce->cluster_size,
+ reduce->operands[1].physReg(), // tmp
+ reduce->definitions[1].physReg(), // stmp
+ reduce->operands[2].physReg(), // vtmp
+ reduce->definitions[2].physReg(), // sitmp
+ reduce->operands[0], reduce->definitions[0]);
+ } else {
+ ctx.instructions.emplace_back(std::move(instr));
+ }
+
+ }
+ block->instructions.swap(ctx.instructions);
+ }
+}
+
+}
diff --git a/src/amd/compiler/aco_opcodes.py b/src/amd/compiler/aco_opcodes.py
new file mode 100644
index 00000000000..2221e2817af
--- /dev/null
+++ b/src/amd/compiler/aco_opcodes.py
@@ -0,0 +1,1552 @@
+#
+# Copyright (c) 2018 Valve Corporation
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice (including the next
+# paragraph) shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# Authors:
+# Daniel Schuermann ([email protected])
+
+
+# Class that represents all the information we have about the opcode
+# NOTE: this must be kept in sync with aco_op_info
+
+from enum import Enum
+
+class Format(Enum):
+ PSEUDO = 0
+ SOP1 = 1
+ SOP2 = 2
+ SOPK = 3
+ SOPP = 4
+ SOPC = 5
+ SMEM = 6
+ DS = 8
+ MTBUF = 9
+ MUBUF = 10
+ MIMG = 11
+ EXP = 12
+ FLAT = 13
+ GLOBAL = 14
+ SCRATCH = 15
+ PSEUDO_BRANCH = 16
+ PSEUDO_BARRIER = 17
+ PSEUDO_REDUCTION = 18
+ VOP1 = 1 << 8
+ VOP2 = 1 << 9
+ VOPC = 1 << 10
+ VOP3A = 1 << 11
+ VOP3B = 1 << 11
+ VOP3P = 1 << 12
+ VINTRP = 1 << 13
+ DPP = 1 << 14
+ SDWA = 1 << 15
+
+ def get_builder_fields(self):
+ if self == Format.SOPK:
+ return [('uint16_t', 'imm', None)]
+ elif self == Format.SOPP:
+ return [('uint32_t', 'block', '-1'),
+ ('uint32_t', 'imm', '0')]
+ elif self == Format.SMEM:
+ return [('bool', 'can_reorder', 'true'),
+ ('bool', 'glc', 'false'),
+ ('bool', 'dlc', 'false'),
+ ('bool', 'nv', 'false')]
+ elif self == Format.DS:
+ return [('int16_t', 'offset0', '0'),
+ ('int8_t', 'offset1', '0'),
+ ('bool', 'gds', 'false')]
+ elif self == Format.MTBUF:
+ return [('unsigned', 'dfmt', None),
+ ('unsigned', 'nfmt', None),
+ ('unsigned', 'img_format', None),
+ ('unsigned', 'offset', None),
+ ('bool', 'offen', None),
+ ('bool', 'idxen', 'false'),
+ ('bool', 'disable_wqm', 'false'),
+ ('bool', 'glc', 'false'),
+ ('bool', 'dlc', 'false'),
+ ('bool', 'slc', 'false'),
+ ('bool', 'tfe', 'false'),
+ ('bool', 'lds', 'false')]
+ elif self == Format.MUBUF:
+ return [('unsigned', 'offset', None),
+ ('bool', 'offen', None),
+ ('bool', 'idxen', 'false'),
+ ('bool', 'disable_wqm', 'false'),
+ ('bool', 'glc', 'false'),
+ ('bool', 'dlc', 'false'),
+ ('bool', 'slc', 'false'),
+ ('bool', 'tfe', 'false'),
+ ('bool', 'lds', 'false')]
+ elif self == Format.MIMG:
+ return [('unsigned', 'dmask', '0xF'),
+ ('bool', 'da', 'false'),
+ ('bool', 'unrm', 'true'),
+ ('bool', 'disable_wqm', 'false'),
+ ('bool', 'glc', 'false'),
+ ('bool', 'dlc', 'false'),
+ ('bool', 'slc', 'false'),
+ ('bool', 'tfe', 'false'),
+ ('bool', 'lwe', 'false'),
+ ('bool', 'r128_a16', 'false', 'r128'),
+ ('bool', 'd16', 'false')]
+ return [('unsigned', 'attribute', None),
+ ('unsigned', 'component', None)]
+ elif self == Format.EXP:
+ return [('unsigned', 'enabled_mask', None),
+ ('unsigned', 'dest', None),
+ ('bool', 'compr', 'false', 'compressed'),
+ ('bool', 'done', 'false'),
+ ('bool', 'vm', 'false', 'valid_mask')]
+ elif self == Format.PSEUDO_BRANCH:
+ return [('uint32_t', 'target0', '0', 'target[0]'),
+ ('uint32_t', 'target1', '0', 'target[1]')]
+ elif self == Format.PSEUDO_REDUCTION:
+ return [('ReduceOp', 'op', None, 'reduce_op'),
+ ('unsigned', 'cluster_size', '0')]
+ elif self == Format.VINTRP:
+ return [('unsigned', 'attribute', None),
+ ('unsigned', 'component', None)]
+ elif self == Format.DPP:
+ return [('uint16_t', 'dpp_ctrl', None),
+ ('uint8_t', 'row_mask', '0xF'),
+ ('uint8_t', 'bank_mask', '0xF'),
+ ('bool', 'bound_ctrl', 'false')]
+ elif self in [Format.FLAT, Format.GLOBAL, Format.SCRATCH]:
+ return [('uint16_t', 'offset', 0),
+ ('bool', 'glc', 'false'),
+ ('bool', 'slc', 'false'),
+ ('bool', 'lds', 'false'),
+ ('bool', 'nv', 'false')]
+ else:
+ return []
+
+ def get_builder_field_names(self):
+ return [f[1] for f in self.get_builder_fields()]
+
+ def get_builder_field_dests(self):
+ return [(f[3] if len(f) >= 4 else f[1]) for f in self.get_builder_fields()]
+
+ def get_builder_field_decls(self):
+ return [('%s %s=%s' % (f[0], f[1], f[2]) if f[2] != None else '%s %s' % (f[0], f[1])) for f in self.get_builder_fields()]
+
+
+class Opcode(object):
+ """Class that represents all the information we have about the opcode
+ NOTE: this must be kept in sync with aco_op_info
+ """
+ def __init__(self, name, opcode_gfx9, opcode_gfx10, format, input_mod, output_mod):
+ """Parameters:
+
+ - name is the name of the opcode (prepend nir_op_ for the enum name)
+ - all types are strings that get nir_type_ prepended to them
+ - input_types is a list of types
+ - algebraic_properties is a space-seperated string, where nir_op_is_ is
+ prepended before each entry
+ - const_expr is an expression or series of statements that computes the
+ constant value of the opcode given the constant values of its inputs.
+ """
+ assert isinstance(name, str)
+ assert isinstance(opcode_gfx9, int)
+ assert isinstance(opcode_gfx10, int)
+ assert isinstance(format, Format)
+ assert isinstance(input_mod, bool)
+ assert isinstance(output_mod, bool)
+
+ self.name = name
+ self.opcode_gfx9 = opcode_gfx9
+ self.opcode_gfx10 = opcode_gfx10
+ self.input_mod = "1" if input_mod else "0"
+ self.output_mod = "1" if output_mod else "0"
+ self.format = format
+
+
+# global dictionary of opcodes
+opcodes = {}
+
+# VOPC to GFX6 opcode translation map
+VOPC_GFX6 = [0] * 256
+
+def opcode(name, opcode_gfx9 = -1, opcode_gfx10 = -1, format = Format.PSEUDO, input_mod = False, output_mod = False):
+ assert name not in opcodes
+ opcodes[name] = Opcode(name, opcode_gfx9, opcode_gfx10, format, input_mod, output_mod)
+
+opcode("exp", 0, 0, format = Format.EXP)
+opcode("p_parallelcopy")
+opcode("p_startpgm")
+opcode("p_phi")
+opcode("p_linear_phi")
+opcode("p_as_uniform")
+
+opcode("p_create_vector")
+opcode("p_extract_vector")
+opcode("p_split_vector")
+
+# start/end the parts where we can use exec based instructions
+# implicitly
+opcode("p_logical_start")
+opcode("p_logical_end")
+
+# e.g. subgroupMin() in SPIR-V
+opcode("p_reduce", format=Format.PSEUDO_REDUCTION)
+# e.g. subgroupInclusiveMin()
+opcode("p_inclusive_scan", format=Format.PSEUDO_REDUCTION)
+# e.g. subgroupExclusiveMin()
+opcode("p_exclusive_scan", format=Format.PSEUDO_REDUCTION)
+
+opcode("p_branch", format=Format.PSEUDO_BRANCH)
+opcode("p_cbranch", format=Format.PSEUDO_BRANCH)
+opcode("p_cbranch_z", format=Format.PSEUDO_BRANCH)
+opcode("p_cbranch_nz", format=Format.PSEUDO_BRANCH)
+
+opcode("p_memory_barrier_all", format=Format.PSEUDO_BARRIER)
+opcode("p_memory_barrier_atomic", format=Format.PSEUDO_BARRIER)
+opcode("p_memory_barrier_buffer", format=Format.PSEUDO_BARRIER)
+opcode("p_memory_barrier_image", format=Format.PSEUDO_BARRIER)
+opcode("p_memory_barrier_shared", format=Format.PSEUDO_BARRIER)
+
+opcode("p_spill")
+opcode("p_reload")
+
+# start/end linear vgprs
+opcode("p_start_linear_vgpr")
+opcode("p_end_linear_vgpr")
+
+opcode("p_wqm")
+opcode("p_discard_if")
+opcode("p_load_helper")
+opcode("p_demote_to_helper")
+opcode("p_is_helper")
+
+opcode("p_fs_buffer_store_smem", format=Format.SMEM)
+
+
+# SOP2 instructions: 2 scalar inputs, 1 scalar output (+optional scc)
+SOP2 = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x00, 0x00, 0x00, 0x00, 0x00, "s_add_u32"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "s_sub_u32"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "s_add_i32"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "s_sub_i32"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "s_addc_u32"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "s_subb_u32"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "s_min_i32"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "s_min_u32"),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "s_max_i32"),
+ (0x09, 0x09, 0x09, 0x09, 0x09, "s_max_u32"),
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "s_cselect_b32"),
+ (0x0b, 0x0b, 0x0b, 0x0b, 0x0b, "s_cselect_b64"),
+ (0x0e, 0x0e, 0x0c, 0x0c, 0x0e, "s_and_b32"),
+ (0x0f, 0x0f, 0x0d, 0x0d, 0x0f, "s_and_b64"),
+ (0x10, 0x10, 0x0e, 0x0e, 0x10, "s_or_b32"),
+ (0x11, 0x11, 0x0f, 0x0f, 0x11, "s_or_b64"),
+ (0x12, 0x12, 0x10, 0x10, 0x12, "s_xor_b32"),
+ (0x13, 0x13, 0x11, 0x11, 0x13, "s_xor_b64"),
+ (0x14, 0x14, 0x12, 0x12, 0x14, "s_andn2_b32"),
+ (0x15, 0x15, 0x13, 0x13, 0x15, "s_andn2_b64"),
+ (0x16, 0x16, 0x14, 0x14, 0x16, "s_orn2_b32"),
+ (0x17, 0x17, 0x15, 0x15, 0x17, "s_orn2_b64"),
+ (0x18, 0x18, 0x16, 0x16, 0x18, "s_nand_b32"),
+ (0x19, 0x19, 0x17, 0x17, 0x19, "s_nand_b64"),
+ (0x1a, 0x1a, 0x18, 0x18, 0x1a, "s_nor_b32"),
+ (0x1b, 0x1b, 0x19, 0x19, 0x1b, "s_nor_b64"),
+ (0x1c, 0x1c, 0x1a, 0x1a, 0x1c, "s_xnor_b32"),
+ (0x1d, 0x1d, 0x1b, 0x1b, 0x1d, "s_xnor_b64"),
+ (0x1e, 0x1e, 0x1c, 0x1c, 0x1e, "s_lshl_b32"),
+ (0x1f, 0x1f, 0x1d, 0x1d, 0x1f, "s_lshl_b64"),
+ (0x20, 0x20, 0x1e, 0x1e, 0x20, "s_lshr_b32"),
+ (0x21, 0x21, 0x1f, 0x1f, 0x21, "s_lshr_b64"),
+ (0x22, 0x22, 0x20, 0x20, 0x22, "s_ashr_i32"),
+ (0x23, 0x23, 0x21, 0x21, 0x23, "s_ashr_i64"),
+ (0x24, 0x24, 0x22, 0x22, 0x24, "s_bfm_b32"),
+ (0x25, 0x25, 0x23, 0x23, 0x25, "s_bfm_b64"),
+ (0x26, 0x26, 0x24, 0x24, 0x26, "s_mul_i32"),
+ (0x27, 0x27, 0x25, 0x25, 0x27, "s_bfe_u32"),
+ (0x28, 0x28, 0x26, 0x26, 0x28, "s_bfe_i32"),
+ (0x29, 0x29, 0x27, 0x27, 0x29, "s_bfe_u64"),
+ (0x2a, 0x2a, 0x28, 0x28, 0x2a, "s_bfe_i64"),
+ (0x2b, 0x2b, 0x29, 0x29, -1, "s_cbranch_g_fork"),
+ (0x2c, 0x2c, 0x2a, 0x2a, 0x2c, "s_absdiff_i32"),
+ ( -1, -1, 0x2b, 0x2b, -1, "s_rfe_restore_b64"),
+ ( -1, -1, -1, 0x2e, 0x2e, "s_lshl1_add_u32"),
+ ( -1, -1, -1, 0x2f, 0x2f, "s_lshl2_add_u32"),
+ ( -1, -1, -1, 0x30, 0x30, "s_lshl3_add_u32"),
+ ( -1, -1, -1, 0x31, 0x31, "s_lshl4_add_u32"),
+ ( -1, -1, -1, 0x32, 0x32, "s_pack_ll_b32_b16"),
+ ( -1, -1, -1, 0x33, 0x33, "s_pack_lh_b32_b16"),
+ ( -1, -1, -1, 0x34, 0x34, "s_pack_hh_b32_b16"),
+ ( -1, -1, -1, 0x2c, 0x35, "s_mul_hi_u32"),
+ ( -1, -1, -1, 0x2c, 0x35, "s_mul_hi_i32"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SOP2:
+ opcode(name, gfx9, gfx10, Format.SOP2)
+
+
+# SOPK instructions: 0 input (+ imm), 1 output + optional scc
+SOPK = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x00, 0x00, 0x00, 0x00, 0x00, "s_movk_i32"),
+ ( -1, -1, -1, -1, 0x01, "s_version"), # GFX10+
+ (0x02, 0x02, 0x01, 0x01, 0x02, "s_cmovk_i32"), # GFX8_GFX9
+ (0x03, 0x03, 0x02, 0x02, 0x03, "s_cmpk_eq_i32"),
+ (0x04, 0x04, 0x03, 0x03, 0x04, "s_cmpk_lg_i32"),
+ (0x05, 0x05, 0x04, 0x04, 0x05, "s_cmpk_gt_i32"),
+ (0x06, 0x06, 0x05, 0x05, 0x06, "s_cmpk_ge_i32"),
+ (0x07, 0x07, 0x06, 0x06, 0x07, "s_cmpk_lt_i32"),
+ (0x08, 0x08, 0x07, 0x07, 0x08, "s_cmpk_le_i32"),
+ (0x09, 0x09, 0x08, 0x08, 0x09, "s_cmpk_eq_u32"),
+ (0x0a, 0x0a, 0x09, 0x09, 0x0a, "s_cmpk_lg_u32"),
+ (0x0b, 0x0b, 0x0a, 0x0a, 0x0b, "s_cmpk_gt_u32"),
+ (0x0c, 0x0c, 0x0b, 0x0b, 0x0c, "s_cmpk_ge_u32"),
+ (0x0d, 0x0d, 0x0c, 0x0c, 0x0d, "s_cmpk_lt_u32"),
+ (0x0e, 0x0e, 0x0d, 0x0d, 0x0e, "s_cmpk_le_u32"),
+ (0x0f, 0x0f, 0x0e, 0x0e, 0x0f, "s_addk_i32"),
+ (0x10, 0x10, 0x0f, 0x0f, 0x10, "s_mulk_i32"),
+ (0x11, 0x11, 0x10, 0x10, -1, "s_cbranch_i_fork"),
+ (0x12, 0x12, 0x11, 0x11, 0x12, "s_getreg_b32"),
+ (0x13, 0x13, 0x12, 0x12, 0x13, "s_setreg_b32"),
+ (0x15, 0x15, 0x14, 0x14, 0x15, "s_setreg_imm32_b32"), # requires 32bit literal
+ ( -1, -1, 0x15, 0x15, 0x16, "s_call_b64"),
+ ( -1, -1, -1, -1, 0x17, "s_waitcnt_vscnt"),
+ ( -1, -1, -1, -1, 0x18, "s_waitcnt_vmcnt"),
+ ( -1, -1, -1, -1, 0x19, "s_waitcnt_expcnt"),
+ ( -1, -1, -1, -1, 0x1a, "s_waitcnt_lgkmcnt"),
+ ( -1, -1, -1, -1, 0x1b, "s_subvector_loop_begin"),
+ ( -1, -1, -1, -1, 0x1c, "s_subvector_loop_end"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SOPK:
+ opcode(name, gfx9, gfx10, Format.SOPK)
+
+
+# SOP1 instructions: 1 input, 1 output (+optional SCC)
+SOP1 = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x03, 0x03, 0x00, 0x00, 0x03, "s_mov_b32"),
+ (0x04, 0x04, 0x01, 0x01, 0x04, "s_mov_b64"),
+ (0x05, 0x05, 0x02, 0x02, 0x05, "s_cmov_b32"),
+ (0x06, 0x06, 0x03, 0x03, 0x06, "s_cmov_b64"),
+ (0x07, 0x07, 0x04, 0x04, 0x07, "s_not_b32"),
+ (0x08, 0x08, 0x05, 0x05, 0x08, "s_not_b64"),
+ (0x09, 0x09, 0x06, 0x06, 0x09, "s_wqm_b32"),
+ (0x0a, 0x0a, 0x07, 0x07, 0x0a, "s_wqm_b64"),
+ (0x0b, 0x0b, 0x08, 0x08, 0x0b, "s_brev_b32"),
+ (0x0c, 0x0c, 0x09, 0x09, 0x0c, "s_brev_b64"),
+ (0x0d, 0x0d, 0x0a, 0x0a, 0x0d, "s_bcnt0_i32_b32"),
+ (0x0e, 0x0e, 0x0b, 0x0b, 0x0e, "s_bcnt0_i32_b64"),
+ (0x0f, 0x0f, 0x0c, 0x0c, 0x0f, "s_bcnt1_i32_b32"),
+ (0x10, 0x10, 0x0d, 0x0d, 0x10, "s_bcnt1_i32_b64"),
+ (0x11, 0x11, 0x0e, 0x0e, 0x11, "s_ff0_i32_b32"),
+ (0x12, 0x12, 0x0f, 0x0f, 0x12, "s_ff0_i32_b64"),
+ (0x13, 0x13, 0x10, 0x10, 0x13, "s_ff1_i32_b32"),
+ (0x14, 0x14, 0x11, 0x11, 0x14, "s_ff1_i32_b64"),
+ (0x15, 0x15, 0x12, 0x12, 0x15, "s_flbit_i32_b32"),
+ (0x16, 0x16, 0x13, 0x13, 0x16, "s_flbit_i32_b64"),
+ (0x17, 0x17, 0x14, 0x14, 0x17, "s_flbit_i32"),
+ (0x18, 0x18, 0x15, 0x15, 0x18, "s_flbit_i32_i64"),
+ (0x19, 0x19, 0x16, 0x16, 0x19, "s_sext_i32_i8"),
+ (0x1a, 0x1a, 0x17, 0x17, 0x1a, "s_sext_i32_i16"),
+ (0x1b, 0x1b, 0x18, 0x18, 0x1b, "s_bitset0_b32"),
+ (0x1c, 0x1c, 0x19, 0x19, 0x1c, "s_bitset0_b64"),
+ (0x1d, 0x1d, 0x1a, 0x1a, 0x1d, "s_bitset1_b32"),
+ (0x1e, 0x1e, 0x1b, 0x1b, 0x1e, "s_bitset1_b64"),
+ (0x1f, 0x1f, 0x1c, 0x1c, 0x1f, "s_getpc_b64"),
+ (0x20, 0x20, 0x1d, 0x1d, 0x20, "s_setpc_b64"),
+ (0x21, 0x21, 0x1e, 0x1e, 0x21, "s_swappc_b64"),
+ (0x22, 0x22, 0x1f, 0x1f, 0x22, "s_rfe_b64"),
+ (0x24, 0x24, 0x20, 0x20, 0x24, "s_and_saveexec_b64"),
+ (0x25, 0x25, 0x21, 0x21, 0x25, "s_or_saveexec_b64"),
+ (0x26, 0x26, 0x22, 0x22, 0x26, "s_xor_saveexec_b64"),
+ (0x27, 0x27, 0x23, 0x23, 0x27, "s_andn2_saveexec_b64"),
+ (0x28, 0x28, 0x24, 0x24, 0x28, "s_orn2_saveexec_b64"),
+ (0x29, 0x29, 0x25, 0x25, 0x29, "s_nand_saveexec_b64"),
+ (0x2a, 0x2a, 0x26, 0x26, 0x2a, "s_nor_saveexec_b64"),
+ (0x2b, 0x2b, 0x27, 0x27, 0x2b, "s_xnor_saveexec_b64"),
+ (0x2c, 0x2c, 0x28, 0x28, 0x2c, "s_quadmask_b32"),
+ (0x2d, 0x2d, 0x29, 0x29, 0x2d, "s_quadmask_b64"),
+ (0x2e, 0x2e, 0x2a, 0x2a, 0x2e, "s_movrels_b32"),
+ (0x2f, 0x2f, 0x2b, 0x2b, 0x2f, "s_movrels_b64"),
+ (0x30, 0x30, 0x2c, 0x2c, 0x30, "s_movreld_b32"),
+ (0x31, 0x31, 0x2d, 0x2d, 0x31, "s_movreld_b64"),
+ (0x32, 0x32, 0x2e, 0x2e, -1, "s_cbranch_join"),
+ (0x34, 0x34, 0x30, 0x30, 0x34, "s_abs_i32"),
+ (0x35, 0x35, -1, -1, 0x35, "s_mov_fed_b32"),
+ ( -1, -1, 0x32, 0x32, -1, "s_set_gpr_idx_idx"),
+ ( -1, -1, -1, 0x33, 0x37, "s_andn1_saveexec_b64"),
+ ( -1, -1, -1, 0x34, 0x38, "s_orn1_saveexec_b64"),
+ ( -1, -1, -1, 0x35, 0x39, "s_andn1_wrexec_b64"),
+ ( -1, -1, -1, 0x36, 0x3a, "s_andn2_wrexec_b64"),
+ ( -1, -1, -1, 0x37, 0x3b, "s_bitreplicate_b64_b32"),
+ ( -1, -1, -1, -1, 0x3c, "s_and_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x3d, "s_or_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x3e, "s_xor_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x3f, "s_andn2_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x40, "s_orn2_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x41, "s_nand_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x42, "s_nor_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x43, "s_xnor_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x44, "s_andn1_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x45, "s_orn1_saveexec_b32"),
+ ( -1, -1, -1, -1, 0x46, "s_andn1_wrexec_b32"),
+ ( -1, -1, -1, -1, 0x47, "s_andn2_wrexec_b32"),
+ ( -1, -1, -1, -1, 0x49, "s_movrelsd_2_b32"),
+ # actually a pseudo-instruction. it's lowered to SALU during assembly though, so it's useful to identify it as a SOP1.
+ ( -1, -1, -1, -1, -1, "p_constaddr"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SOP1:
+ opcode(name, gfx9, gfx10, Format.SOP1)
+
+
+# SOPC instructions: 2 inputs and 0 outputs (+SCC)
+SOPC = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x00, 0x00, 0x00, 0x00, 0x00, "s_cmp_eq_i32"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "s_cmp_lg_i32"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "s_cmp_gt_i32"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "s_cmp_ge_i32"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "s_cmp_lt_i32"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "s_cmp_le_i32"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "s_cmp_eq_u32"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "s_cmp_lg_u32"),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "s_cmp_gt_u32"),
+ (0x09, 0x09, 0x09, 0x09, 0x09, "s_cmp_ge_u32"),
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "s_cmp_lt_u32"),
+ (0x0b, 0x0b, 0x0b, 0x0b, 0x0b, "s_cmp_le_u32"),
+ (0x0c, 0x0c, 0x0c, 0x0c, 0x0c, "s_bitcmp0_b32"),
+ (0x0d, 0x0d, 0x0d, 0x0d, 0x0d, "s_bitcmp1_b32"),
+ (0x0e, 0x0e, 0x0e, 0x0e, 0x0e, "s_bitcmp0_b64"),
+ (0x0f, 0x0f, 0x0f, 0x0f, 0x0f, "s_bitcmp1_b64"),
+ (0x10, 0x10, 0x10, 0x10, -1, "s_setvskip"),
+ ( -1, -1, 0x11, 0x11, -1, "s_set_gpr_idx_on"),
+ ( -1, -1, 0x12, 0x12, 0x12, "s_cmp_eq_u64"),
+ ( -1, -1, 0x13, 0x13, 0x13, "s_cmp_lg_u64"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SOPC:
+ opcode(name, gfx9, gfx10, Format.SOPC)
+
+
+# SOPP instructions: 0 inputs (+optional scc/vcc), 0 outputs
+SOPP = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x00, 0x00, 0x00, 0x00, 0x00, "s_nop"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "s_endpgm"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "s_branch"),
+ ( -1, -1, 0x03, 0x03, 0x03, "s_wakeup"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "s_cbranch_scc0"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "s_cbranch_scc1"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "s_cbranch_vccz"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "s_cbranch_vccnz"),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "s_cbranch_execz"),
+ (0x09, 0x09, 0x09, 0x09, 0x09, "s_cbranch_execnz"),
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "s_barrier"),
+ ( -1, 0x0b, 0x0b, 0x0b, 0x0b, "s_setkill"),
+ (0x0c, 0x0c, 0x0c, 0x0c, 0x0c, "s_waitcnt"),
+ (0x0d, 0x0d, 0x0d, 0x0d, 0x0d, "s_sethalt"),
+ (0x0e, 0x0e, 0x0e, 0x0e, 0x0e, "s_sleep"),
+ (0x0f, 0x0f, 0x0f, 0x0f, 0x0f, "s_setprio"),
+ (0x10, 0x10, 0x10, 0x10, 0x10, "s_sendmsg"),
+ (0x11, 0x11, 0x11, 0x11, 0x11, "s_sendmsghalt"),
+ (0x12, 0x12, 0x12, 0x12, 0x12, "s_trap"),
+ (0x13, 0x13, 0x13, 0x13, 0x13, "s_icache_inv"),
+ (0x14, 0x14, 0x14, 0x14, 0x14, "s_incperflevel"),
+ (0x15, 0x15, 0x15, 0x15, 0x15, "s_decperflevel"),
+ (0x16, 0x16, 0x16, 0x16, 0x16, "s_ttracedata"),
+ ( -1, 0x17, 0x17, 0x17, 0x17, "s_cbranch_cdbgsys"),
+ ( -1, 0x18, 0x18, 0x18, 0x18, "s_cbranch_cdbguser"),
+ ( -1, 0x19, 0x19, 0x19, 0x19, "s_cbranch_cdbgsys_or_user"),
+ ( -1, 0x1a, 0x1a, 0x1a, 0x1a, "s_cbranch_cdbgsys_and_user"),
+ ( -1, -1, 0x1b, 0x1b, 0x1b, "s_endpgm_saved"),
+ ( -1, -1, 0x1c, 0x1c, -1, "s_set_gpr_idx_off"),
+ ( -1, -1, 0x1d, 0x1d, -1, "s_set_gpr_idx_mode"),
+ ( -1, -1, -1, 0x1e, 0x1e, "s_endpgm_ordered_ps_done"),
+ ( -1, -1, -1, -1, 0x1f, "s_code_end"),
+ ( -1, -1, -1, -1, 0x20, "s_inst_prefetch"),
+ ( -1, -1, -1, -1, 0x21, "s_clause"),
+ ( -1, -1, -1, -1, 0x22, "s_wait_idle"),
+ ( -1, -1, -1, -1, 0x23, "s_waitcnt_depctr"),
+ ( -1, -1, -1, -1, 0x24, "s_round_mode"),
+ ( -1, -1, -1, -1, 0x25, "s_denorm_mode"),
+ ( -1, -1, -1, -1, 0x26, "s_ttracedata_imm"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SOPP:
+ opcode(name, gfx9, gfx10, Format.SOPP)
+
+
+# SMEM instructions: sbase input (2 sgpr), potentially 2 offset inputs, 1 sdata input/output
+SMEM = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name
+ (0x00, 0x00, 0x00, 0x00, 0x00, "s_load_dword"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "s_load_dwordx2"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "s_load_dwordx4"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "s_load_dwordx8"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "s_load_dwordx16"),
+ ( -1, -1, -1, 0x05, 0x05, "s_scratch_load_dword"),
+ ( -1, -1, -1, 0x06, 0x06, "s_scratch_load_dwordx2"),
+ ( -1, -1, -1, 0x07, 0x07, "s_scratch_load_dwordx4"),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "s_buffer_load_dword"),
+ (0x09, 0x09, 0x09, 0x09, 0x09, "s_buffer_load_dwordx2"),
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "s_buffer_load_dwordx4"),
+ (0x0b, 0x0b, 0x0b, 0x0b, 0x0b, "s_buffer_load_dwordx8"),
+ (0x0c, 0x0c, 0x0c, 0x0c, 0x0c, "s_buffer_load_dwordx16"),
+ ( -1, -1, 0x10, 0x10, 0x10, "s_store_dword"),
+ ( -1, -1, 0x11, 0x11, 0x11, "s_store_dwordx2"),
+ ( -1, -1, 0x12, 0x12, 0x12, "s_store_dwordx4"),
+ ( -1, -1, -1, 0x15, 0x15, "s_scratch_store_dword"),
+ ( -1, -1, -1, 0x16, 0x16, "s_scratch_store_dwordx2"),
+ ( -1, -1, -1, 0x17, 0x17, "s_scratch_store_dwordx4"),
+ ( -1, -1, 0x18, 0x18, 0x18, "s_buffer_store_dword"),
+ ( -1, -1, 0x19, 0x19, 0x19, "s_buffer_store_dwordx2"),
+ ( -1, -1, 0x1a, 0x1a, 0x1a, "s_buffer_store_dwordx4"),
+ ( -1, -1, 0x1f, 0x1f, 0x1f, "s_gl1_inv"),
+ (0x1f, 0x1f, 0x20, 0x20, 0x20, "s_dcache_inv"),
+ ( -1, -1, 0x21, 0x21, 0x21, "s_dcache_wb"),
+ ( -1, 0x1d, 0x22, 0x22, -1, "s_dcache_inv_vol"),
+ ( -1, -1, 0x23, 0x23, -1, "s_dcache_wb_vol"),
+ (0x1e, 0x1e, 0x24, 0x24, 0x24, "s_memtime"),
+ ( -1, -1, 0x25, 0x25, 0x25, "s_memrealtime"),
+ ( -1, -1, 0x26, 0x26, 0x26, "s_atc_probe"),
+ ( -1, -1, 0x27, 0x27, 0x27, "s_atc_probe_buffer"),
+ ( -1, -1, -1, 0x28, 0x28, "s_dcache_discard"),
+ ( -1, -1, -1, 0x29, 0x29, "s_dcache_discard_x2"),
+ ( -1, -1, -1, -1, 0x2a, "s_get_waveid_in_workgroup"),
+ ( -1, -1, -1, 0x40, 0x40, "s_buffer_atomic_swap"),
+ ( -1, -1, -1, 0x41, 0x41, "s_buffer_atomic_cmpswap"),
+ ( -1, -1, -1, 0x42, 0x42, "s_buffer_atomic_add"),
+ ( -1, -1, -1, 0x43, 0x43, "s_buffer_atomic_sub"),
+ ( -1, -1, -1, 0x44, 0x44, "s_buffer_atomic_smin"),
+ ( -1, -1, -1, 0x45, 0x45, "s_buffer_atomic_umin"),
+ ( -1, -1, -1, 0x46, 0x46, "s_buffer_atomic_smax"),
+ ( -1, -1, -1, 0x47, 0x47, "s_buffer_atomic_umax"),
+ ( -1, -1, -1, 0x48, 0x48, "s_buffer_atomic_and"),
+ ( -1, -1, -1, 0x49, 0x49, "s_buffer_atomic_or"),
+ ( -1, -1, -1, 0x4a, 0x4a, "s_buffer_atomic_xor"),
+ ( -1, -1, -1, 0x4b, 0x4b, "s_buffer_atomic_inc"),
+ ( -1, -1, -1, 0x4c, 0x4c, "s_buffer_atomic_dec"),
+ ( -1, -1, -1, 0x60, 0x60, "s_buffer_atomic_swap_x2"),
+ ( -1, -1, -1, 0x61, 0x61, "s_buffer_atomic_cmpswap_x2"),
+ ( -1, -1, -1, 0x62, 0x62, "s_buffer_atomic_add_x2"),
+ ( -1, -1, -1, 0x63, 0x63, "s_buffer_atomic_sub_x2"),
+ ( -1, -1, -1, 0x64, 0x64, "s_buffer_atomic_smin_x2"),
+ ( -1, -1, -1, 0x65, 0x65, "s_buffer_atomic_umin_x2"),
+ ( -1, -1, -1, 0x66, 0x66, "s_buffer_atomic_smax_x2"),
+ ( -1, -1, -1, 0x67, 0x67, "s_buffer_atomic_umax_x2"),
+ ( -1, -1, -1, 0x68, 0x68, "s_buffer_atomic_and_x2"),
+ ( -1, -1, -1, 0x69, 0x69, "s_buffer_atomic_or_x2"),
+ ( -1, -1, -1, 0x6a, 0x6a, "s_buffer_atomic_xor_x2"),
+ ( -1, -1, -1, 0x6b, 0x6b, "s_buffer_atomic_inc_x2"),
+ ( -1, -1, -1, 0x6c, 0x6c, "s_buffer_atomic_dec_x2"),
+ ( -1, -1, -1, 0x80, 0x80, "s_atomic_swap"),
+ ( -1, -1, -1, 0x81, 0x81, "s_atomic_cmpswap"),
+ ( -1, -1, -1, 0x82, 0x82, "s_atomic_add"),
+ ( -1, -1, -1, 0x83, 0x83, "s_atomic_sub"),
+ ( -1, -1, -1, 0x84, 0x84, "s_atomic_smin"),
+ ( -1, -1, -1, 0x85, 0x85, "s_atomic_umin"),
+ ( -1, -1, -1, 0x86, 0x86, "s_atomic_smax"),
+ ( -1, -1, -1, 0x87, 0x87, "s_atomic_umax"),
+ ( -1, -1, -1, 0x88, 0x88, "s_atomic_and"),
+ ( -1, -1, -1, 0x89, 0x89, "s_atomic_or"),
+ ( -1, -1, -1, 0x8a, 0x8a, "s_atomic_xor"),
+ ( -1, -1, -1, 0x8b, 0x8b, "s_atomic_inc"),
+ ( -1, -1, -1, 0x8c, 0x8c, "s_atomic_dec"),
+ ( -1, -1, -1, 0xa0, 0xa0, "s_atomic_swap_x2"),
+ ( -1, -1, -1, 0xa1, 0xa1, "s_atomic_cmpswap_x2"),
+ ( -1, -1, -1, 0xa2, 0xa2, "s_atomic_add_x2"),
+ ( -1, -1, -1, 0xa3, 0xa3, "s_atomic_sub_x2"),
+ ( -1, -1, -1, 0xa4, 0xa4, "s_atomic_smin_x2"),
+ ( -1, -1, -1, 0xa5, 0xa5, "s_atomic_umin_x2"),
+ ( -1, -1, -1, 0xa6, 0xa6, "s_atomic_smax_x2"),
+ ( -1, -1, -1, 0xa7, 0xa7, "s_atomic_umax_x2"),
+ ( -1, -1, -1, 0xa8, 0xa8, "s_atomic_and_x2"),
+ ( -1, -1, -1, 0xa9, 0xa9, "s_atomic_or_x2"),
+ ( -1, -1, -1, 0xaa, 0xaa, "s_atomic_xor_x2"),
+ ( -1, -1, -1, 0xab, 0xab, "s_atomic_inc_x2"),
+ ( -1, -1, -1, 0xac, 0xac, "s_atomic_dec_x2"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in SMEM:
+ opcode(name, gfx9, gfx10, Format.SMEM)
+
+
+# VOP2 instructions: 2 inputs, 1 output (+ optional vcc)
+# TODO: misses some GFX6_7 opcodes which were shifted to VOP3 in GFX8
+VOP2 = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name, input/output modifiers
+ (0x00, 0x00, 0x00, 0x00, 0x01, "v_cndmask_b32", False),
+ (0x03, 0x03, 0x01, 0x01, 0x03, "v_add_f32", True),
+ (0x04, 0x04, 0x02, 0x02, 0x04, "v_sub_f32", True),
+ (0x05, 0x05, 0x03, 0x03, 0x05, "v_subrev_f32", True),
+ (0x06, 0x06, -1, -1, 0x06, "v_mac_legacy_f32", True),
+ (0x07, 0x07, 0x04, 0x04, 0x07, "v_mul_legacy_f32", True),
+ (0x08, 0x08, 0x05, 0x05, 0x08, "v_mul_f32", True),
+ (0x09, 0x09, 0x06, 0x06, 0x09, "v_mul_i32_i24", False),
+ (0x0a, 0x0a, 0x07, 0x07, 0x0a, "v_mul_hi_i32_i24", False),
+ (0x0b, 0x0b, 0x08, 0x08, 0x0b, "v_mul_u32_u24", False),
+ (0x0c, 0x0c, 0x09, 0x09, 0x0c, "v_mul_hi_u32_u24", False),
+ (0x0d, 0x0d, -1, -1, -1, "v_min_legacy_f32", True),
+ (0x0e, 0x0e, -1, -1, -1, "v_max_legacy_f32", True),
+ (0x0f, 0x0f, 0x0a, 0x0a, 0x0f, "v_min_f32", True),
+ (0x10, 0x10, 0x0b, 0x0b, 0x10, "v_max_f32", True),
+ (0x11, 0x11, 0x0c, 0x0c, 0x11, "v_min_i32", False),
+ (0x12, 0x12, 0x0d, 0x0d, 0x12, "v_max_i32", False),
+ (0x13, 0x13, 0x0e, 0x0e, 0x13, "v_min_u32", False),
+ (0x14, 0x14, 0x0f, 0x0f, 0x14, "v_max_u32", False),
+ (0x15, 0x15, -1, -1, -1, "v_lshr_b32", False),
+ (0x16, 0x16, 0x10, 0x10, 0x16, "v_lshrrev_b32", False),
+ (0x17, 0x17, -1, -1, -1, "v_ashr_i32", False),
+ (0x18, 0x18, 0x11, 0x11, 0x18, "v_ashrrev_i32", False),
+ (0x19, 0x19, -1, -1, -1, "v_lshl_b32", False),
+ (0x1a, 0x1a, 0x12, 0x12, 0x1a, "v_lshlrev_b32", False),
+ (0x1b, 0x1b, 0x13, 0x13, 0x1b, "v_and_b32", False),
+ (0x1c, 0x1c, 0x14, 0x14, 0x1c, "v_or_b32", False),
+ (0x1d, 0x1d, 0x15, 0x15, 0x1d, "v_xor_b32", False),
+ ( -1, -1, -1, -1, 0x1e, "v_xnor_b32", False),
+ (0x1f, 0x1f, 0x16, 0x16, 0x1f, "v_mac_f32", True),
+ (0x20, 0x20, 0x17, 0x17, 0x20, "v_madmk_f32", False),
+ (0x21, 0x21, 0x18, 0x18, 0x21, "v_madak_f32", False),
+ (0x25, 0x25, 0x19, 0x19, -1, "v_add_co_u32", False), # VOP3B only in RDNA
+ (0x26, 0x26, 0x1a, 0x1a, -1, "v_sub_co_u32", False), # VOP3B only in RDNA
+ (0x27, 0x27, 0x1b, 0x1b, -1, "v_subrev_co_u32", False), # VOP3B only in RDNA
+ (0x28, 0x28, 0x1c, 0x1c, 0x28, "v_addc_co_u32", False), # v_add_co_ci_u32 in RDNA
+ (0x29, 0x29, 0x1d, 0x1d, 0x29, "v_subb_co_u32", False), # v_sub_co_ci_u32 in RDNA
+ (0x2a, 0x2a, 0x1e, 0x1e, 0x2a, "v_subbrev_co_u32", False), # v_subrev_co_ci_u32 in RDNA
+ ( -1, -1, -1, -1, 0x2b, "v_fmac_f32", True),
+ ( -1, -1, -1, -1, 0x2c, "v_fmamk_f32", True),
+ ( -1, -1, -1, -1, 0x2d, "v_fmaak_f32", True),
+ ( -1, -1, 0x1f, 0x1f, 0x32, "v_add_f16", True),
+ ( -1, -1, 0x20, 0x20, 0x33, "v_sub_f16", True),
+ ( -1, -1, 0x21, 0x21, 0x34, "v_subrev_f16", True),
+ ( -1, -1, 0x22, 0x22, 0x35, "v_mul_f16", True),
+ ( -1, -1, 0x23, 0x23, -1, "v_mac_f16", True),
+ ( -1, -1, 0x24, 0x24, -1, "v_madmk_f16", False),
+ ( -1, -1, 0x25, 0x25, -1, "v_madak_f16", False),
+ ( -1, -1, 0x26, 0x26, -1, "v_add_u16", False),
+ ( -1, -1, 0x27, 0x27, -1, "v_sub_u16", False),
+ ( -1, -1, 0x28, 0x28, -1, "v_subrev_u16", False),
+ ( -1, -1, 0x29, 0x29, -1, "v_mul_lo_u16", False),
+ ( -1, -1, 0x2a, 0x2a, -1, "v_lshlrev_b16", False),
+ ( -1, -1, 0x2b, 0x2b, -1, "v_lshrrev_b16", False),
+ ( -1, -1, 0x2c, 0x2c, -1, "v_ashrrev_b16", False),
+ ( -1, -1, 0x2d, 0x2d, 0x39, "v_max_f16", True),
+ ( -1, -1, 0x2e, 0x2e, 0x3a, "v_min_f16", True),
+ ( -1, -1, 0x2f, 0x2f, -1, "v_max_u16", False),
+ ( -1, -1, 0x30, 0x30, -1, "v_max_i16", False),
+ ( -1, -1, 0x31, 0x31, -1, "v_min_u16", False),
+ ( -1, -1, 0x32, 0x32, -1, "v_min_i16", False),
+ ( -1, -1, 0x33, 0x33, 0x3b, "v_ldexp_f16", False),
+ ( -1, -1, 0x34, 0x34, 0x25, "v_add_u32", False), # v_add_nc_u32 in RDNA
+ ( -1, -1, 0x35, 0x35, 0x26, "v_sub_u32", False), # v_sub_nc_u32 in RDNA
+ ( -1, -1, 0x36, 0x36, 0x27, "v_subrev_u32", False), # v_subrev_nc_u32 in RDNA
+ ( -1, -1, -1, -1, 0x36, "v_fmac_f16", False),
+ ( -1, -1, -1, -1, 0x37, "v_fmamk_f16", False),
+ ( -1, -1, -1, -1, 0x38, "v_fmaak_f16", False),
+ ( -1, -1, -1, -1, 0x3c, "v_pk_fmac_f16", False),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name, modifiers) in VOP2:
+ opcode(name, gfx9, gfx10, Format.VOP2, modifiers, modifiers)
+
+
+# VOP1 instructions: instructions with 1 input and 1 output
+VOP1 = {
+ # GFX6, GFX7, GFX8, GFX9, GFX10, name, input_modifiers, output_modifiers
+ (0x00, 0x00, 0x00, 0x00, 0x00, "v_nop", False, False),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "v_mov_b32", False, False),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "v_readfirstlane_b32", False, False),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "v_cvt_i32_f64", True, False),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "v_cvt_f64_i32", False, True),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "v_cvt_f32_i32", False, True),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "v_cvt_f32_u32", False, True),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "v_cvt_u32_f32", True, False),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "v_cvt_i32_f32", True, False),
+ (0x09, 0x09, -1, -1, 0x09, "v_mov_fed_b32", True, False), # LLVM mentions it for GFX8_9
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "v_cvt_f16_f32", True, True),
+ (0x0b, 0x0b, 0x0b, 0x0b, 0x0b, "v_cvt_f32_f16", True, True),
+ (0x0c, 0x0c, 0x0c, 0x0c, 0x0c, "v_cvt_rpi_i32_f32", True, False),
+ (0x0d, 0x0d, 0x0d, 0x0d, 0x0d, "v_cvt_flr_i32_f32", True, False),
+ (0x0e, 0x0e, 0x0e, 0x0e, 0x0e, "v_cvt_off_f32_i4", False, True),
+ (0x0f, 0x0f, 0x0f, 0x0f, 0x0f, "v_cvt_f32_f64", True, True),
+ (0x10, 0x10, 0x10, 0x10, 0x10, "v_cvt_f64_f32", True, True),
+ (0x11, 0x11, 0x11, 0x11, 0x11, "v_cvt_f32_ubyte0", False, True),
+ (0x12, 0x12, 0x12, 0x12, 0x12, "v_cvt_f32_ubyte1", False, True),
+ (0x13, 0x13, 0x13, 0x13, 0x13, "v_cvt_f32_ubyte2", False, True),
+ (0x14, 0x14, 0x14, 0x14, 0x14, "v_cvt_f32_ubyte3", False, True),
+ (0x15, 0x15, 0x15, 0x15, 0x15, "v_cvt_u32_f64", True, False),
+ (0x16, 0x16, 0x16, 0x16, 0x16, "v_cvt_f64_u32", False, True),
+ ( -1, 0x17, 0x17, 0x17, 0x17, "v_trunc_f64", True, True),
+ ( -1, 0x18, 0x18, 0x18, 0x18, "v_ceil_f64", True, True),
+ ( -1, 0x19, 0x19, 0x19, 0x19, "v_rndne_f64", True, True),
+ ( -1, 0x1a, 0x1a, 0x1a, 0x1a, "v_floor_f64", True, True),
+ ( -1, -1, -1, -1, 0x1b, "v_pipeflush", False, False),
+ (0x20, 0x20, 0x1b, 0x1b, 0x20, "v_fract_f32", True, True),
+ (0x21, 0x21, 0x1c, 0x1c, 0x21, "v_trunc_f32", True, True),
+ (0x22, 0x22, 0x1d, 0x1d, 0x22, "v_ceil_f32", True, True),
+ (0x23, 0x23, 0x1e, 0x1e, 0x23, "v_rndne_f32", True, True),
+ (0x24, 0x24, 0x1f, 0x1f, 0x24, "v_floor_f32", True, True),
+ (0x25, 0x25, 0x20, 0x20, 0x25, "v_exp_f32", True, True),
+ (0x26, 0x26, -1, -1, -1, "v_log_clamp_f32", True, True),
+ (0x27, 0x27, 0x21, 0x21, 0x27, "v_log_f32", True, True),
+ (0x28, 0x28, -1, -1, -1, "v_rcp_clamp_f32", True, True),
+ (0x29, 0x29, -1, -1, -1, "v_rcp_legacy_f32", True, True),
+ (0x2a, 0x2a, 0x22, 0x22, 0x2a, "v_rcp_f32", True, True),
+ (0x2b, 0x2b, 0x23, 0x23, 0x2b, "v_rcp_iflag_f32", True, True),
+ (0x2c, 0x2c, -1, -1, -1, "v_rsq_clamp_f32", True, True),
+ (0x2d, 0x2d, -1, -1, -1, "v_rsq_legacy_f32", True, True),
+ (0x2e, 0x2e, 0x24, 0x24, 0x2e, "v_rsq_f32", True, True),
+ (0x2f, 0x2f, 0x25, 0x25, 0x2f, "v_rcp_f64", True, True),
+ (0x30, 0x30, -1, -1, -1, "v_rcp_clamp_f64", True, True),
+ (0x31, 0x31, 0x26, 0x26, 0x31, "v_rsq_f64", True, True),
+ (0x32, 0x32, -1, -1, -1, "v_rsq_clamp_f64", True, True),
+ (0x33, 0x33, 0x27, 0x27, 0x33, "v_sqrt_f32", True, True),
+ (0x34, 0x34, 0x28, 0x28, 0x34, "v_sqrt_f64", True, True),
+ (0x35, 0x35, 0x29, 0x29, 0x35, "v_sin_f32", True, True),
+ (0x36, 0x36, 0x2a, 0x2a, 0x36, "v_cos_f32", True, True),
+ (0x37, 0x37, 0x2b, 0x2b, 0x37, "v_not_b32", False, False),
+ (0x38, 0x38, 0x2c, 0x2c, 0x38, "v_bfrev_b32", False, False),
+ (0x39, 0x39, 0x2d, 0x2d, 0x39, "v_ffbh_u32", False, False),
+ (0x3a, 0x3a, 0x2e, 0x2e, 0x3a, "v_ffbl_b32", False, False),
+ (0x3b, 0x3b, 0x2f, 0x2f, 0x3b, "v_ffbh_i32", False, False),
+ (0x3c, 0x3c, 0x30, 0x30, 0x3c, "v_frexp_exp_i32_f64", True, False),
+ (0x3d, 0x3d, 0x31, 0x31, 0x3d, "v_frexp_mant_f64", True, False),
+ (0x3e, 0x3e, 0x32, 0x32, 0x3e, "v_fract_f64", True, True),
+ (0x3f, 0x3f, 0x33, 0x33, 0x3f, "v_frexp_exp_i32_f32", True, False),
+ (0x40, 0x40, 0x34, 0x34, 0x40, "v_frexp_mant_f32", True, False),
+ (0x41, 0x41, 0x35, 0x35, 0x41, "v_clrexcp", False, False),
+ (0x42, 0x42, 0x36, -1, 0x42, "v_movreld_b32", False, False),
+ (0x43, 0x43, 0x37, -1, 0x43, "v_movrels_b32", False, False),
+ (0x44, 0x44, 0x38, -1, 0x44, "v_movrelsd_b32", False, False),
+ ( -1, -1, -1, -1, 0x48, "v_movrelsd_2_b32", False, False),
+ ( -1, -1, -1, 0x37, -1, "v_screen_partition_4se_b32", False, False),
+ ( -1, -1, 0x39, 0x39, 0x50, "v_cvt_f16_u16", False, True),
+ ( -1, -1, 0x3a, 0x3a, 0x51, "v_cvt_f16_i16", False, True),
+ ( -1, -1, 0x3b, 0x3b, 0x52, "v_cvt_u16_f16", True, False),
+ ( -1, -1, 0x3c, 0x3c, 0x53, "v_cvt_i16_f16", True, False),
+ ( -1, -1, 0x3d, 0x3d, 0x54, "v_rcp_f16", True, True),
+ ( -1, -1, 0x3e, 0x3e, 0x55, "v_sqrt_f16", True, True),
+ ( -1, -1, 0x3f, 0x3f, 0x56, "v_rsq_f16", True, True),
+ ( -1, -1, 0x40, 0x40, 0x57, "v_log_f16", True, True),
+ ( -1, -1, 0x41, 0x41, 0x58, "v_exp_f16", True, True),
+ ( -1, -1, 0x42, 0x42, 0x59, "v_frexp_mant_f16", True, False),
+ ( -1, -1, 0x43, 0x43, 0x5a, "v_frexp_exp_i16_f16", True, False),
+ ( -1, -1, 0x44, 0x44, 0x5b, "v_floor_f16", True, True),
+ ( -1, -1, 0x45, 0x45, 0x5c, "v_ceil_f16", True, True),
+ ( -1, -1, 0x46, 0x46, 0x5d, "v_trunc_f16", True, True),
+ ( -1, -1, 0x47, 0x47, 0x5e, "v_rndne_f16", True, True),
+ ( -1, -1, 0x48, 0x48, 0x5f, "v_fract_f16", True, True),
+ ( -1, -1, 0x49, 0x49, 0x60, "v_sin_f16", True, True),
+ ( -1, -1, 0x4a, 0x4a, 0x61, "v_cos_f16", True, True),
+ ( -1, 0x46, 0x4b, 0x4b, -1, "v_exp_legacy_f32", True, True),
+ ( -1, 0x45, 0x4c, 0x4c, -1, "v_log_legacy_f32", True, True),
+ ( -1, -1, -1, 0x4f, 0x62, "v_sat_pk_u8_i16", False, False),
+ ( -1, -1, -1, 0x4d, 0x63, "v_cvt_norm_i16_f16", True, False),
+ ( -1, -1, -1, 0x4e, 0x64, "v_cvt_norm_u16_f16", True, False),
+ ( -1, -1, -1, 0x51, 0x65, "v_swap_b32", False, False),
+ ( -1, -1, -1, -1, 0x68, "v_swaprel_b32", False, False),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name, in_mod, out_mod) in VOP1:
+ opcode(name, gfx9, gfx10, Format.VOP1, in_mod, out_mod)
+
+
+# VOPC instructions:
+
+VOPC_CLASS = {
+ (0x88, 0x88, 0x10, 0x10, 0x88, "v_cmp_class_f32"),
+ ( -1, -1, 0x14, 0x14, 0x8f, "v_cmp_class_f16"),
+ (0x98, 0x98, 0x11, 0x11, 0x98, "v_cmpx_class_f32"),
+ ( -1, -1, 0x15, 0x15, 0x9f, "v_cmpx_class_f16"),
+ (0xa8, 0xa8, 0x12, 0x12, 0xa8, "v_cmp_class_f64"),
+ (0xb8, 0xb8, 0x13, 0x13, 0xb8, "v_cmpx_class_f64"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in VOPC_CLASS:
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+
+COMPF = ["f", "lt", "eq", "le", "gt", "lg", "ge", "o", "u", "nge", "nlg", "ngt", "nle", "neq", "nlt", "tru"]
+
+for i in range(8):
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0x20+i, 0x20+i, 0xc8+i, "v_cmp_"+COMPF[i]+"_f16")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0x30+i, 0x30+i, 0xd8+i, "v_cmpx_"+COMPF[i]+"_f16")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0x28+i, 0x28+i, 0xe8+i, "v_cmp_"+COMPF[i+8]+"_f16")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0x38+i, 0x38+i, 0xf8+i, "v_cmpx_"+COMPF[i+8]+"_f16")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+
+for i in range(16):
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x00+i, 0x00+i, 0x40+i, 0x40+i, 0x00+i, "v_cmp_"+COMPF[i]+"_f32")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x10+i, 0x10+i, 0x50+i, 0x50+i, 0x10+i, "v_cmpx_"+COMPF[i]+"_f32")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x20+i, 0x20+i, 0x60+i, 0x60+i, 0x20+i, "v_cmp_"+COMPF[i]+"_f64")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x30+i, 0x30+i, 0x70+i, 0x70+i, 0x30+i, "v_cmpx_"+COMPF[i]+"_f64")
+ opcode(name, gfx9, gfx10, Format.VOPC, True, False)
+ # GFX_6_7
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x40+i, 0x40+i, -1, -1, -1, "v_cmps_"+COMPF[i]+"_f32")
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x50+i, 0x50+i, -1, -1, -1, "v_cmpsx_"+COMPF[i]+"_f32")
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x60+i, 0x60+i, -1, -1, -1, "v_cmps_"+COMPF[i]+"_f64")
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x70+i, 0x70+i, -1, -1, -1, "v_cmpsx_"+COMPF[i]+"_f64")
+
+COMPI = ["f", "lt", "eq", "le", "gt", "lg", "ge", "tru"]
+
+# GFX_8_9
+for i in [0,7]: # only 0 and 7
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xa0+i, 0xa0+i, -1, "v_cmp_"+COMPI[i]+"_i16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xb0+i, 0xb0+i, -1, "v_cmpx_"+COMPI[i]+"_i16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xa8+i, 0xa8+i, -1, "v_cmp_"+COMPI[i]+"_u16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xb8+i, 0xb8+i, -1, "v_cmpx_"+COMPI[i]+"_u16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+
+for i in range(1, 7): # [1..6]
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xa0+i, 0xa0+i, 0x88+i, "v_cmp_"+COMPI[i]+"_i16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xb0+i, 0xb0+i, 0x98+i, "v_cmpx_"+COMPI[i]+"_i16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xa8+i, 0xa8+i, 0xa8+i, "v_cmp_"+COMPI[i]+"_u16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, 0xb8+i, 0xb8+i, 0xb8+i, "v_cmpx_"+COMPI[i]+"_u16")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+
+for i in range(8):
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x80+i, 0x80+i, 0xc0+i, 0xc0+i, 0x80+i, "v_cmp_"+COMPI[i]+"_i32")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0x90+i, 0x90+i, 0xd0+i, 0xd0+i, 0x90+i, "v_cmpx_"+COMPI[i]+"_i32")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xa0+i, 0xa0+i, 0xe0+i, 0xe0+i, 0xa0+i, "v_cmp_"+COMPI[i]+"_i64")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xb0+i, 0xb0+i, 0xf0+i, 0xf0+i, 0xb0+i, "v_cmpx_"+COMPI[i]+"_i64")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xc0+i, 0xc0+i, 0xc8+i, 0xc8+i, 0xc0+i, "v_cmp_"+COMPI[i]+"_u32")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xd0+i, 0xd0+i, 0xd8+i, 0xd8+i, 0xd0+i, "v_cmpx_"+COMPI[i]+"_u32")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xe0+i, 0xe0+i, 0xe8+i, 0xe8+i, 0xe0+i, "v_cmp_"+COMPI[i]+"_u64")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+ (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (0xf0+i, 0xf0+i, 0xf8+i, 0xf8+i, 0xf0+i, "v_cmpx_"+COMPI[i]+"_u64")
+ opcode(name, gfx9, gfx10, Format.VOPC)
+
+
+# VOPP instructions: packed 16bit instructions - 1 or 2 inputs and 1 output
+VOPP = {
+ (0x00, "v_pk_mad_i16"),
+ (0x01, "v_pk_mul_lo_u16"),
+ (0x02, "v_pk_add_i16"),
+ (0x03, "v_pk_sub_i16"),
+ (0x04, "v_pk_lshlrev_b16"),
+ (0x05, "v_pk_lshrrev_b16"),
+ (0x06, "v_pk_ashrrev_i16"),
+ (0x07, "v_pk_max_i16"),
+ (0x08, "v_pk_min_i16"),
+ (0x09, "v_pk_mad_u16"),
+ (0x0a, "v_pk_add_u16"),
+ (0x0b, "v_pk_sub_u16"),
+ (0x0c, "v_pk_max_u16"),
+ (0x0d, "v_pk_min_u16"),
+ (0x0e, "v_pk_fma_f16"),
+ (0x0f, "v_pk_add_f16"),
+ (0x10, "v_pk_mul_f16"),
+ (0x11, "v_pk_min_f16"),
+ (0x12, "v_pk_max_f16"),
+ (0x20, "v_pk_fma_mix_f32"), # v_mad_mix_f32 in VEGA ISA, v_fma_mix_f32 in RDNA ISA
+ (0x21, "v_pk_fma_mixlo_f16"), # v_mad_mixlo_f16 in VEGA ISA, v_fma_mixlo_f16 in RDNA ISA
+ (0x22, "v_pk_fma_mixhi_f16"), # v_mad_mixhi_f16 in VEGA ISA, v_fma_mixhi_f16 in RDNA ISA
+}
+# note that these are only supported on gfx9+ so we'll need to distinguish between gfx8 and gfx9 here
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (-1, -1, -1, code, code, name)
+for (code, name) in VOPP:
+ opcode(name, code, code, Format.VOP3P)
+
+
+# VINTERP instructions:
+VINTRP = {
+ (0x00, "v_interp_p1_f32"),
+ (0x01, "v_interp_p2_f32"),
+ (0x02, "v_interp_mov_f32"),
+}
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (code, code, code, code, code, name)
+for (code, name) in VINTRP:
+ opcode(name, code, code, Format.VINTRP)
+
+# VOP3 instructions: 3 inputs, 1 output
+# VOP3b instructions: have a unique scalar output, e.g. VOP2 with vcc out
+VOP3 = {
+ (0x140, 0x140, 0x1c0, 0x1c0, 0x140, "v_mad_legacy_f32", True, True),
+ (0x141, 0x141, 0x1c1, 0x1c1, 0x141, "v_mad_f32", True, True),
+ (0x142, 0x142, 0x1c2, 0x1c2, 0x142, "v_mad_i32_i24", False, False),
+ (0x143, 0x143, 0x1c3, 0x1c3, 0x143, "v_mad_u32_u24", False, False),
+ (0x144, 0x144, 0x1c4, 0x1c4, 0x144, "v_cubeid_f32", True, True),
+ (0x145, 0x145, 0x1c5, 0x1c5, 0x145, "v_cubesc_f32", True, True),
+ (0x146, 0x146, 0x1c6, 0x1c6, 0x146, "v_cubetc_f32", True, True),
+ (0x147, 0x147, 0x1c7, 0x1c7, 0x147, "v_cubema_f32", True, True),
+ (0x148, 0x148, 0x1c8, 0x1c8, 0x148, "v_bfe_u32", False, False),
+ (0x149, 0x149, 0x1c9, 0x1c9, 0x149, "v_bfe_i32", False, False),
+ (0x14a, 0x14a, 0x1ca, 0x1ca, 0x14a, "v_bfi_b32", False, False),
+ (0x14b, 0x14b, 0x1cb, 0x1cb, 0x14b, "v_fma_f32", True, True),
+ (0x14c, 0x14c, 0x1cc, 0x1cc, 0x14c, "v_fma_f64", True, True),
+ (0x14d, 0x14d, 0x1cd, 0x1cd, 0x14d, "v_lerp_u8", False, False),
+ (0x14e, 0x14e, 0x1ce, 0x1ce, 0x14e, "v_alignbit_b32", False, False),
+ (0x14f, 0x14f, 0x1cf, 0x1cf, 0x14f, "v_alignbyte_b32", False, False),
+ (0x150, 0x150, -1, -1, 0x150, "v_mullit_f32", True, True),
+ (0x151, 0x151, 0x1d0, 0x1d0, 0x151, "v_min3_f32", True, True),
+ (0x152, 0x152, 0x1d1, 0x1d1, 0x152, "v_min3_i32", False, False),
+ (0x153, 0x153, 0x1d2, 0x1d2, 0x153, "v_min3_u32", False, False),
+ (0x154, 0x154, 0x1d3, 0x1d3, 0x154, "v_max3_f32", True, True),
+ (0x155, 0x155, 0x1d4, 0x1d4, 0x155, "v_max3_i32", False, False),
+ (0x156, 0x156, 0x1d5, 0x1d5, 0x156, "v_max3_u32", False, False),
+ (0x157, 0x157, 0x1d6, 0x1d6, 0x157, "v_med3_f32", True, True),
+ (0x158, 0x158, 0x1d7, 0x1d7, 0x158, "v_med3_i32", False, False),
+ (0x159, 0x159, 0x1d8, 0x1d8, 0x159, "v_med3_u32", False, False),
+ (0x15a, 0x15a, 0x1d9, 0x1d9, 0x15a, "v_sad_u8", False, False),
+ (0x15b, 0x15b, 0x1da, 0x1da, 0x15b, "v_sad_hi_u8", False, False),
+ (0x15c, 0x15c, 0x1db, 0x1db, 0x15c, "v_sad_u16", False, False),
+ (0x15d, 0x15d, 0x1dc, 0x1dc, 0x15d, "v_sad_u32", False, False),
+ (0x15e, 0x15e, 0x1dd, 0x1dd, 0x15e, "v_cvt_pk_u8_f32", True, False),
+ (0x15f, 0x15f, 0x1de, 0x1de, 0x15f, "v_div_fixup_f32", True, True),
+ (0x160, 0x160, 0x1df, 0x1df, 0x160, "v_div_fixup_f64", True, True),
+ (0x161, 0x161, -1, -1, -1, "v_lshl_b64", False, False),
+ (0x162, 0x162, -1, -1, -1, "v_lshr_b64", False, False),
+ (0x163, 0x163, -1, -1, -1, "v_ashr_i64", False, False),
+ (0x164, 0x164, 0x280, 0x280, 0x164, "v_add_f64", True, True),
+ (0x165, 0x165, 0x281, 0x281, 0x165, "v_mul_f64", True, True),
+ (0x166, 0x166, 0x282, 0x282, 0x166, "v_min_f64", True, True),
+ (0x167, 0x167, 0x283, 0x283, 0x167, "v_max_f64", True, True),
+ (0x168, 0x168, 0x284, 0x284, 0x168, "v_ldexp_f64", False, True), # src1 can take input modifiers
+ (0x169, 0x169, 0x285, 0x285, 0x169, "v_mul_lo_u32", False, False),
+ (0x16a, 0x16a, 0x286, 0x286, 0x16a, "v_mul_hi_u32", False, False),
+ (0x16b, 0x16b, 0x285, 0x285, 0x16b, "v_mul_lo_i32", False, False), # identical to v_mul_lo_u32
+ (0x16c, 0x16c, 0x287, 0x287, 0x16c, "v_mul_hi_i32", False, False),
+ (0x16d, 0x16d, 0x1e0, 0x1e0, 0x16d, "v_div_scale_f32", True, True), # writes to VCC
+ (0x16e, 0x16e, 0x1e1, 0x1e1, 0x16e, "v_div_scale_f64", True, True), # writes to VCC
+ (0x16f, 0x16f, 0x1e2, 0x1e2, 0x16f, "v_div_fmas_f32", True, True), # takes VCC input
+ (0x170, 0x170, 0x1e3, 0x1e3, 0x170, "v_div_fmas_f64", True, True), # takes VCC input
+ (0x171, 0x171, 0x1e4, 0x1e4, 0x171, "v_msad_u8", False, False),
+ (0x172, 0x172, 0x1e5, 0x1e5, 0x172, "v_qsad_pk_u16_u8", False, False),
+ (0x172, -1, -1, -1, -1, "v_qsad_u8", False, False), # what's the difference?
+ (0x173, 0x173, 0x1e6, 0x1e6, 0x173, "v_mqsad_pk_u16_u8", False, False),
+ (0x173, -1, -1, -1, -1, "v_mqsad_u8", False, False), # what's the difference?
+ (0x174, 0x174, 0x292, 0x292, 0x174, "v_trig_preop_f64", False, False),
+ ( -1, 0x175, 0x1e7, 0x1e7, 0x175, "v_mqsad_u32_u8", False, False),
+ ( -1, 0x176, 0x1e8, 0x1e8, 0x176, "v_mad_u64_u32", False, False),
+ ( -1, 0x177, 0x1e9, 0x1e9, 0x177, "v_mad_i64_i32", False, False),
+ ( -1, -1, 0x1ea, 0x1ea, -1, "v_mad_legacy_f16", True, True),
+ ( -1, -1, 0x1eb, 0x1eb, -1, "v_mad_legacy_u16", False, False),
+ ( -1, -1, 0x1ec, 0x1ec, -1, "v_mad_legacy_i16", False, False),
+ ( -1, -1, 0x1ed, 0x1ed, 0x344, "v_perm_b32", False, False),
+ ( -1, -1, 0x1ee, 0x1ee, -1, "v_fma_legacy_f16", True, True),
+ ( -1, -1, 0x1ef, 0x1ef, -1, "v_div_fixup_legacy_f16", True, True),
+ (0x12c, 0x12c, 0x1f0, 0x1f0, -1, "v_cvt_pkaccum_u8_f32", True, False),
+ ( -1, -1, -1, 0x1f1, 0x373, "v_mad_u32_u16", False, False),
+ ( -1, -1, -1, 0x1f2, 0x375, "v_mad_i32_i16", False, False),
+ ( -1, -1, -1, 0x1f2, 0x345, "v_xad_u32", False, False),
+ ( -1, -1, -1, 0x1f4, 0x351, "v_min3_f16", True, True),
+ ( -1, -1, -1, 0x1f5, 0x352, "v_min3_i16", False, False),
+ ( -1, -1, -1, 0x1f6, 0x353, "v_min3_u16", False, False),
+ ( -1, -1, -1, 0x1f7, 0x354, "v_max3_f16", True, True),
+ ( -1, -1, -1, 0x1f8, 0x355, "v_max3_i16", False, False),
+ ( -1, -1, -1, 0x1f9, 0x356, "v_max3_u16", False, False),
+ ( -1, -1, -1, 0x1fa, 0x357, "v_med3_f16", True, True),
+ ( -1, -1, -1, 0x1fb, 0x358, "v_med3_i16", False, False),
+ ( -1, -1, -1, 0x1fc, 0x359, "v_med3_u16", False, False),
+ ( -1, -1, -1, 0x1fd, 0x346, "v_lshl_add_u32", False, False),
+ ( -1, -1, -1, 0x1fe, 0x347, "v_add_lshl_u32", False, False),
+ ( -1, -1, -1, 0x1ff, 0x36d, "v_add3_u32", False, False),
+ ( -1, -1, -1, 0x200, 0x36f, "v_lshl_or_b32", False, False),
+ ( -1, -1, -1, 0x201, 0x371, "v_and_or_b32", False, False),
+ ( -1, -1, -1, 0x202, 0x372, "v_or3_b32", False, False),
+ ( -1, -1, -1, 0x203, -1, "v_mad_f16", True, True),
+ ( -1, -1, -1, 0x204, 0x340, "v_mad_u16", False, False),
+ ( -1, -1, -1, 0x205, 0x35e, "v_mad_i16", False, False),
+ ( -1, -1, -1, 0x206, 0x34b, "v_fma_f16", True, True),
+ ( -1, -1, -1, 0x207, 0x35f, "v_div_fixup_f16", True, True),
+ ( -1, -1, 0x274, 0x274, 0x342, "v_interp_p1ll_f16", True, True),
+ ( -1, -1, 0x275, 0x275, 0x343, "v_interp_p1lv_f16", True, True),
+ ( -1, -1, 0x276, 0x276, -1, "v_interp_p2_legacy_f16", True, True),
+ ( -1, -1, -1, 0x277, 0x35a, "v_interp_p2_f16", True, True),
+ (0x12b, 0x12b, 0x288, 0x288, 0x362, "v_ldexp_f32", False, True),
+ (0x101, 0x101, 0x289, 0x289, 0x360, "v_readlane_b32", False, False),
+ (0x102, 0x102, 0x28a, 0x28a, 0x361, "v_writelane_b32", False, False),
+ (0x122, 0x122, 0x28b, 0x28b, 0x364, "v_bcnt_u32_b32", False, False),
+ (0x123, 0x123, 0x28c, 0x28c, 0x365, "v_mbcnt_lo_u32_b32", False, False),
+ (0x124, 0x124, 0x28d, 0x28d, 0x366, "v_mbcnt_hi_u32_b32", False, False),
+ ( -1, -1, 0x28f, 0x28f, 0x2ff, "v_lshlrev_b64", False, False),
+ ( -1, -1, 0x290, 0x290, 0x300, "v_lshrrev_b64", False, False),
+ ( -1, -1, 0x291, 0x291, 0x301, "v_ashrrev_i64", False, False),
+ (0x11e, 0x11e, 0x293, 0x293, 0x363, "v_bfm_b32", False, False),
+ (0x12d, 0x12d, 0x294, 0x294, 0x368, "v_cvt_pknorm_i16_f32", True, False),
+ (0x12e, 0x12e, 0x295, 0x295, 0x369, "v_cvt_pknorm_u16_f32", True, False),
+ (0x12f, 0x12f, 0x296, 0x296, 0x12f, "v_cvt_pkrtz_f16_f32", True, False), # GFX6_7_10 is VOP2 with opcode 0x02f
+ (0x130, 0x130, 0x297, 0x297, 0x36a, "v_cvt_pk_u16_u32", False, False),
+ (0x131, 0x131, 0x298, 0x298, 0x36b, "v_cvt_pk_i16_i32", False, False),
+ ( -1, -1, -1, 0x299, 0x312, "v_cvt_pknorm_i16_f16", True, False),
+ ( -1, -1, -1, 0x29a, 0x313, "v_cvt_pknorm_u16_f16", True, False),
+ ( -1, -1, -1, 0x29c, 0x37f, "v_add_i32", False, False),
+ ( -1, -1, -1, 0x29d, 0x376, "v_sub_i32", False, False),
+ ( -1, -1, -1, 0x29e, 0x30d, "v_add_i16", False, False),
+ ( -1, -1, -1, 0x29f, 0x30e, "v_sub_i16", False, False),
+ ( -1, -1, -1, 0x2a0, 0x311, "v_pack_b32_f16", True, False),
+ ( -1, -1, -1, -1, 0x178, "v_xor3_b32", False, False),
+ ( -1, -1, -1, -1, 0x377, "v_permlane16_b32", False, False),
+ ( -1, -1, -1, -1, 0x378, "v_permlanex16_b32", False, False),
+ ( -1, -1, -1, -1, 0x30f, "v_add_co_u32_e64", False, False),
+ ( -1, -1, -1, -1, 0x310, "v_sub_co_u32_e64", False, False),
+ ( -1, -1, -1, -1, 0x311, "v_subrev_co_u32_e64", False, False),
+# TODO: many 16bit instructions moved from VOP2 to VOP3 on GFX10
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name, in_mod, out_mod) in VOP3:
+ opcode(name, gfx9, gfx10, Format.VOP3A, in_mod, out_mod)
+
+
+# DS instructions: 3 inputs (1 addr, 2 data), 1 output
+DS = {
+ (0x00, 0x00, 0x00, 0x00, 0x00, "ds_add_u32"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "ds_sub_u32"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "ds_rsub_u32"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "ds_inc_u32"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "ds_dec_u32"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "ds_min_i32"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "ds_max_i32"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "ds_min_u32"),
+ (0x08, 0x08, 0x08, 0x08, 0x08, "ds_max_u32"),
+ (0x09, 0x09, 0x09, 0x09, 0x09, "ds_and_b32"),
+ (0x0a, 0x0a, 0x0a, 0x0a, 0x0a, "ds_or_b32"),
+ (0x0b, 0x0b, 0x0b, 0x0b, 0x0b, "ds_xor_b32"),
+ (0x0c, 0x0c, 0x0c, 0x0c, 0x0c, "ds_mskor_b32"),
+ (0x0d, 0x0d, 0x0d, 0x0d, 0x0d, "ds_write_b32"),
+ (0x0e, 0x0e, 0x0e, 0x0e, 0x0e, "ds_write2_b32"),
+ (0x0f, 0x0f, 0x0f, 0x0f, 0x0f, "ds_write2st64_b32"),
+ (0x10, 0x10, 0x10, 0x10, 0x10, "ds_cmpst_b32"),
+ (0x11, 0x11, 0x11, 0x11, 0x11, "ds_cmpst_f32"),
+ (0x12, 0x12, 0x12, 0x12, 0x12, "ds_min_f32"),
+ (0x13, 0x13, 0x13, 0x13, 0x13, "ds_max_f32"),
+ ( -1, 0x14, 0x14, 0x14, 0x14, "ds_nop"),
+ ( -1, -1, 0x15, 0x15, 0x15, "ds_add_f32"),
+ ( -1, -1, 0x1d, 0x1d, 0xb0, "ds_write_addtid_b32"),
+ (0x1e, 0x1e, 0x1e, 0x1e, 0x1e, "ds_write_b8"),
+ (0x1f, 0x1f, 0x1f, 0x1f, 0x1f, "ds_write_b16"),
+ (0x20, 0x20, 0x20, 0x20, 0x20, "ds_add_rtn_u32"),
+ (0x21, 0x21, 0x21, 0x21, 0x21, "ds_sub_rtn_u32"),
+ (0x22, 0x22, 0x22, 0x22, 0x22, "ds_rsub_rtn_u32"),
+ (0x23, 0x23, 0x23, 0x23, 0x23, "ds_inc_rtn_u32"),
+ (0x24, 0x24, 0x24, 0x24, 0x24, "ds_dec_rtn_u32"),
+ (0x25, 0x25, 0x25, 0x25, 0x25, "ds_min_rtn_i32"),
+ (0x26, 0x26, 0x26, 0x26, 0x26, "ds_max_rtn_i32"),
+ (0x27, 0x27, 0x27, 0x27, 0x27, "ds_min_rtn_u32"),
+ (0x28, 0x28, 0x28, 0x28, 0x28, "ds_max_rtn_u32"),
+ (0x29, 0x29, 0x29, 0x29, 0x29, "ds_and_rtn_b32"),
+ (0x2a, 0x2a, 0x2a, 0x2a, 0x2a, "ds_or_rtn_b32"),
+ (0x2b, 0x2b, 0x2b, 0x2b, 0x2b, "ds_xor_rtn_b32"),
+ (0x2c, 0x2c, 0x2c, 0x2c, 0x2c, "ds_mskor_rtn_b32"),
+ (0x2d, 0x2d, 0x2d, 0x2d, 0x2d, "ds_wrxchg_rtn_b32"),
+ (0x2e, 0x2e, 0x2e, 0x2e, 0x2e, "ds_wrxchg2_rtn_b32"),
+ (0x2f, 0x2f, 0x2f, 0x2f, 0x2f, "ds_wrxchg2st64_rtn_b32"),
+ (0x30, 0x30, 0x30, 0x30, 0x30, "ds_cmpst_rtn_b32"),
+ (0x31, 0x31, 0x31, 0x31, 0x31, "ds_cmpst_rtn_f32"),
+ (0x32, 0x32, 0x32, 0x32, 0x32, "ds_min_rtn_f32"),
+ (0x33, 0x33, 0x33, 0x33, 0x33, "ds_max_rtn_f32"),
+ ( -1, 0x34, 0x34, 0x34, 0x34, "ds_wrap_rtn_b32"),
+ ( -1, -1, 0x35, 0x35, 0x55, "ds_add_rtn_f32"),
+ (0x36, 0x36, 0x36, 0x36, 0x36, "ds_read_b32"),
+ (0x37, 0x37, 0x37, 0x37, 0x37, "ds_read2_b32"),
+ (0x38, 0x38, 0x38, 0x38, 0x38, "ds_read2st64_b32"),
+ (0x39, 0x39, 0x39, 0x39, 0x39, "ds_read_i8"),
+ (0x3a, 0x3a, 0x3a, 0x3a, 0x3a, "ds_read_u8"),
+ (0x3b, 0x3b, 0x3b, 0x3b, 0x3b, "ds_read_i16"),
+ (0x3c, 0x3c, 0x3c, 0x3c, 0x3c, "ds_read_u16"),
+ (0x35, 0x35, 0x3d, 0x3d, 0x35, "ds_swizzle_b32"), #data1 & offset, no addr/data2
+ ( -1, -1, 0x3e, 0x3e, 0xb2, "ds_permute_b32"),
+ ( -1, -1, 0x3f, 0x3f, 0xb3, "ds_bpermute_b32"),
+ (0x40, 0x40, 0x40, 0x40, 0x40, "ds_add_u64"),
+ (0x41, 0x41, 0x41, 0x41, 0x41, "ds_sub_u64"),
+ (0x42, 0x42, 0x42, 0x42, 0x42, "ds_rsub_u64"),
+ (0x43, 0x43, 0x43, 0x43, 0x43, "ds_inc_u64"),
+ (0x44, 0x44, 0x44, 0x44, 0x44, "ds_dec_u64"),
+ (0x45, 0x45, 0x45, 0x45, 0x45, "ds_min_i64"),
+ (0x46, 0x46, 0x46, 0x46, 0x46, "ds_max_i64"),
+ (0x47, 0x47, 0x47, 0x47, 0x47, "ds_min_u64"),
+ (0x48, 0x48, 0x48, 0x48, 0x48, "ds_max_u64"),
+ (0x49, 0x49, 0x49, 0x49, 0x49, "ds_and_b64"),
+ (0x4a, 0x4a, 0x4a, 0x4a, 0x4a, "ds_or_b64"),
+ (0x4b, 0x4b, 0x4b, 0x4b, 0x4b, "ds_xor_b64"),
+ (0x4c, 0x4c, 0x4c, 0x4c, 0x4c, "ds_mskor_b64"),
+ (0x4d, 0x4d, 0x4d, 0x4d, 0x4d, "ds_write_b64"),
+ (0x4e, 0x4e, 0x4e, 0x4e, 0x4e, "ds_write2_b64"),
+ (0x4f, 0x4f, 0x4f, 0x4f, 0x4f, "ds_write2st64_b64"),
+ (0x50, 0x50, 0x50, 0x50, 0x50, "ds_cmpst_b64"),
+ (0x51, 0x51, 0x51, 0x51, 0x51, "ds_cmpst_f64"),
+ (0x52, 0x52, 0x52, 0x52, 0x52, "ds_min_f64"),
+ (0x53, 0x53, 0x53, 0x53, 0x53, "ds_max_f64"),
+ ( -1, -1, 0x54, 0x54, 0xa0, "ds_write_b8_d16_hi"),
+ ( -1, -1, 0x55, 0x55, 0xa1, "ds_write_b16_d16_hi"),
+ ( -1, -1, 0x56, 0x56, 0xa2, "ds_read_u8_d16"),
+ ( -1, -1, 0x57, 0x57, 0xa3, "ds_read_u8_d16_hi"),
+ ( -1, -1, 0x58, 0x58, 0xa4, "ds_read_i8_d16"),
+ ( -1, -1, 0x59, 0x59, 0xa5, "ds_read_i8_d16_hi"),
+ ( -1, -1, 0x5a, 0x5a, 0xa6, "ds_read_u16_d16"),
+ ( -1, -1, 0x5b, 0x5b, 0xa7, "ds_read_u16_d16_hi"),
+ (0x60, 0x60, 0x60, 0x60, 0x60, "ds_add_rtn_u64"),
+ (0x61, 0x61, 0x61, 0x61, 0x61, "ds_sub_rtn_u64"),
+ (0x62, 0x62, 0x62, 0x62, 0x62, "ds_rsub_rtn_u64"),
+ (0x63, 0x63, 0x63, 0x63, 0x63, "ds_inc_rtn_u64"),
+ (0x64, 0x64, 0x64, 0x64, 0x64, "ds_dec_rtn_u64"),
+ (0x65, 0x65, 0x65, 0x65, 0x65, "ds_min_rtn_i64"),
+ (0x66, 0x66, 0x66, 0x66, 0x66, "ds_max_rtn_i64"),
+ (0x67, 0x67, 0x67, 0x67, 0x67, "ds_min_rtn_u64"),
+ (0x68, 0x68, 0x68, 0x68, 0x68, "ds_max_rtn_u64"),
+ (0x69, 0x69, 0x69, 0x69, 0x69, "ds_and_rtn_b64"),
+ (0x6a, 0x6a, 0x6a, 0x6a, 0x6a, "ds_or_rtn_b64"),
+ (0x6b, 0x6b, 0x6b, 0x6b, 0x6b, "ds_xor_rtn_b64"),
+ (0x6c, 0x6c, 0x6c, 0x6c, 0x6c, "ds_mskor_rtn_b64"),
+ (0x6d, 0x6d, 0x6d, 0x6d, 0x6d, "ds_wrxchg_rtn_b64"),
+ (0x6e, 0x6e, 0x6e, 0x6e, 0x6e, "ds_wrxchg2_rtn_b64"),
+ (0x6f, 0x6f, 0x6f, 0x6f, 0x6f, "ds_wrxchg2st64_rtn_b64"),
+ (0x70, 0x70, 0x70, 0x70, 0x70, "ds_cmpst_rtn_b64"),
+ (0x71, 0x71, 0x71, 0x71, 0x71, "ds_cmpst_rtn_f64"),
+ (0x72, 0x72, 0x72, 0x72, 0x72, "ds_min_rtn_f64"),
+ (0x73, 0x73, 0x73, 0x73, 0x73, "ds_max_rtn_f64"),
+ (0x76, 0x76, 0x76, 0x76, 0x76, "ds_read_b64"),
+ (0x77, 0x77, 0x77, 0x77, 0x77, "ds_read2_b64"),
+ (0x78, 0x78, 0x78, 0x78, 0x78, "ds_read2st64_b64"),
+ ( -1, 0x7e, 0x7e, 0x7e, 0x7e, "ds_condxchg32_rtn_b64"),
+ (0x80, 0x80, 0x80, 0x80, 0x80, "ds_add_src2_u32"),
+ (0x81, 0x81, 0x81, 0x81, 0x81, "ds_sub_src2_u32"),
+ (0x82, 0x82, 0x82, 0x82, 0x82, "ds_rsub_src2_u32"),
+ (0x83, 0x83, 0x83, 0x83, 0x83, "ds_inc_src2_u32"),
+ (0x84, 0x84, 0x84, 0x84, 0x84, "ds_dec_src2_u32"),
+ (0x85, 0x85, 0x85, 0x85, 0x85, "ds_min_src2_i32"),
+ (0x86, 0x86, 0x86, 0x86, 0x86, "ds_max_src2_i32"),
+ (0x87, 0x87, 0x87, 0x87, 0x87, "ds_min_src2_u32"),
+ (0x88, 0x88, 0x88, 0x88, 0x88, "ds_max_src2_u32"),
+ (0x89, 0x89, 0x89, 0x89, 0x89, "ds_and_src2_b32"),
+ (0x8a, 0x8a, 0x8a, 0x8a, 0x8a, "ds_or_src2_b32"),
+ (0x8b, 0x8b, 0x8b, 0x8b, 0x8b, "ds_xor_src2_b32"),
+ (0x8d, 0x8d, 0x8d, 0x8d, 0x8d, "ds_write_src2_b32"),
+ (0x92, 0x92, 0x92, 0x92, 0x92, "ds_min_src2_f32"),
+ (0x93, 0x93, 0x93, 0x93, 0x93, "ds_max_src2_f32"),
+ ( -1, -1, 0x95, 0x95, 0x95, "ds_add_src2_f32"),
+ ( -1, 0x18, 0x98, 0x98, 0x18, "ds_gws_sema_release_all"),
+ (0x19, 0x19, 0x99, 0x99, 0x19, "ds_gws_init"),
+ (0x1a, 0x1a, 0x9a, 0x9a, 0x1a, "ds_gws_sema_v"),
+ (0x1b, 0x1b, 0x9b, 0x9b, 0x1b, "ds_gws_sema_br"),
+ (0x1c, 0x1c, 0x9c, 0x9c, 0x1c, "ds_gws_sema_p"),
+ (0x1d, 0x1d, 0x9d, 0x9d, 0x1d, "ds_gws_barrier"),
+ ( -1, -1, 0xb6, 0xb6, 0xb1, "ds_read_addtid_b32"),
+ (0x3d, 0x3d, 0xbd, 0xbd, 0x3d, "ds_consume"),
+ (0x3e, 0x3e, 0xbe, 0xbe, 0x3e, "ds_append"),
+ (0x3f, 0x3f, 0xbf, 0xbf, 0x3f, "ds_ordered_count"),
+ (0xc0, 0xc0, 0xc0, 0xc0, 0xc0, "ds_add_src2_u64"),
+ (0xc1, 0xc1, 0xc1, 0xc1, 0xc1, "ds_sub_src2_u64"),
+ (0xc2, 0xc2, 0xc2, 0xc2, 0xc2, "ds_rsub_src2_u64"),
+ (0xc3, 0xc3, 0xc3, 0xc3, 0xc3, "ds_inc_src2_u64"),
+ (0xc4, 0xc4, 0xc4, 0xc4, 0xc4, "ds_dec_src2_u64"),
+ (0xc5, 0xc5, 0xc5, 0xc5, 0xc5, "ds_min_src2_i64"),
+ (0xc6, 0xc6, 0xc6, 0xc6, 0xc6, "ds_max_src2_i64"),
+ (0xc7, 0xc7, 0xc7, 0xc7, 0xc7, "ds_min_src2_u64"),
+ (0xc8, 0xc8, 0xc8, 0xc8, 0xc8, "ds_max_src2_u64"),
+ (0xc9, 0xc9, 0xc9, 0xc9, 0xc9, "ds_and_src2_b64"),
+ (0xca, 0xca, 0xca, 0xca, 0xca, "ds_or_src2_b64"),
+ (0xcb, 0xcb, 0xcb, 0xcb, 0xcb, "ds_xor_src2_b64"),
+ (0xcd, 0xcd, 0xcd, 0xcd, 0xcd, "ds_write_src2_b64"),
+ (0xd2, 0xd2, 0xd2, 0xd2, 0xd2, "ds_min_src2_f64"),
+ (0xd3, 0xd3, 0xd3, 0xd3, 0xd3, "ds_max_src2_f64"),
+ ( -1, 0xde, 0xde, 0xde, 0xde, "ds_write_b96"),
+ ( -1, 0xdf, 0xdf, 0xdf, 0xdf, "ds_write_b128"),
+ ( -1, 0xfd, 0xfd, -1, -1, "ds_condxchg32_rtn_b128"),
+ ( -1, 0xfe, 0xfe, 0xfe, 0xfe, "ds_read_b96"),
+ ( -1, 0xff, 0xff, 0xff, 0xff, "ds_read_b128"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in DS:
+ opcode(name, gfx9, gfx10, Format.DS)
+
+# MUBUF instructions:
+MUBUF = {
+ (0x00, 0x00, 0x00, 0x00, 0x00, "buffer_load_format_x"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "buffer_load_format_xy"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "buffer_load_format_xyz"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "buffer_load_format_xyzw"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "buffer_store_format_x"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "buffer_store_format_xy"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "buffer_store_format_xyz"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "buffer_store_format_xyzw"),
+ ( -1, -1, 0x08, 0x08, 0x80, "buffer_load_format_d16_x"),
+ ( -1, -1, 0x09, 0x09, 0x81, "buffer_load_format_d16_xy"),
+ ( -1, -1, 0x0a, 0x0a, 0x82, "buffer_load_format_d16_xyz"),
+ ( -1, -1, 0x0b, 0x0b, 0x83, "buffer_load_format_d16_xyzw"),
+ ( -1, -1, 0x0c, 0x0c, 0x84, "buffer_store_format_d16_x"),
+ ( -1, -1, 0x0d, 0x0d, 0x85, "buffer_store_format_d16_xy"),
+ ( -1, -1, 0x0e, 0x0e, 0x86, "buffer_store_format_d16_xyz"),
+ ( -1, -1, 0x0f, 0x0f, 0x87, "buffer_store_format_d16_xyzw"),
+ (0x08, 0x08, 0x10, 0x10, 0x08, "buffer_load_ubyte"),
+ (0x09, 0x09, 0x11, 0x11, 0x09, "buffer_load_sbyte"),
+ (0x0a, 0x0a, 0x12, 0x12, 0x0a, "buffer_load_ushort"),
+ (0x0b, 0x0b, 0x13, 0x13, 0x0b, "buffer_load_sshort"),
+ (0x0c, 0x0c, 0x14, 0x14, 0x0c, "buffer_load_dword"),
+ (0x0d, 0x0d, 0x15, 0x15, 0x0d, "buffer_load_dwordx2"),
+ ( -1, 0x0f, 0x16, 0x16, 0x0f, "buffer_load_dwordx3"),
+ (0x0f, 0x0e, 0x17, 0x17, 0x0e, "buffer_load_dwordx4"),
+ (0x18, 0x18, 0x18, 0x18, 0x18, "buffer_store_byte"),
+ ( -1, -1, -1, 0x19, 0x19, "buffer_store_byte_d16_hi"),
+ (0x1a, 0x1a, 0x1a, 0x1a, 0x1a, "buffer_store_short"),
+ ( -1, -1, -1, 0x1b, 0x1b, "buffer_store_short_d16_hi"),
+ (0x1c, 0x1c, 0x1c, 0x1c, 0x1c, "buffer_store_dword"),
+ (0x1d, 0x1d, 0x1d, 0x1d, 0x1d, "buffer_store_dwordx2"),
+ ( -1, 0x1f, 0x1e, 0x1e, 0x1f, "buffer_store_dwordx3"),
+ (0x1e, 0x1e, 0x1f, 0x1f, 0x1e, "buffer_store_dwordx4"),
+ ( -1, -1, -1, 0x20, 0x20, "buffer_load_ubyte_d16"),
+ ( -1, -1, -1, 0x21, 0x21, "buffer_load_ubyte_d16_hi"),
+ ( -1, -1, -1, 0x22, 0x22, "buffer_load_sbyte_d16"),
+ ( -1, -1, -1, 0x23, 0x23, "buffer_load_sbyte_d16_hi"),
+ ( -1, -1, -1, 0x24, 0x24, "buffer_load_short_d16"),
+ ( -1, -1, -1, 0x25, 0x25, "buffer_load_short_d16_hi"),
+ ( -1, -1, -1, 0x26, 0x26, "buffer_load_format_d16_hi_x"),
+ ( -1, -1, -1, 0x27, 0x27, "buffer_store_format_d16_hi_x"),
+ ( -1, -1, 0x3d, 0x3d, -1, "buffer_store_lds_dword"),
+ (0x71, 0x71, 0x3e, 0x3e, -1, "buffer_wbinvl1"),
+ (0x70, 0x70, 0x3f, 0x3f, -1, "buffer_wbinvl1_vol"),
+ (0x30, 0x30, 0x40, 0x40, 0x30, "buffer_atomic_swap"),
+ (0x31, 0x31, 0x41, 0x41, 0x31, "buffer_atomic_cmpswap"),
+ (0x32, 0x32, 0x42, 0x42, 0x32, "buffer_atomic_add"),
+ (0x33, 0x33, 0x43, 0x43, 0x33, "buffer_atomic_sub"),
+ (0x34, -1, -1, -1, -1, "buffer_atomic_rsub"),
+ (0x35, 0x35, 0x44, 0x44, 0x35, "buffer_atomic_smin"),
+ (0x36, 0x36, 0x45, 0x45, 0x36, "buffer_atomic_umin"),
+ (0x37, 0x37, 0x46, 0x46, 0x37, "buffer_atomic_smax"),
+ (0x38, 0x38, 0x47, 0x47, 0x38, "buffer_atomic_umax"),
+ (0x39, 0x39, 0x48, 0x48, 0x39, "buffer_atomic_and"),
+ (0x3a, 0x3a, 0x49, 0x49, 0x3a, "buffer_atomic_or"),
+ (0x3b, 0x3b, 0x4a, 0x4a, 0x3b, "buffer_atomic_xor"),
+ (0x3c, 0x3c, 0x4b, 0x4b, 0x3c, "buffer_atomic_inc"),
+ (0x3d, 0x3d, 0x4c, 0x4c, 0x3d, "buffer_atomic_dec"),
+ (0x3e, 0x3e, -1, -1, 0x3e, "buffer_atomic_fcmpswap"),
+ (0x3f, 0x3f, -1, -1, 0x3f, "buffer_atomic_fmin"),
+ (0x40, 0x40, -1, -1, 0x40, "buffer_atomic_fmax"),
+ (0x50, 0x50, 0x60, 0x60, 0x50, "buffer_atomic_swap_x2"),
+ (0x51, 0x51, 0x61, 0x61, 0x51, "buffer_atomic_cmpswap_x2"),
+ (0x52, 0x52, 0x62, 0x62, 0x52, "buffer_atomic_add_x2"),
+ (0x53, 0x53, 0x63, 0x63, 0x53, "buffer_atomic_sub_x2"),
+ (0x54, -1, -1, -1, -1, "buffer_atomic_rsub_x2"),
+ (0x55, 0x55, 0x64, 0x64, 0x55, "buffer_atomic_smin_x2"),
+ (0x56, 0x56, 0x65, 0x65, 0x56, "buffer_atomic_umin_x2"),
+ (0x57, 0x57, 0x66, 0x66, 0x57, "buffer_atomic_smax_x2"),
+ (0x58, 0x58, 0x67, 0x67, 0x58, "buffer_atomic_umax_x2"),
+ (0x59, 0x59, 0x68, 0x68, 0x59, "buffer_atomic_and_x2"),
+ (0x5a, 0x5a, 0x69, 0x69, 0x5a, "buffer_atomic_or_x2"),
+ (0x5b, 0x5b, 0x6a, 0x6a, 0x5b, "buffer_atomic_xor_x2"),
+ (0x5c, 0x5c, 0x6b, 0x6b, 0x5c, "buffer_atomic_inc_x2"),
+ (0x5d, 0x5d, 0x6c, 0x6c, 0x5d, "buffer_atomic_dec_x2"),
+ (0x5e, 0x5e, -1, -1, 0x5e, "buffer_atomic_fcmpswap_x2"),
+ (0x5f, 0x5f, -1, -1, 0x5f, "buffer_atomic_fmin_x2"),
+ (0x60, 0x60, -1, -1, 0x60, "buffer_atomic_fmax_x2"),
+ ( -1, -1, -1, -1, 0x71, "buffer_gl0_inv"),
+ ( -1, -1, -1, -1, 0x72, "buffer_gl1_inv"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in MUBUF:
+ opcode(name, gfx9, gfx10, Format.MUBUF)
+
+MTBUF = {
+ (0x00, 0x00, 0x00, 0x00, 0x00, "tbuffer_load_format_x"),
+ (0x01, 0x01, 0x01, 0x01, 0x01, "tbuffer_load_format_xy"),
+ (0x02, 0x02, 0x02, 0x02, 0x02, "tbuffer_load_format_xyz"),
+ (0x03, 0x03, 0x03, 0x03, 0x03, "tbuffer_load_format_xyzw"),
+ (0x04, 0x04, 0x04, 0x04, 0x04, "tbuffer_store_format_x"),
+ (0x05, 0x05, 0x05, 0x05, 0x05, "tbuffer_store_format_xy"),
+ (0x06, 0x06, 0x06, 0x06, 0x06, "tbuffer_store_format_xyz"),
+ (0x07, 0x07, 0x07, 0x07, 0x07, "tbuffer_store_format_xyzw"),
+ ( -1, -1, 0x08, 0x08, 0x08, "tbuffer_load_format_d16_x"),
+ ( -1, -1, 0x09, 0x09, 0x09, "tbuffer_load_format_d16_xy"),
+ ( -1, -1, 0x0a, 0x0a, 0x0a, "tbuffer_load_format_d16_xyz"),
+ ( -1, -1, 0x0b, 0x0b, 0x0b, "tbuffer_load_format_d16_xyzw"),
+ ( -1, -1, 0x0c, 0x0c, 0x0c, "tbuffer_store_format_d16_x"),
+ ( -1, -1, 0x0d, 0x0d, 0x0d, "tbuffer_store_format_d16_xy"),
+ ( -1, -1, 0x0e, 0x0e, 0x0e, "tbuffer_store_format_d16_xyz"),
+ ( -1, -1, 0x0f, 0x0f, 0x0f, "tbuffer_store_format_d16_xyzw"),
+}
+for (gfx6, gfx7, gfx8, gfx9, gfx10, name) in MTBUF:
+ opcode(name, gfx9, gfx10, Format.MTBUF)
+
+
+IMAGE = {
+ (0x00, "image_load"),
+ (0x01, "image_load_mip"),
+ (0x02, "image_load_pck"),
+ (0x03, "image_load_pck_sgn"),
+ (0x04, "image_load_mip_pck"),
+ (0x05, "image_load_mip_pck_sgn"),
+ (0x08, "image_store"),
+ (0x09, "image_store_mip"),
+ (0x0a, "image_store_pck"),
+ (0x0b, "image_store_mip_pck"),
+ (0x0e, "image_get_resinfo"),
+ (0x60, "image_get_lod"),
+}
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (code, code, code, code, code, name)
+for (code, name) in IMAGE:
+ opcode(name, code, code, Format.MIMG)
+
+IMAGE_ATOMIC = {
+ (0x0f, 0x0f, 0x10, "image_atomic_swap"),
+ (0x10, 0x10, 0x11, "image_atomic_cmpswap"),
+ (0x11, 0x11, 0x12, "image_atomic_add"),
+ (0x12, 0x12, 0x13, "image_atomic_sub"),
+ (0x13, -1, -1, "image_atomic_rsub"),
+ (0x14, 0x14, 0x14, "image_atomic_smin"),
+ (0x15, 0x15, 0x15, "image_atomic_umin"),
+ (0x16, 0x16, 0x16, "image_atomic_smax"),
+ (0x17, 0x17, 0x17, "image_atomic_umax"),
+ (0x18, 0x18, 0x18, "image_atomic_and"),
+ (0x19, 0x19, 0x19, "image_atomic_or"),
+ (0x1a, 0x1a, 0x1a, "image_atomic_xor"),
+ (0x1b, 0x1b, 0x1b, "image_atomic_inc"),
+ (0x1c, 0x1c, 0x1c, "image_atomic_dec"),
+ (0x1d, 0x1d, -1, "image_atomic_fcmpswap"),
+ (0x1e, 0x1e, -1, "image_atomic_fmin"),
+ (0x1f, 0x1f, -1, "image_atomic_fmax"),
+}
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (gfx6, gfx7, gfx89, gfx89, ???, name)
+# gfx7 and gfx10 opcodes are the same here
+for (gfx6, gfx7, gfx89, name) in IMAGE_ATOMIC:
+ opcode(name, gfx89, gfx7, Format.MIMG)
+
+IMAGE_SAMPLE = {
+ (0x20, "image_sample"),
+ (0x21, "image_sample_cl"),
+ (0x22, "image_sample_d"),
+ (0x23, "image_sample_d_cl"),
+ (0x24, "image_sample_l"),
+ (0x25, "image_sample_b"),
+ (0x26, "image_sample_b_cl"),
+ (0x27, "image_sample_lz"),
+ (0x28, "image_sample_c"),
+ (0x29, "image_sample_c_cl"),
+ (0x2a, "image_sample_c_d"),
+ (0x2b, "image_sample_c_d_cl"),
+ (0x2c, "image_sample_c_l"),
+ (0x2d, "image_sample_c_b"),
+ (0x2e, "image_sample_c_b_cl"),
+ (0x2f, "image_sample_c_lz"),
+ (0x30, "image_sample_o"),
+ (0x31, "image_sample_cl_o"),
+ (0x32, "image_sample_d_o"),
+ (0x33, "image_sample_d_cl_o"),
+ (0x34, "image_sample_l_o"),
+ (0x35, "image_sample_b_o"),
+ (0x36, "image_sample_b_cl_o"),
+ (0x37, "image_sample_lz_o"),
+ (0x38, "image_sample_c_o"),
+ (0x39, "image_sample_c_cl_o"),
+ (0x3a, "image_sample_c_d_o"),
+ (0x3b, "image_sample_c_d_cl_o"),
+ (0x3c, "image_sample_c_l_o"),
+ (0x3d, "image_sample_c_b_o"),
+ (0x3e, "image_sample_c_b_cl_o"),
+ (0x3f, "image_sample_c_lz_o"),
+ (0x68, "image_sample_cd"),
+ (0x69, "image_sample_cd_cl"),
+ (0x6a, "image_sample_c_cd"),
+ (0x6b, "image_sample_c_cd_cl"),
+ (0x6c, "image_sample_cd_o"),
+ (0x6d, "image_sample_cd_cl_o"),
+ (0x6e, "image_sample_c_cd_o"),
+ (0x6f, "image_sample_c_cd_cl_o"),
+}
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (code, code, code, code, code, name)
+for (code, name) in IMAGE_SAMPLE:
+ opcode(name, code, code, Format.MIMG)
+
+IMAGE_GATHER4 = {
+ (0x40, "image_gather4"),
+ (0x41, "image_gather4_cl"),
+ #(0x42, "image_gather4h"), VEGA only?
+ (0x44, "image_gather4_l"), # following instructions have different opcodes according to ISA sheet.
+ (0x45, "image_gather4_b"),
+ (0x46, "image_gather4_b_cl"),
+ (0x47, "image_gather4_lz"),
+ (0x48, "image_gather4_c"),
+ (0x49, "image_gather4_c_cl"), # previous instructions have different opcodes according to ISA sheet.
+ #(0x4a, "image_gather4h_pck"), VEGA only?
+ #(0x4b, "image_gather8h_pck"), VGEA only?
+ (0x4c, "image_gather4_c_l"),
+ (0x4d, "image_gather4_c_b"),
+ (0x4e, "image_gather4_c_b_cl"),
+ (0x4f, "image_gather4_c_lz"),
+ (0x50, "image_gather4_o"),
+ (0x51, "image_gather4_cl_o"),
+ (0x54, "image_gather4_l_o"),
+ (0x55, "image_gather4_b_o"),
+ (0x56, "image_gather4_b_cl_o"),
+ (0x57, "image_gather4_lz_o"),
+ (0x58, "image_gather4_c_o"),
+ (0x59, "image_gather4_c_cl_o"),
+ (0x5c, "image_gather4_c_l_o"),
+ (0x5d, "image_gather4_c_b_o"),
+ (0x5e, "image_gather4_c_b_cl_o"),
+ (0x5f, "image_gather4_c_lz_o"),
+}
+# (gfx6, gfx7, gfx8, gfx9, gfx10, name) = (code, code, code, code, code, name)
+for (code, name) in IMAGE_GATHER4:
+ opcode(name, code, code, Format.MIMG)
+
+
+FLAT = {
+ #GFX7, GFX8_9, GFX10
+ (0x08, 0x10, 0x08, "flat_load_ubyte"),
+ (0x09, 0x11, 0x09, "flat_load_sbyte"),
+ (0x0a, 0x12, 0x0a, "flat_load_ushort"),
+ (0x0b, 0x13, 0x0b, "flat_load_sshort"),
+ (0x0c, 0x14, 0x0c, "flat_load_dword"),
+ (0x0d, 0x15, 0x0d, "flat_load_dwordx2"),
+ (0x0f, 0x16, 0x0f, "flat_load_dwordx3"),
+ (0x0e, 0x17, 0x0e, "flat_load_dwordx4"),
+ (0x18, 0x18, 0x18, "flat_store_byte"),
+ ( -1, 0x19, 0x19, "flat_store_byte_d16_hi"),
+ (0x1a, 0x1a, 0x1a, "flat_store_short"),
+ ( -1, 0x1b, 0x1b, "flat_store_short_d16_hi"),
+ (0x1c, 0x1c, 0x1c, "flat_store_dword"),
+ (0x1d, 0x1d, 0x1d, "flat_store_dwordx2"),
+ (0x1f, 0x1e, 0x1f, "flat_store_dwordx3"),
+ (0x1e, 0x1f, 0x1e, "flat_store_dwordx4"),
+ ( -1, 0x20, 0x20, "flat_load_ubyte_d16"),
+ ( -1, 0x21, 0x21, "flat_load_ubyte_d16_hi"),
+ ( -1, 0x22, 0x22, "flat_load_sbyte_d16"),
+ ( -1, 0x23, 0x23, "flat_load_sbyte_d16_hi"),
+ ( -1, 0x24, 0x24, "flat_load_short_d16"),
+ ( -1, 0x25, 0x25, "flat_load_short_d16_hi"),
+ (0x30, 0x40, 0x30, "flat_atomic_swap"),
+ (0x31, 0x41, 0x31, "flat_atomic_cmpswap"),
+ (0x32, 0x42, 0x32, "flat_atomic_add"),
+ (0x33, 0x43, 0x33, "flat_atomic_sub"),
+ (0x35, 0x44, 0x35, "flat_atomic_smin"),
+ (0x36, 0x45, 0x36, "flat_atomic_umin"),
+ (0x37, 0x46, 0x37, "flat_atomic_smax"),
+ (0x38, 0x47, 0x38, "flat_atomic_umax"),
+ (0x39, 0x48, 0x39, "flat_atomic_and"),
+ (0x3a, 0x49, 0x3a, "flat_atomic_or"),
+ (0x3b, 0x4a, 0x3b, "flat_atomic_xor"),
+ (0x3c, 0x4b, 0x3c, "flat_atomic_inc"),
+ (0x3d, 0x4c, 0x3d, "flat_atomic_dec"),
+ (0x3e, -1, 0x3e, "flat_atomic_fcmpswap"),
+ (0x3f, -1, 0x3f, "flat_atomic_fmin"),
+ (0x40, -1, 0x40, "flat_atomic_fmax"),
+ (0x50, 0x60, 0x50, "flat_atomic_swap_x2"),
+ (0x51, 0x61, 0x51, "flat_atomic_cmpswap_x2"),
+ (0x52, 0x62, 0x52, "flat_atomic_add_x2"),
+ (0x53, 0x63, 0x53, "flat_atomic_sub_x2"),
+ (0x55, 0x64, 0x54, "flat_atomic_smin_x2"),
+ (0x56, 0x65, 0x55, "flat_atomic_umin_x2"),
+ (0x57, 0x66, 0x56, "flat_atomic_smax_x2"),
+ (0x58, 0x67, 0x58, "flat_atomic_umax_x2"),
+ (0x59, 0x68, 0x59, "flat_atomic_and_x2"),
+ (0x5a, 0x69, 0x5a, "flat_atomic_or_x2"),
+ (0x5b, 0x6a, 0x5b, "flat_atomic_xor_x2"),
+ (0x5c, 0x6b, 0x5c, "flat_atomic_inc_x2"),
+ (0x5d, 0x6c, 0x5d, "flat_atomic_dec_x2"),
+ (0x5e, -1, 0x5e, "flat_atomic_fcmpswap_x2"),
+ (0x5f, -1, 0x5f, "flat_atomic_fmin_x2"),
+ (0x60, -1, 0x60, "flat_atomic_fmax_x2"),
+}
+for (gfx7, gfx8, gfx10, name) in FLAT:
+ opcode(name, gfx8, gfx10, Format.FLAT)
+
+GLOBAL = {
+ #GFX8_9, GFX10
+ (0x10, 0x08, "global_load_ubyte"),
+ (0x11, 0x09, "global_load_sbyte"),
+ (0x12, 0x0a, "global_load_ushort"),
+ (0x13, 0x0b, "global_load_sshort"),
+ (0x14, 0x0c, "global_load_dword"),
+ (0x15, 0x0d, "global_load_dwordx2"),
+ (0x16, 0x0f, "global_load_dwordx3"),
+ (0x17, 0x0e, "global_load_dwordx4"),
+ (0x18, 0x18, "global_store_byte"),
+ (0x19, 0x19, "global_store_byte_d16_hi"),
+ (0x1a, 0x1a, "global_store_short"),
+ (0x1b, 0x1b, "global_store_short_d16_hi"),
+ (0x1c, 0x1c, "global_store_dword"),
+ (0x1d, 0x1d, "global_store_dwordx2"),
+ (0x1e, 0x1f, "global_store_dwordx3"),
+ (0x1f, 0x1e, "global_store_dwordx4"),
+ (0x20, 0x20, "global_load_ubyte_d16"),
+ (0x21, 0x21, "global_load_ubyte_d16_hi"),
+ (0x22, 0x22, "global_load_sbyte_d16"),
+ (0x23, 0x23, "global_load_sbyte_d16_hi"),
+ (0x24, 0x24, "global_load_short_d16"),
+ (0x25, 0x25, "global_load_short_d16_hi"),
+ (0x40, 0x30, "global_atomic_swap"),
+ (0x41, 0x31, "global_atomic_cmpswap"),
+ (0x42, 0x32, "global_atomic_add"),
+ (0x43, 0x33, "global_atomic_sub"),
+ (0x44, 0x35, "global_atomic_smin"),
+ (0x45, 0x36, "global_atomic_umin"),
+ (0x46, 0x37, "global_atomic_smax"),
+ (0x47, 0x38, "global_atomic_umax"),
+ (0x48, 0x39, "global_atomic_and"),
+ (0x49, 0x3a, "global_atomic_or"),
+ (0x4a, 0x3b, "global_atomic_xor"),
+ (0x4b, 0x3c, "global_atomic_inc"),
+ (0x4c, 0x3d, "global_atomic_dec"),
+ ( -1, 0x3e, "global_atomic_fcmpswap"),
+ ( -1, 0x3f, "global_atomic_fmin"),
+ ( -1, 0x40, "global_atomic_fmax"),
+ (0x60, 0x50, "global_atomic_swap_x2"),
+ (0x61, 0x51, "global_atomic_cmpswap_x2"),
+ (0x62, 0x52, "global_atomic_add_x2"),
+ (0x63, 0x53, "global_atomic_sub_x2"),
+ (0x64, 0x54, "global_atomic_smin_x2"),
+ (0x65, 0x55, "global_atomic_umin_x2"),
+ (0x66, 0x56, "global_atomic_smax_x2"),
+ (0x67, 0x58, "global_atomic_umax_x2"),
+ (0x68, 0x59, "global_atomic_and_x2"),
+ (0x69, 0x5a, "global_atomic_or_x2"),
+ (0x6a, 0x5b, "global_atomic_xor_x2"),
+ (0x6b, 0x5c, "global_atomic_inc_x2"),
+ (0x6c, 0x5d, "global_atomic_dec_x2"),
+ ( -1, 0x5e, "global_atomic_fcmpswap_x2"),
+ ( -1, 0x5f, "global_atomic_fmin_x2"),
+ ( -1, 0x60, "global_atomic_fmax_x2"),
+}
+for (gfx8, gfx10, name) in GLOBAL:
+ opcode(name, gfx8, gfx10, Format.GLOBAL)
+
+SCRATCH = {
+ #GFX8_9, GFX10
+ (0x10, 0x08, "scratch_load_ubyte"),
+ (0x11, 0x09, "scratch_load_sbyte"),
+ (0x12, 0x0a, "scratch_load_ushort"),
+ (0x13, 0x0b, "scratch_load_sshort"),
+ (0x14, 0x0c, "scratch_load_dword"),
+ (0x15, 0x0d, "scratch_load_dwordx2"),
+ (0x16, 0x0f, "scratch_load_dwordx3"),
+ (0x17, 0x0e, "scratch_load_dwordx4"),
+ (0x18, 0x18, "scratch_store_byte"),
+ (0x19, 0x19, "scratch_store_byte_d16_hi"),
+ (0x1a, 0x1a, "scratch_store_short"),
+ (0x1b, 0x1b, "scratch_store_short_d16_hi"),
+ (0x1c, 0x1c, "scratch_store_dword"),
+ (0x1d, 0x1d, "scratch_store_dwordx2"),
+ (0x1e, 0x1f, "scratch_store_dwordx3"),
+ (0x1f, 0x1e, "scratch_store_dwordx4"),
+ (0x20, 0x20, "scratch_load_ubyte_d16"),
+ (0x21, 0x21, "scratch_load_ubyte_d16_hi"),
+ (0x22, 0x22, "scratch_load_sbyte_d16"),
+ (0x23, 0x23, "scratch_load_sbyte_d16_hi"),
+ (0x24, 0x24, "scratch_load_short_d16"),
+ (0x25, 0x25, "scratch_load_short_d16_hi"),
+}
+for (gfx8, gfx10, name) in SCRATCH:
+ opcode(name, gfx8, gfx10, Format.SCRATCH)
diff --git a/src/amd/compiler/aco_opcodes_cpp.py b/src/amd/compiler/aco_opcodes_cpp.py
new file mode 100644
index 00000000000..83c24e0eb44
--- /dev/null
+++ b/src/amd/compiler/aco_opcodes_cpp.py
@@ -0,0 +1,74 @@
+
+template = """\
+/*
+ * Copyright (c) 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+
+namespace aco {
+
+const unsigned VOPC_to_GFX6[256] = {
+% for code in VOPC_GFX6:
+ ${code},
+% endfor
+};
+
+<%
+opcode_names = sorted(opcodes.keys())
+can_use_input_modifiers = "".join([opcodes[name].input_mod for name in reversed(opcode_names)])
+can_use_output_modifiers = "".join([opcodes[name].output_mod for name in reversed(opcode_names)])
+%>
+
+extern const aco::Info instr_info = {
+ .opcode_gfx9 = {
+ % for name in opcode_names:
+ ${opcodes[name].opcode_gfx9},
+ % endfor
+ },
+ .opcode_gfx10 = {
+ % for name in opcode_names:
+ ${opcodes[name].opcode_gfx10},
+ % endfor
+ },
+ .can_use_input_modifiers = std::bitset<${len(opcode_names)}>("${can_use_input_modifiers}"),
+ .can_use_output_modifiers = std::bitset<${len(opcode_names)}>("${can_use_output_modifiers}"),
+ .name = {
+ % for name in opcode_names:
+ "${name}",
+ % endfor
+ },
+ .format = {
+ % for name in opcode_names:
+ aco::Format::${str(opcodes[name].format.name)},
+ % endfor
+ },
+};
+
+}
+"""
+
+from aco_opcodes import opcodes, VOPC_GFX6
+from mako.template import Template
+
+print(Template(template).render(opcodes=opcodes, VOPC_GFX6=VOPC_GFX6))
diff --git a/src/amd/compiler/aco_opcodes_h.py b/src/amd/compiler/aco_opcodes_h.py
new file mode 100644
index 00000000000..6b8bfc1ee07
--- /dev/null
+++ b/src/amd/compiler/aco_opcodes_h.py
@@ -0,0 +1,47 @@
+
+template = """\
+/*
+ * Copyright (c) 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schuermann ([email protected])
+ */
+
+#ifndef _ACO_OPCODES_
+#define _ACO_OPCODES_
+
+<% opcode_names = sorted(opcodes.keys()) %>
+
+enum class aco_opcode : std::uint16_t {
+% for name in opcode_names:
+ ${name},
+% endfor
+ last_opcode = ${opcode_names[-1]},
+ num_opcodes = last_opcode + 1
+};
+
+#endif /* _ACO_OPCODES_ */"""
+
+from aco_opcodes import opcodes
+from mako.template import Template
+
+print(Template(template).render(opcodes=opcodes))
diff --git a/src/amd/compiler/aco_opt_value_numbering.cpp b/src/amd/compiler/aco_opt_value_numbering.cpp
new file mode 100644
index 00000000000..8071ace1f97
--- /dev/null
+++ b/src/amd/compiler/aco_opt_value_numbering.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include <map>
+#include <unordered_set>
+
+#include "aco_ir.h"
+
+/*
+ * Implements the algorithm for dominator-tree value numbering
+ * from "Value Numbering" by Briggs, Cooper, and Simpson.
+ */
+
+namespace aco {
+namespace {
+
+struct InstrHash {
+ std::size_t operator()(Instruction* instr) const
+ {
+ uint64_t hash = (uint64_t) instr->opcode + (uint64_t) instr->format;
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ Operand op = instr->operands[i];
+ uint64_t val = op.isTemp() ? op.tempId() : op.isFixed() ? op.physReg() : op.constantValue();
+ hash |= val << (i+1) * 8;
+ }
+ if (instr->isVOP3()) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr);
+ for (unsigned i = 0; i < 3; i++) {
+ hash ^= vop3->abs[i] << (i*3 + 0);
+ hash ^= vop3->opsel[i] << (i*3 + 1);
+ hash ^= vop3->neg[i] << (i*3 + 2);
+ }
+ hash ^= (vop3->clamp << 28) * 13;
+ hash += vop3->omod << 19;
+ }
+ switch (instr->format) {
+ case Format::SMEM:
+ break;
+ case Format::VINTRP: {
+ Interp_instruction* interp = static_cast<Interp_instruction*>(instr);
+ hash ^= interp->attribute << 13;
+ hash ^= interp->component << 27;
+ break;
+ }
+ case Format::DS:
+ break;
+ default:
+ break;
+ }
+
+ return hash;
+ }
+};
+
+struct InstrPred {
+ bool operator()(Instruction* a, Instruction* b) const
+ {
+ if (a->format != b->format)
+ return false;
+ if (a->opcode != b->opcode)
+ return false;
+ if (a->operands.size() != b->operands.size() || a->definitions.size() != b->definitions.size())
+ return false; /* possible with pseudo-instructions */
+ for (unsigned i = 0; i < a->operands.size(); i++) {
+ if (a->operands[i].isConstant()) {
+ if (!b->operands[i].isConstant())
+ return false;
+ if (a->operands[i].constantValue() != b->operands[i].constantValue())
+ return false;
+ }
+ else if (a->operands[i].isTemp()) {
+ if (!b->operands[i].isTemp())
+ return false;
+ if (a->operands[i].tempId() != b->operands[i].tempId())
+ return false;
+ }
+ else if (a->operands[i].isUndefined() ^ b->operands[i].isUndefined())
+ return false;
+ if (a->operands[i].isFixed()) {
+ if (a->operands[i].physReg() == exec)
+ return false;
+ if (!b->operands[i].isFixed())
+ return false;
+ if (!(a->operands[i].physReg() == b->operands[i].physReg()))
+ return false;
+ }
+ }
+ for (unsigned i = 0; i < a->definitions.size(); i++) {
+ if (a->definitions[i].isTemp()) {
+ if (!b->definitions[i].isTemp())
+ return false;
+ if (a->definitions[i].regClass() != b->definitions[i].regClass())
+ return false;
+ }
+ if (a->definitions[i].isFixed()) {
+ if (!b->definitions[i].isFixed())
+ return false;
+ if (!(a->definitions[i].physReg() == b->definitions[i].physReg()))
+ return false;
+ }
+ }
+ if (a->format == Format::PSEUDO_BRANCH)
+ return false;
+ if (a->isVOP3()) {
+ VOP3A_instruction* a3 = static_cast<VOP3A_instruction*>(a);
+ VOP3A_instruction* b3 = static_cast<VOP3A_instruction*>(b);
+ for (unsigned i = 0; i < 3; i++) {
+ if (a3->abs[i] != b3->abs[i] ||
+ a3->opsel[i] != b3->opsel[i] ||
+ a3->neg[i] != b3->neg[i])
+ return false;
+ }
+ return a3->clamp == b3->clamp &&
+ a3->omod == b3->omod;
+ }
+ if (a->isDPP()) {
+ DPP_instruction* aDPP = static_cast<DPP_instruction*>(a);
+ DPP_instruction* bDPP = static_cast<DPP_instruction*>(b);
+ return aDPP->dpp_ctrl == bDPP->dpp_ctrl &&
+ aDPP->bank_mask == bDPP->bank_mask &&
+ aDPP->row_mask == bDPP->row_mask &&
+ aDPP->bound_ctrl == bDPP->bound_ctrl &&
+ aDPP->abs[0] == bDPP->abs[0] &&
+ aDPP->abs[1] == bDPP->abs[1] &&
+ aDPP->neg[0] == bDPP->neg[0] &&
+ aDPP->neg[1] == bDPP->neg[1];
+ }
+ switch (a->format) {
+ case Format::VOPC: {
+ /* Since the results depend on the exec mask, these shouldn't
+ * be value numbered (this is especially useful for subgroupBallot()). */
+ return false;
+ }
+ case Format::SOPK: {
+ SOPK_instruction* aK = static_cast<SOPK_instruction*>(a);
+ SOPK_instruction* bK = static_cast<SOPK_instruction*>(b);
+ return aK->imm == bK->imm;
+ }
+ case Format::SMEM: {
+ SMEM_instruction* aS = static_cast<SMEM_instruction*>(a);
+ SMEM_instruction* bS = static_cast<SMEM_instruction*>(b);
+ return aS->can_reorder && bS->can_reorder &&
+ aS->glc == bS->glc && aS->nv == bS->nv;
+ }
+ case Format::VINTRP: {
+ Interp_instruction* aI = static_cast<Interp_instruction*>(a);
+ Interp_instruction* bI = static_cast<Interp_instruction*>(b);
+ if (aI->attribute != bI->attribute)
+ return false;
+ if (aI->component != bI->component)
+ return false;
+ return true;
+ }
+ case Format::PSEUDO_REDUCTION:
+ return false;
+ case Format::MTBUF: {
+ /* this is fine since they are only used for vertex input fetches */
+ MTBUF_instruction* aM = static_cast<MTBUF_instruction *>(a);
+ MTBUF_instruction* bM = static_cast<MTBUF_instruction *>(b);
+ return aM->dfmt == bM->dfmt &&
+ aM->nfmt == bM->nfmt &&
+ aM->offset == bM->offset &&
+ aM->offen == bM->offen &&
+ aM->idxen == bM->idxen &&
+ aM->glc == bM->glc &&
+ aM->slc == bM->slc &&
+ aM->tfe == bM->tfe &&
+ aM->disable_wqm == bM->disable_wqm;
+ }
+ /* we want to optimize these in NIR and don't hassle with load-store dependencies */
+ case Format::MUBUF:
+ case Format::FLAT:
+ case Format::GLOBAL:
+ case Format::SCRATCH:
+ case Format::DS:
+ return false;
+ case Format::MIMG: {
+ MIMG_instruction* aM = static_cast<MIMG_instruction*>(a);
+ MIMG_instruction* bM = static_cast<MIMG_instruction*>(b);
+ return aM->can_reorder && bM->can_reorder &&
+ aM->dmask == bM->dmask &&
+ aM->unrm == bM->unrm &&
+ aM->glc == bM->glc &&
+ aM->slc == bM->slc &&
+ aM->tfe == bM->tfe &&
+ aM->da == bM->da &&
+ aM->lwe == bM->lwe &&
+ aM->r128 == bM->r128 &&
+ aM->a16 == bM->a16 &&
+ aM->d16 == bM->d16 &&
+ aM->disable_wqm == bM->disable_wqm;
+ }
+ default:
+ return true;
+ }
+ }
+};
+
+
+typedef std::unordered_set<Instruction*, InstrHash, InstrPred> expr_set;
+
+void process_block(Block& block,
+ expr_set& expr_values,
+ std::map<uint32_t, Temp>& renames)
+{
+ bool run = false;
+ std::vector<aco_ptr<Instruction>>::iterator it = block.instructions.begin();
+ std::vector<aco_ptr<Instruction>> new_instructions;
+ new_instructions.reserve(block.instructions.size());
+ expr_set phi_values;
+
+ while (it != block.instructions.end()) {
+ aco_ptr<Instruction>& instr = *it;
+ /* first, rename operands */
+ for (Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ auto it = renames.find(op.tempId());
+ if (it != renames.end())
+ op.setTemp(it->second);
+ }
+
+ if (instr->definitions.empty() || !run) {
+ if (instr->opcode == aco_opcode::p_logical_start)
+ run = true;
+ else if (instr->opcode == aco_opcode::p_logical_end)
+ run = false;
+ else if (instr->opcode == aco_opcode::p_phi || instr->opcode == aco_opcode::p_linear_phi) {
+ std::pair<expr_set::iterator, bool> res = phi_values.emplace(instr.get());
+ if (!res.second) {
+ Instruction* orig_phi = *(res.first);
+ renames.emplace(instr->definitions[0].tempId(), orig_phi->definitions[0].getTemp()).second;
+ ++it;
+ continue;
+ }
+ }
+ new_instructions.emplace_back(std::move(instr));
+ ++it;
+ continue;
+ }
+
+ /* simple copy-propagation through renaming */
+ if ((instr->opcode == aco_opcode::s_mov_b32 || instr->opcode == aco_opcode::s_mov_b64 || instr->opcode == aco_opcode::v_mov_b32) &&
+ !instr->definitions[0].isFixed() && instr->operands[0].isTemp() && instr->operands[0].regClass() == instr->definitions[0].regClass() &&
+ !instr->isDPP() && !((int)instr->format & (int)Format::SDWA)) {
+ renames[instr->definitions[0].tempId()] = instr->operands[0].getTemp();
+ }
+
+ std::pair<expr_set::iterator, bool> res = expr_values.emplace(instr.get());
+
+ /* if there was already an expression with the same value number */
+ if (!res.second) {
+ Instruction* orig_instr = *(res.first);
+ assert(instr->definitions.size() == orig_instr->definitions.size());
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ assert(instr->definitions[i].regClass() == orig_instr->definitions[i].regClass());
+ renames.emplace(instr->definitions[i].tempId(), orig_instr->definitions[i].getTemp()).second;
+ }
+ } else {
+ new_instructions.emplace_back(std::move(instr));
+ }
+ ++it;
+ }
+
+ block.instructions.swap(new_instructions);
+}
+
+void rename_phi_operands(Block& block, std::map<uint32_t, Temp>& renames)
+{
+ for (aco_ptr<Instruction>& phi : block.instructions) {
+ if (phi->opcode != aco_opcode::p_phi && phi->opcode != aco_opcode::p_linear_phi)
+ break;
+
+ for (Operand& op : phi->operands) {
+ if (!op.isTemp())
+ continue;
+ auto it = renames.find(op.tempId());
+ if (it != renames.end())
+ op.setTemp(it->second);
+ }
+ }
+}
+} /* end namespace */
+
+
+void value_numbering(Program* program)
+{
+ std::vector<expr_set> expr_values(program->blocks.size());
+ std::map<uint32_t, Temp> renames;
+
+ for (Block& block : program->blocks) {
+ if (block.logical_idom != -1) {
+ /* initialize expr_values from idom */
+ expr_values[block.index] = expr_values[block.logical_idom];
+ process_block(block, expr_values[block.index], renames);
+ } else {
+ expr_set empty;
+ process_block(block, empty, renames);
+ }
+ }
+
+ for (Block& block : program->blocks)
+ rename_phi_operands(block, renames);
+}
+
+}
diff --git a/src/amd/compiler/aco_optimizer.cpp b/src/amd/compiler/aco_optimizer.cpp
new file mode 100644
index 00000000000..fe05634c280
--- /dev/null
+++ b/src/amd/compiler/aco_optimizer.cpp
@@ -0,0 +1,2401 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schürmann ([email protected])
+ *
+ */
+
+#include <algorithm>
+#include <math.h>
+
+#include "aco_ir.h"
+#include "util/half_float.h"
+#include "util/u_math.h"
+
+namespace aco {
+
+/**
+ * The optimizer works in 4 phases:
+ * (1) The first pass collects information for each ssa-def,
+ * propagates reg->reg operands of the same type, inline constants
+ * and neg/abs input modifiers.
+ * (2) The second pass combines instructions like mad, omod, clamp and
+ * propagates sgpr's on VALU instructions.
+ * This pass depends on information collected in the first pass.
+ * (3) The third pass goes backwards, and selects instructions,
+ * i.e. decides if a mad instruction is profitable and eliminates dead code.
+ * (4) The fourth pass cleans up the sequence: literals get applied and dead
+ * instructions are removed from the sequence.
+ */
+
+
+struct mad_info {
+ aco_ptr<Instruction> add_instr;
+ uint32_t mul_temp_id;
+ uint32_t literal_idx;
+ bool needs_vop3;
+ bool check_literal;
+
+ mad_info(aco_ptr<Instruction> instr, uint32_t id, bool vop3)
+ : add_instr(std::move(instr)), mul_temp_id(id), needs_vop3(vop3), check_literal(false) {}
+};
+
+enum Label {
+ label_vec = 1 << 0,
+ label_constant = 1 << 1,
+ label_abs = 1 << 2,
+ label_neg = 1 << 3,
+ label_mul = 1 << 4,
+ label_temp = 1 << 5,
+ label_literal = 1 << 6,
+ label_mad = 1 << 7,
+ label_omod2 = 1 << 8,
+ label_omod4 = 1 << 9,
+ label_omod5 = 1 << 10,
+ label_omod_success = 1 << 11,
+ label_clamp = 1 << 12,
+ label_clamp_success = 1 << 13,
+ label_undefined = 1 << 14,
+ label_vcc = 1 << 15,
+ label_b2f = 1 << 16,
+ label_add_sub = 1 << 17,
+ label_bitwise = 1 << 18,
+ label_minmax = 1 << 19,
+ label_fcmp = 1 << 20,
+};
+
+static constexpr uint32_t instr_labels = label_vec | label_mul | label_mad | label_omod_success | label_clamp_success | label_add_sub | label_bitwise | label_minmax | label_fcmp;
+static constexpr uint32_t temp_labels = label_abs | label_neg | label_temp | label_vcc | label_b2f;
+static constexpr uint32_t val_labels = label_constant | label_literal | label_mad;
+
+struct ssa_info {
+ uint32_t val;
+ union {
+ Temp temp;
+ Instruction* instr;
+ };
+ uint32_t label;
+
+ void add_label(Label new_label)
+ {
+ /* Since all labels which use "instr" use it for the same thing
+ * (indicating the defining instruction), there is no need to clear
+ * any other instr labels. */
+ if (new_label & instr_labels)
+ label &= ~temp_labels; /* instr and temp alias */
+
+ if (new_label & temp_labels) {
+ label &= ~temp_labels;
+ label &= ~instr_labels; /* instr and temp alias */
+ }
+
+ if (new_label & val_labels)
+ label &= ~val_labels;
+
+ label |= new_label;
+ }
+
+ void set_vec(Instruction* vec)
+ {
+ add_label(label_vec);
+ instr = vec;
+ }
+
+ bool is_vec()
+ {
+ return label & label_vec;
+ }
+
+ void set_constant(uint32_t constant)
+ {
+ add_label(label_constant);
+ val = constant;
+ }
+
+ bool is_constant()
+ {
+ return label & label_constant;
+ }
+
+ void set_abs(Temp abs_temp)
+ {
+ add_label(label_abs);
+ temp = abs_temp;
+ }
+
+ bool is_abs()
+ {
+ return label & label_abs;
+ }
+
+ void set_neg(Temp neg_temp)
+ {
+ add_label(label_neg);
+ temp = neg_temp;
+ }
+
+ bool is_neg()
+ {
+ return label & label_neg;
+ }
+
+ void set_neg_abs(Temp neg_abs_temp)
+ {
+ add_label((Label)((uint32_t)label_abs | (uint32_t)label_neg));
+ temp = neg_abs_temp;
+ }
+
+ void set_mul(Instruction* mul)
+ {
+ add_label(label_mul);
+ instr = mul;
+ }
+
+ bool is_mul()
+ {
+ return label & label_mul;
+ }
+
+ void set_temp(Temp tmp)
+ {
+ add_label(label_temp);
+ temp = tmp;
+ }
+
+ bool is_temp()
+ {
+ return label & label_temp;
+ }
+
+ void set_literal(uint32_t lit)
+ {
+ add_label(label_literal);
+ val = lit;
+ }
+
+ bool is_literal()
+ {
+ return label & label_literal;
+ }
+
+ void set_mad(Instruction* mad, uint32_t mad_info_idx)
+ {
+ add_label(label_mad);
+ val = mad_info_idx;
+ instr = mad;
+ }
+
+ bool is_mad()
+ {
+ return label & label_mad;
+ }
+
+ void set_omod2()
+ {
+ add_label(label_omod2);
+ }
+
+ bool is_omod2()
+ {
+ return label & label_omod2;
+ }
+
+ void set_omod4()
+ {
+ add_label(label_omod4);
+ }
+
+ bool is_omod4()
+ {
+ return label & label_omod4;
+ }
+
+ void set_omod5()
+ {
+ add_label(label_omod5);
+ }
+
+ bool is_omod5()
+ {
+ return label & label_omod5;
+ }
+
+ void set_omod_success(Instruction* omod_instr)
+ {
+ add_label(label_omod_success);
+ instr = omod_instr;
+ }
+
+ bool is_omod_success()
+ {
+ return label & label_omod_success;
+ }
+
+ void set_clamp()
+ {
+ add_label(label_clamp);
+ }
+
+ bool is_clamp()
+ {
+ return label & label_clamp;
+ }
+
+ void set_clamp_success(Instruction* clamp_instr)
+ {
+ add_label(label_clamp_success);
+ instr = clamp_instr;
+ }
+
+ bool is_clamp_success()
+ {
+ return label & label_clamp_success;
+ }
+
+ void set_undefined()
+ {
+ add_label(label_undefined);
+ }
+
+ bool is_undefined()
+ {
+ return label & label_undefined;
+ }
+
+ void set_vcc(Temp vcc)
+ {
+ add_label(label_vcc);
+ temp = vcc;
+ }
+
+ bool is_vcc()
+ {
+ return label & label_vcc;
+ }
+
+ bool is_constant_or_literal()
+ {
+ return is_constant() || is_literal();
+ }
+
+ void set_b2f(Temp val)
+ {
+ add_label(label_b2f);
+ temp = val;
+ }
+
+ bool is_b2f()
+ {
+ return label & label_b2f;
+ }
+
+ void set_add_sub(Instruction *add_sub_instr)
+ {
+ add_label(label_add_sub);
+ instr = add_sub_instr;
+ }
+
+ bool is_add_sub()
+ {
+ return label & label_add_sub;
+ }
+
+ void set_bitwise(Instruction *bitwise_instr)
+ {
+ add_label(label_bitwise);
+ instr = bitwise_instr;
+ }
+
+ bool is_bitwise()
+ {
+ return label & label_bitwise;
+ }
+
+ void set_minmax(Instruction *minmax_instr)
+ {
+ add_label(label_minmax);
+ instr = minmax_instr;
+ }
+
+ bool is_minmax()
+ {
+ return label & label_minmax;
+ }
+
+ void set_fcmp(Instruction *fcmp_instr)
+ {
+ add_label(label_fcmp);
+ instr = fcmp_instr;
+ }
+
+ bool is_fcmp()
+ {
+ return label & label_fcmp;
+ }
+
+};
+
+struct opt_ctx {
+ Program* program;
+ std::vector<aco_ptr<Instruction>> instructions;
+ ssa_info* info;
+ std::pair<uint32_t,Temp> last_literal;
+ std::vector<mad_info> mad_infos;
+ std::vector<uint16_t> uses;
+};
+
+bool can_swap_operands(aco_ptr<Instruction>& instr)
+{
+ if (instr->operands[0].isConstant() ||
+ (instr->operands[0].isTemp() && instr->operands[0].getTemp().type() == RegType::sgpr))
+ return false;
+
+ switch (instr->opcode) {
+ case aco_opcode::v_add_f32:
+ case aco_opcode::v_mul_f32:
+ case aco_opcode::v_or_b32:
+ case aco_opcode::v_and_b32:
+ case aco_opcode::v_xor_b32:
+ case aco_opcode::v_max_f32:
+ case aco_opcode::v_min_f32:
+ case aco_opcode::v_cmp_eq_f32:
+ case aco_opcode::v_cmp_lg_f32:
+ return true;
+ case aco_opcode::v_sub_f32:
+ instr->opcode = aco_opcode::v_subrev_f32;
+ return true;
+ case aco_opcode::v_cmp_lt_f32:
+ instr->opcode = aco_opcode::v_cmp_gt_f32;
+ return true;
+ case aco_opcode::v_cmp_ge_f32:
+ instr->opcode = aco_opcode::v_cmp_le_f32;
+ return true;
+ case aco_opcode::v_cmp_lt_i32:
+ instr->opcode = aco_opcode::v_cmp_gt_i32;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool can_use_VOP3(aco_ptr<Instruction>& instr)
+{
+ if (instr->operands.size() && instr->operands[0].isLiteral())
+ return false;
+
+ if (instr->isDPP() || instr->isSDWA())
+ return false;
+
+ return instr->opcode != aco_opcode::v_madmk_f32 &&
+ instr->opcode != aco_opcode::v_madak_f32 &&
+ instr->opcode != aco_opcode::v_madmk_f16 &&
+ instr->opcode != aco_opcode::v_madak_f16;
+}
+
+void to_VOP3(opt_ctx& ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->isVOP3())
+ return;
+
+ assert(!instr->operands[0].isLiteral());
+ aco_ptr<Instruction> tmp = std::move(instr);
+ Format format = asVOP3(tmp->format);
+ instr.reset(create_instruction<VOP3A_instruction>(tmp->opcode, format, tmp->operands.size(), tmp->definitions.size()));
+ std::copy(tmp->operands.cbegin(), tmp->operands.cend(), instr->operands.begin());
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ instr->definitions[i] = tmp->definitions[i];
+ if (instr->definitions[i].isTemp()) {
+ ssa_info& info = ctx.info[instr->definitions[i].tempId()];
+ if (info.label & instr_labels && info.instr == tmp.get())
+ info.instr = instr.get();
+ }
+ }
+}
+
+bool valu_can_accept_literal(opt_ctx& ctx, aco_ptr<Instruction>& instr)
+{
+ // TODO: VOP3 can take a literal on GFX10
+ return !instr->isSDWA() && !instr->isDPP() && !instr->isVOP3();
+}
+
+/* only covers special cases */
+bool can_accept_constant(aco_ptr<Instruction>& instr, unsigned operand)
+{
+ switch (instr->opcode) {
+ case aco_opcode::v_interp_p2_f32:
+ case aco_opcode::v_mac_f32:
+ case aco_opcode::v_writelane_b32:
+ case aco_opcode::v_cndmask_b32:
+ return operand != 2;
+ case aco_opcode::s_addk_i32:
+ case aco_opcode::s_mulk_i32:
+ case aco_opcode::p_wqm:
+ case aco_opcode::p_extract_vector:
+ case aco_opcode::p_split_vector:
+ return operand != 0;
+ default:
+ if ((instr->format == Format::MUBUF ||
+ instr->format == Format::MIMG) &&
+ instr->definitions.size() == 1 &&
+ instr->operands.size() == 4) {
+ return operand != 3;
+ }
+ return true;
+ }
+}
+
+bool parse_base_offset(opt_ctx &ctx, Instruction* instr, unsigned op_index, Temp *base, uint32_t *offset)
+{
+ Operand op = instr->operands[op_index];
+
+ if (!op.isTemp())
+ return false;
+ Temp tmp = op.getTemp();
+ if (!ctx.info[tmp.id()].is_add_sub())
+ return false;
+
+ Instruction *add_instr = ctx.info[tmp.id()].instr;
+
+ switch (add_instr->opcode) {
+ case aco_opcode::v_add_u32:
+ case aco_opcode::v_add_co_u32:
+ case aco_opcode::s_add_i32:
+ case aco_opcode::s_add_u32:
+ break;
+ default:
+ return false;
+ }
+
+ for (unsigned i = 0; i < 2; i++) {
+ if (add_instr->operands[i].isConstant()) {
+ *offset = add_instr->operands[i].constantValue();
+ } else if (add_instr->operands[i].isTemp() &&
+ ctx.info[add_instr->operands[i].tempId()].is_constant_or_literal()) {
+ *offset = ctx.info[add_instr->operands[i].tempId()].val;
+ } else {
+ continue;
+ }
+ if (!add_instr->operands[!i].isTemp())
+ continue;
+
+ uint32_t offset2 = 0;
+ if (parse_base_offset(ctx, add_instr, !i, base, &offset2)) {
+ *offset += offset2;
+ } else {
+ *base = add_instr->operands[!i].getTemp();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void label_instruction(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->isSALU() || instr->isVALU() || instr->format == Format::PSEUDO) {
+ ASSERTED bool all_const = false;
+ for (Operand& op : instr->operands)
+ all_const = all_const && (!op.isTemp() || ctx.info[op.tempId()].is_constant_or_literal());
+ perfwarn(all_const, "All instruction operands are constant", instr.get());
+ }
+
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (!instr->operands[i].isTemp())
+ continue;
+
+ ssa_info info = ctx.info[instr->operands[i].tempId()];
+ /* propagate undef */
+ if (info.is_undefined() && is_phi(instr))
+ instr->operands[i] = Operand(instr->operands[i].regClass());
+ /* propagate reg->reg of same type */
+ if (info.is_temp() && info.temp.regClass() == instr->operands[i].getTemp().regClass()) {
+ instr->operands[i].setTemp(ctx.info[instr->operands[i].tempId()].temp);
+ info = ctx.info[info.temp.id()];
+ }
+
+ /* SALU / PSEUDO: propagate inline constants */
+ if (instr->isSALU() || instr->format == Format::PSEUDO) {
+ if (info.is_temp() && info.temp.type() == RegType::sgpr) {
+ instr->operands[i].setTemp(info.temp);
+ info = ctx.info[info.temp.id()];
+ } else if (info.is_temp() && info.temp.type() == RegType::vgpr) {
+ /* propagate vgpr if it can take it */
+ switch (instr->opcode) {
+ case aco_opcode::p_create_vector:
+ case aco_opcode::p_split_vector:
+ case aco_opcode::p_extract_vector:
+ case aco_opcode::p_phi: {
+ const bool all_vgpr = std::none_of(instr->definitions.begin(), instr->definitions.end(),
+ [] (const Definition& def) { return def.getTemp().type() != RegType::vgpr;});
+ if (all_vgpr) {
+ instr->operands[i] = Operand(info.temp);
+ info = ctx.info[info.temp.id()];
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if ((info.is_constant() || (info.is_literal() && instr->format == Format::PSEUDO)) && !instr->operands[i].isFixed() && can_accept_constant(instr, i)) {
+ instr->operands[i] = Operand(info.val);
+ continue;
+ }
+ }
+
+ /* VALU: propagate neg, abs & inline constants */
+ else if (instr->isVALU()) {
+ if (info.is_temp() && info.temp.type() == RegType::vgpr) {
+ instr->operands[i].setTemp(info.temp);
+ info = ctx.info[info.temp.id()];
+ }
+ if (info.is_abs() && (can_use_VOP3(instr) || instr->isDPP()) && instr_info.can_use_input_modifiers[(int)instr->opcode]) {
+ if (!instr->isDPP())
+ to_VOP3(ctx, instr);
+ instr->operands[i] = Operand(info.temp);
+ if (instr->isDPP())
+ static_cast<DPP_instruction*>(instr.get())->abs[i] = true;
+ else
+ static_cast<VOP3A_instruction*>(instr.get())->abs[i] = true;
+ }
+ if (info.is_neg() && instr->opcode == aco_opcode::v_add_f32) {
+ instr->opcode = i ? aco_opcode::v_sub_f32 : aco_opcode::v_subrev_f32;
+ instr->operands[i].setTemp(info.temp);
+ continue;
+ } else if (info.is_neg() && (can_use_VOP3(instr) || instr->isDPP()) && instr_info.can_use_input_modifiers[(int)instr->opcode]) {
+ if (!instr->isDPP())
+ to_VOP3(ctx, instr);
+ instr->operands[i].setTemp(info.temp);
+ if (instr->isDPP())
+ static_cast<DPP_instruction*>(instr.get())->neg[i] = true;
+ else
+ static_cast<VOP3A_instruction*>(instr.get())->neg[i] = true;
+ continue;
+ }
+ if (info.is_constant() && can_accept_constant(instr, i)) {
+ perfwarn(instr->opcode == aco_opcode::v_cndmask_b32 && i == 2, "v_cndmask_b32 with a constant selector", instr.get());
+ if (i == 0) {
+ instr->operands[i] = Operand(info.val);
+ continue;
+ } else if (!instr->isVOP3() && can_swap_operands(instr)) {
+ instr->operands[i] = instr->operands[0];
+ instr->operands[0] = Operand(info.val);
+ continue;
+ } else if (can_use_VOP3(instr)) {
+ to_VOP3(ctx, instr);
+ instr->operands[i] = Operand(info.val);
+ continue;
+ }
+ }
+ }
+
+ /* MUBUF: propagate constants and combine additions */
+ else if (instr->format == Format::MUBUF) {
+ MUBUF_instruction *mubuf = static_cast<MUBUF_instruction *>(instr.get());
+ Temp base;
+ uint32_t offset;
+ while (info.is_temp())
+ info = ctx.info[info.temp.id()];
+
+ if (mubuf->offen && i == 0 && info.is_constant_or_literal() && mubuf->offset + info.val < 4096) {
+ assert(!mubuf->idxen);
+ instr->operands[i] = Operand(v1);
+ mubuf->offset += info.val;
+ mubuf->offen = false;
+ continue;
+ } else if (i == 2 && info.is_constant_or_literal() && mubuf->offset + info.val < 4096) {
+ instr->operands[2] = Operand((uint32_t) 0);
+ mubuf->offset += info.val;
+ continue;
+ } else if (mubuf->offen && i == 0 && parse_base_offset(ctx, instr.get(), i, &base, &offset) && base.regClass() == v1 && mubuf->offset + offset < 4096) {
+ assert(!mubuf->idxen);
+ instr->operands[i].setTemp(base);
+ mubuf->offset += offset;
+ continue;
+ } else if (i == 2 && parse_base_offset(ctx, instr.get(), i, &base, &offset) && base.regClass() == s1 && mubuf->offset + offset < 4096) {
+ instr->operands[i].setTemp(base);
+ mubuf->offset += offset;
+ continue;
+ }
+ }
+
+ /* DS: combine additions */
+ else if (instr->format == Format::DS) {
+
+ DS_instruction *ds = static_cast<DS_instruction *>(instr.get());
+ Temp base;
+ uint32_t offset;
+ if (i == 0 && parse_base_offset(ctx, instr.get(), i, &base, &offset) && base.regClass() == instr->operands[i].regClass()) {
+ if (instr->opcode == aco_opcode::ds_write2_b32 || instr->opcode == aco_opcode::ds_read2_b32) {
+ if (offset % 4 == 0 &&
+ ds->offset0 + (offset >> 2) <= 255 &&
+ ds->offset1 + (offset >> 2) <= 255) {
+ instr->operands[i].setTemp(base);
+ ds->offset0 += offset >> 2;
+ ds->offset1 += offset >> 2;
+ }
+ } else {
+ if (ds->offset0 + offset <= 65535) {
+ instr->operands[i].setTemp(base);
+ ds->offset0 += offset;
+ }
+ }
+ }
+ }
+
+ /* SMEM: propagate constants and combine additions */
+ else if (instr->format == Format::SMEM) {
+
+ SMEM_instruction *smem = static_cast<SMEM_instruction *>(instr.get());
+ Temp base;
+ uint32_t offset;
+ if (i == 1 && info.is_constant_or_literal() && info.val <= 0xFFFFF) {
+ instr->operands[i] = Operand(info.val);
+ continue;
+ } else if (i == 1 && parse_base_offset(ctx, instr.get(), i, &base, &offset) && base.regClass() == s1 && offset <= 0xFFFFF && ctx.program->chip_class >= GFX9) {
+ bool soe = smem->operands.size() >= (!smem->definitions.empty() ? 3 : 4);
+ if (soe &&
+ (!ctx.info[smem->operands.back().tempId()].is_constant_or_literal() ||
+ ctx.info[smem->operands.back().tempId()].val != 0)) {
+ continue;
+ }
+ if (soe) {
+ smem->operands[1] = Operand(offset);
+ smem->operands.back() = Operand(base);
+ } else {
+ SMEM_instruction *new_instr = create_instruction<SMEM_instruction>(smem->opcode, Format::SMEM, smem->operands.size() + 1, smem->definitions.size());
+ new_instr->operands[0] = smem->operands[0];
+ new_instr->operands[1] = Operand(offset);
+ if (smem->definitions.empty())
+ new_instr->operands[2] = smem->operands[2];
+ new_instr->operands.back() = Operand(base);
+ if (!smem->definitions.empty())
+ new_instr->definitions[0] = smem->definitions[0];
+ instr.reset(new_instr);
+ smem = static_cast<SMEM_instruction *>(instr.get());
+ }
+ continue;
+ }
+ }
+ }
+
+ /* if this instruction doesn't define anything, return */
+ if (instr->definitions.empty())
+ return;
+
+ switch (instr->opcode) {
+ case aco_opcode::p_create_vector: {
+ unsigned num_ops = instr->operands.size();
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && ctx.info[op.tempId()].is_vec())
+ num_ops += ctx.info[op.tempId()].instr->operands.size() - 1;
+ }
+ if (num_ops != instr->operands.size()) {
+ aco_ptr<Instruction> old_vec = std::move(instr);
+ instr.reset(create_instruction<Pseudo_instruction>(aco_opcode::p_create_vector, Format::PSEUDO, num_ops, 1));
+ instr->definitions[0] = old_vec->definitions[0];
+ unsigned k = 0;
+ for (Operand& old_op : old_vec->operands) {
+ if (old_op.isTemp() && ctx.info[old_op.tempId()].is_vec()) {
+ for (unsigned j = 0; j < ctx.info[old_op.tempId()].instr->operands.size(); j++)
+ instr->operands[k++] = ctx.info[old_op.tempId()].instr->operands[j];
+ } else {
+ instr->operands[k++] = old_op;
+ }
+ }
+ assert(k == num_ops);
+ }
+ if (instr->operands.size() == 1 && instr->operands[0].isTemp())
+ ctx.info[instr->definitions[0].tempId()].set_temp(instr->operands[0].getTemp());
+ else if (instr->definitions[0].getTemp().size() == instr->operands.size())
+ ctx.info[instr->definitions[0].tempId()].set_vec(instr.get());
+ break;
+ }
+ case aco_opcode::p_split_vector: {
+ if (!ctx.info[instr->operands[0].tempId()].is_vec())
+ break;
+ Instruction* vec = ctx.info[instr->operands[0].tempId()].instr;
+ assert(instr->definitions.size() == vec->operands.size());
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Operand vec_op = vec->operands[i];
+ if (vec_op.isConstant()) {
+ if (vec_op.isLiteral())
+ ctx.info[instr->definitions[i].tempId()].set_literal(vec_op.constantValue());
+ else
+ ctx.info[instr->definitions[i].tempId()].set_constant(vec_op.constantValue());
+ } else {
+ assert(vec_op.isTemp());
+ ctx.info[instr->definitions[i].tempId()].set_temp(vec_op.getTemp());
+ }
+ }
+ break;
+ }
+ case aco_opcode::p_extract_vector: { /* mov */
+ if (!ctx.info[instr->operands[0].tempId()].is_vec())
+ break;
+ Instruction* vec = ctx.info[instr->operands[0].tempId()].instr;
+ if (vec->definitions[0].getTemp().size() == vec->operands.size()) { /* TODO: what about 64bit or other combinations? */
+
+ /* convert this extract into a mov instruction */
+ Operand vec_op = vec->operands[instr->operands[1].constantValue()];
+ bool is_vgpr = instr->definitions[0].getTemp().type() == RegType::vgpr;
+ aco_opcode opcode = is_vgpr ? aco_opcode::v_mov_b32 : aco_opcode::s_mov_b32;
+ Format format = is_vgpr ? Format::VOP1 : Format::SOP1;
+ instr->opcode = opcode;
+ instr->format = format;
+ instr->operands = {instr->operands.begin(), 1 };
+ instr->operands[0] = vec_op;
+
+ if (vec_op.isConstant()) {
+ if (vec_op.isLiteral())
+ ctx.info[instr->definitions[0].tempId()].set_literal(vec_op.constantValue());
+ else
+ ctx.info[instr->definitions[0].tempId()].set_constant(vec_op.constantValue());
+ } else {
+ assert(vec_op.isTemp());
+ ctx.info[instr->definitions[0].tempId()].set_temp(vec_op.getTemp());
+ }
+ }
+ break;
+ }
+ case aco_opcode::s_mov_b32: /* propagate */
+ case aco_opcode::s_mov_b64:
+ case aco_opcode::v_mov_b32:
+ case aco_opcode::p_as_uniform:
+ if (instr->definitions[0].isFixed()) {
+ /* don't copy-propagate copies into fixed registers */
+ } else if (instr->operands[0].isConstant()) {
+ if (instr->operands[0].isLiteral())
+ ctx.info[instr->definitions[0].tempId()].set_literal(instr->operands[0].constantValue());
+ else
+ ctx.info[instr->definitions[0].tempId()].set_constant(instr->operands[0].constantValue());
+ } else if (instr->isDPP()) {
+ // TODO
+ } else if (instr->operands[0].isTemp()) {
+ ctx.info[instr->definitions[0].tempId()].set_temp(instr->operands[0].getTemp());
+ } else {
+ assert(instr->operands[0].isFixed());
+ }
+ break;
+ case aco_opcode::p_is_helper:
+ if (!ctx.program->needs_wqm)
+ ctx.info[instr->definitions[0].tempId()].set_constant(0u);
+ break;
+ case aco_opcode::s_movk_i32: {
+ uint32_t v = static_cast<SOPK_instruction*>(instr.get())->imm;
+ v = v & 0x8000 ? (v | 0xffff0000) : v;
+ if (v <= 64 || v >= 0xfffffff0)
+ ctx.info[instr->definitions[0].tempId()].set_constant(v);
+ else
+ ctx.info[instr->definitions[0].tempId()].set_literal(v);
+ break;
+ }
+ case aco_opcode::v_bfrev_b32:
+ case aco_opcode::s_brev_b32: {
+ if (instr->operands[0].isConstant()) {
+ uint32_t v = util_bitreverse(instr->operands[0].constantValue());
+ if (v <= 64 || v >= 0xfffffff0)
+ ctx.info[instr->definitions[0].tempId()].set_constant(v);
+ else
+ ctx.info[instr->definitions[0].tempId()].set_literal(v);
+ }
+ break;
+ }
+ case aco_opcode::s_bfm_b32: {
+ if (instr->operands[0].isConstant() && instr->operands[1].isConstant()) {
+ unsigned size = instr->operands[0].constantValue() & 0x1f;
+ unsigned start = instr->operands[1].constantValue() & 0x1f;
+ uint32_t v = ((1u << size) - 1u) << start;
+ if (v <= 64 || v >= 0xfffffff0)
+ ctx.info[instr->definitions[0].tempId()].set_constant(v);
+ else
+ ctx.info[instr->definitions[0].tempId()].set_literal(v);
+ }
+ }
+ case aco_opcode::v_mul_f32: { /* omod */
+ /* TODO: try to move the negate/abs modifier to the consumer instead */
+ if (instr->isVOP3()) {
+ VOP3A_instruction *vop3 = static_cast<VOP3A_instruction*>(instr.get());
+ if (vop3->abs[0] || vop3->abs[1] || vop3->neg[0] || vop3->neg[1] || vop3->omod || vop3->clamp)
+ break;
+ }
+
+ for (unsigned i = 0; i < 2; i++) {
+ if (instr->operands[!i].isConstant() && instr->operands[i].isTemp()) {
+ if (instr->operands[!i].constantValue() == 0x40000000) { /* 2.0 */
+ ctx.info[instr->operands[i].tempId()].set_omod2();
+ } else if (instr->operands[!i].constantValue() == 0x40800000) { /* 4.0 */
+ ctx.info[instr->operands[i].tempId()].set_omod4();
+ } else if (instr->operands[!i].constantValue() == 0x3f000000) { /* 0.5 */
+ ctx.info[instr->operands[i].tempId()].set_omod5();
+ } else if (instr->operands[!i].constantValue() == 0x3f800000) { /* 1.0 */
+ ctx.info[instr->definitions[0].tempId()].set_temp(instr->operands[i].getTemp());
+ } else {
+ continue;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case aco_opcode::v_and_b32: /* abs */
+ if (instr->operands[0].constantEquals(0x7FFFFFFF) && instr->operands[1].isTemp())
+ ctx.info[instr->definitions[0].tempId()].set_abs(instr->operands[1].getTemp());
+ else
+ ctx.info[instr->definitions[0].tempId()].set_bitwise(instr.get());
+ break;
+ case aco_opcode::v_xor_b32: { /* neg */
+ if (instr->operands[0].constantEquals(0x80000000u) && instr->operands[1].isTemp()) {
+ if (ctx.info[instr->operands[1].tempId()].is_neg()) {
+ ctx.info[instr->definitions[0].tempId()].set_temp(ctx.info[instr->operands[1].tempId()].temp);
+ } else {
+ if (ctx.info[instr->operands[1].tempId()].is_abs()) { /* neg(abs(x)) */
+ instr->operands[1].setTemp(ctx.info[instr->operands[1].tempId()].temp);
+ instr->opcode = aco_opcode::v_or_b32;
+ ctx.info[instr->definitions[0].tempId()].set_neg_abs(instr->operands[1].getTemp());
+ } else {
+ ctx.info[instr->definitions[0].tempId()].set_neg(instr->operands[1].getTemp());
+ }
+ }
+ } else {
+ ctx.info[instr->definitions[0].tempId()].set_bitwise(instr.get());
+ }
+ break;
+ }
+ case aco_opcode::v_med3_f32: { /* clamp */
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr.get());
+ if (vop3->abs[0] || vop3->neg[0] || vop3->opsel[0] ||
+ vop3->abs[1] || vop3->neg[1] || vop3->opsel[1] ||
+ vop3->abs[2] || vop3->neg[2] || vop3->opsel[2] ||
+ vop3->omod != 0)
+ break;
+
+ unsigned idx = 0;
+ bool found_zero = false, found_one = false;
+ for (unsigned i = 0; i < 3; i++)
+ {
+ if (instr->operands[i].constantEquals(0))
+ found_zero = true;
+ else if (instr->operands[i].constantEquals(0x3f800000)) /* 1.0 */
+ found_one = true;
+ else
+ idx = i;
+ }
+ if (found_zero && found_one && instr->operands[idx].isTemp()) {
+ ctx.info[instr->operands[idx].tempId()].set_clamp();
+ }
+ break;
+ }
+ case aco_opcode::v_cndmask_b32:
+ if (instr->operands[0].constantEquals(0) &&
+ instr->operands[1].constantEquals(0xFFFFFFFF) &&
+ instr->operands[2].isTemp())
+ ctx.info[instr->definitions[0].tempId()].set_vcc(instr->operands[2].getTemp());
+ else if (instr->operands[0].constantEquals(0) &&
+ instr->operands[1].constantEquals(0x3f800000u) &&
+ instr->operands[2].isTemp())
+ ctx.info[instr->definitions[0].tempId()].set_b2f(instr->operands[2].getTemp());
+ break;
+ case aco_opcode::v_cmp_lg_u32:
+ if (instr->format == Format::VOPC && /* don't optimize VOP3 / SDWA / DPP */
+ instr->operands[0].constantEquals(0) &&
+ instr->operands[1].isTemp() && ctx.info[instr->operands[1].tempId()].is_vcc())
+ ctx.info[instr->definitions[0].tempId()].set_temp(ctx.info[instr->operands[1].tempId()].temp);
+ break;
+ case aco_opcode::p_phi:
+ case aco_opcode::p_linear_phi: {
+ /* lower_bool_phis() can create phis like this */
+ bool all_same_temp = instr->operands[0].isTemp();
+ /* this check is needed when moving uniform loop counters out of a divergent loop */
+ if (all_same_temp)
+ all_same_temp = instr->definitions[0].regClass() == instr->operands[0].regClass();
+ for (unsigned i = 1; all_same_temp && (i < instr->operands.size()); i++) {
+ if (!instr->operands[i].isTemp() || instr->operands[i].tempId() != instr->operands[0].tempId())
+ all_same_temp = false;
+ }
+ if (all_same_temp) {
+ ctx.info[instr->definitions[0].tempId()].set_temp(instr->operands[0].getTemp());
+ } else {
+ bool all_undef = instr->operands[0].isUndefined();
+ for (unsigned i = 1; all_undef && (i < instr->operands.size()); i++) {
+ if (!instr->operands[i].isUndefined())
+ all_undef = false;
+ }
+ if (all_undef)
+ ctx.info[instr->definitions[0].tempId()].set_undefined();
+ }
+ break;
+ }
+ case aco_opcode::v_add_u32:
+ case aco_opcode::v_add_co_u32:
+ case aco_opcode::s_add_i32:
+ case aco_opcode::s_add_u32:
+ ctx.info[instr->definitions[0].tempId()].set_add_sub(instr.get());
+ break;
+ case aco_opcode::s_not_b32:
+ case aco_opcode::s_not_b64:
+ case aco_opcode::s_and_b32:
+ case aco_opcode::s_and_b64:
+ case aco_opcode::s_or_b32:
+ case aco_opcode::s_or_b64:
+ case aco_opcode::s_xor_b32:
+ case aco_opcode::s_xor_b64:
+ case aco_opcode::s_lshl_b32:
+ case aco_opcode::v_or_b32:
+ case aco_opcode::v_lshlrev_b32:
+ ctx.info[instr->definitions[0].tempId()].set_bitwise(instr.get());
+ break;
+ case aco_opcode::v_min_f32:
+ case aco_opcode::v_min_f16:
+ case aco_opcode::v_min_u32:
+ case aco_opcode::v_min_i32:
+ case aco_opcode::v_min_u16:
+ case aco_opcode::v_min_i16:
+ case aco_opcode::v_max_f32:
+ case aco_opcode::v_max_f16:
+ case aco_opcode::v_max_u32:
+ case aco_opcode::v_max_i32:
+ case aco_opcode::v_max_u16:
+ case aco_opcode::v_max_i16:
+ ctx.info[instr->definitions[0].tempId()].set_minmax(instr.get());
+ break;
+ case aco_opcode::v_cmp_lt_f32:
+ case aco_opcode::v_cmp_eq_f32:
+ case aco_opcode::v_cmp_le_f32:
+ case aco_opcode::v_cmp_gt_f32:
+ case aco_opcode::v_cmp_lg_f32:
+ case aco_opcode::v_cmp_ge_f32:
+ case aco_opcode::v_cmp_o_f32:
+ case aco_opcode::v_cmp_u_f32:
+ case aco_opcode::v_cmp_nge_f32:
+ case aco_opcode::v_cmp_nlg_f32:
+ case aco_opcode::v_cmp_ngt_f32:
+ case aco_opcode::v_cmp_nle_f32:
+ case aco_opcode::v_cmp_neq_f32:
+ case aco_opcode::v_cmp_nlt_f32:
+ ctx.info[instr->definitions[0].tempId()].set_fcmp(instr.get());
+ break;
+ default:
+ break;
+ }
+}
+
+ALWAYS_INLINE bool get_cmp_info(aco_opcode op, aco_opcode *ordered, aco_opcode *unordered, aco_opcode *inverse)
+{
+ *ordered = *unordered = op;
+ switch (op) {
+ #define CMP(ord, unord) \
+ case aco_opcode::v_cmp_##ord##_f32:\
+ case aco_opcode::v_cmp_n##unord##_f32:\
+ *ordered = aco_opcode::v_cmp_##ord##_f32;\
+ *unordered = aco_opcode::v_cmp_n##unord##_f32;\
+ *inverse = op == aco_opcode::v_cmp_n##unord##_f32 ? aco_opcode::v_cmp_##unord##_f32 : aco_opcode::v_cmp_n##ord##_f32;\
+ return true;
+ CMP(lt, /*n*/ge)
+ CMP(eq, /*n*/lg)
+ CMP(le, /*n*/gt)
+ CMP(gt, /*n*/le)
+ CMP(lg, /*n*/eq)
+ CMP(ge, /*n*/lt)
+ #undef CMP
+ default:
+ return false;
+ }
+}
+
+aco_opcode get_ordered(aco_opcode op)
+{
+ aco_opcode ordered, unordered, inverse;
+ return get_cmp_info(op, &ordered, &unordered, &inverse) ? ordered : aco_opcode::last_opcode;
+}
+
+aco_opcode get_unordered(aco_opcode op)
+{
+ aco_opcode ordered, unordered, inverse;
+ return get_cmp_info(op, &ordered, &unordered, &inverse) ? unordered : aco_opcode::last_opcode;
+}
+
+aco_opcode get_inverse(aco_opcode op)
+{
+ aco_opcode ordered, unordered, inverse;
+ return get_cmp_info(op, &ordered, &unordered, &inverse) ? inverse : aco_opcode::last_opcode;
+}
+
+bool is_cmp(aco_opcode op)
+{
+ aco_opcode ordered, unordered, inverse;
+ return get_cmp_info(op, &ordered, &unordered, &inverse);
+}
+
+unsigned original_temp_id(opt_ctx &ctx, Temp tmp)
+{
+ if (ctx.info[tmp.id()].is_temp())
+ return ctx.info[tmp.id()].temp.id();
+ else
+ return tmp.id();
+}
+
+void decrease_uses(opt_ctx &ctx, Instruction* instr)
+{
+ if (!--ctx.uses[instr->definitions[0].tempId()]) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp())
+ ctx.uses[op.tempId()]--;
+ }
+ }
+}
+
+Instruction *follow_operand(opt_ctx &ctx, Operand op, bool ignore_uses=false)
+{
+ if (!op.isTemp() || !(ctx.info[op.tempId()].label & instr_labels))
+ return nullptr;
+ if (!ignore_uses && ctx.uses[op.tempId()] > 1)
+ return nullptr;
+
+ Instruction *instr = ctx.info[op.tempId()].instr;
+
+ if (instr->definitions.size() == 2) {
+ assert(instr->definitions[0].isTemp() && instr->definitions[0].tempId() == op.tempId());
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return nullptr;
+ }
+
+ return instr;
+}
+
+/* s_or_b64(neq(a, a), neq(b, b)) -> v_cmp_u_f32(a, b)
+ * s_and_b64(eq(a, a), eq(b, b)) -> v_cmp_o_f32(a, b) */
+bool combine_ordering_test(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->opcode != aco_opcode::s_or_b64 && instr->opcode != aco_opcode::s_and_b64)
+ return false;
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ bool neg[2] = {false, false};
+ bool abs[2] = {false, false};
+ bool opsel[2] = {false, false};
+ Instruction *op_instr[2];
+ Temp op[2];
+
+ for (unsigned i = 0; i < 2; i++) {
+ op_instr[i] = follow_operand(ctx, instr->operands[i], true);
+ if (!op_instr[i])
+ return false;
+
+ aco_opcode expected_cmp = instr->opcode == aco_opcode::s_or_b64 ?
+ aco_opcode::v_cmp_neq_f32 : aco_opcode::v_cmp_eq_f32;
+
+ if (op_instr[i]->opcode != expected_cmp)
+ return false;
+ if (!op_instr[i]->operands[0].isTemp() || !op_instr[i]->operands[1].isTemp())
+ return false;
+
+ if (op_instr[i]->isVOP3()) {
+ VOP3A_instruction *vop3 = static_cast<VOP3A_instruction*>(op_instr[i]);
+ if (vop3->neg[0] != vop3->neg[1] || vop3->abs[0] != vop3->abs[1] || vop3->opsel[0] != vop3->opsel[1])
+ return false;
+ neg[i] = vop3->neg[0];
+ abs[i] = vop3->abs[0];
+ opsel[i] = vop3->opsel[0];
+ }
+
+ Temp op0 = op_instr[i]->operands[0].getTemp();
+ Temp op1 = op_instr[i]->operands[1].getTemp();
+ if (original_temp_id(ctx, op0) != original_temp_id(ctx, op1))
+ return false;
+ /* shouldn't happen yet, but best to be safe */
+ if (op1.type() != RegType::vgpr)
+ return false;
+
+ op[i] = op1;
+ }
+
+ ctx.uses[op[0].id()]++;
+ ctx.uses[op[1].id()]++;
+ decrease_uses(ctx, op_instr[0]);
+ decrease_uses(ctx, op_instr[1]);
+
+ aco_opcode new_op = instr->opcode == aco_opcode::s_or_b64 ?
+ aco_opcode::v_cmp_u_f32 : aco_opcode::v_cmp_o_f32;
+ Instruction *new_instr;
+ if (neg[0] || neg[1] || abs[0] || abs[1] || opsel[0] || opsel[1]) {
+ VOP3A_instruction *vop3 = create_instruction<VOP3A_instruction>(new_op, asVOP3(Format::VOPC), 2, 1);
+ for (unsigned i = 0; i < 2; i++) {
+ vop3->neg[i] = neg[i];
+ vop3->abs[i] = abs[i];
+ vop3->opsel[i] = opsel[i];
+ }
+ new_instr = static_cast<Instruction *>(vop3);
+ } else {
+ new_instr = create_instruction<VOPC_instruction>(new_op, Format::VOPC, 2, 1);
+ }
+ new_instr->operands[0] = Operand(op[0]);
+ new_instr->operands[1] = Operand(op[1]);
+ new_instr->definitions[0] = instr->definitions[0];
+
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+ ctx.info[instr->definitions[0].tempId()].set_fcmp(new_instr);
+
+ instr.reset(new_instr);
+
+ return true;
+}
+
+/* s_or_b64(v_cmp_u_f32(a, b), cmp(a, b)) -> get_unordered(cmp)(a, b)
+ * s_and_b64(v_cmp_o_f32(a, b), cmp(a, b)) -> get_ordered(cmp)(a, b) */
+bool combine_comparison_ordering(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->opcode != aco_opcode::s_or_b64 && instr->opcode != aco_opcode::s_and_b64)
+ return false;
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ aco_opcode expected_nan_test = instr->opcode == aco_opcode::s_or_b64 ?
+ aco_opcode::v_cmp_u_f32 : aco_opcode::v_cmp_o_f32;
+
+ Instruction *nan_test = follow_operand(ctx, instr->operands[0], true);
+ Instruction *cmp = follow_operand(ctx, instr->operands[1], true);
+ if (!nan_test || !cmp)
+ return false;
+
+ if (cmp->opcode == expected_nan_test)
+ std::swap(nan_test, cmp);
+ else if (nan_test->opcode != expected_nan_test)
+ return false;
+
+ if (!is_cmp(cmp->opcode))
+ return false;
+
+ if (!nan_test->operands[0].isTemp() || !nan_test->operands[1].isTemp())
+ return false;
+ if (!cmp->operands[0].isTemp() || !cmp->operands[1].isTemp())
+ return false;
+
+ unsigned prop_cmp0 = original_temp_id(ctx, cmp->operands[0].getTemp());
+ unsigned prop_cmp1 = original_temp_id(ctx, cmp->operands[1].getTemp());
+ unsigned prop_nan0 = original_temp_id(ctx, nan_test->operands[0].getTemp());
+ unsigned prop_nan1 = original_temp_id(ctx, nan_test->operands[1].getTemp());
+ if (prop_cmp0 != prop_nan0 && prop_cmp0 != prop_nan1)
+ return false;
+ if (prop_cmp1 != prop_nan0 && prop_cmp1 != prop_nan1)
+ return false;
+
+ ctx.uses[cmp->operands[0].tempId()]++;
+ ctx.uses[cmp->operands[1].tempId()]++;
+ decrease_uses(ctx, nan_test);
+ decrease_uses(ctx, cmp);
+
+ aco_opcode new_op = instr->opcode == aco_opcode::s_or_b64 ?
+ get_unordered(cmp->opcode) : get_ordered(cmp->opcode);
+ Instruction *new_instr;
+ if (cmp->isVOP3()) {
+ VOP3A_instruction *new_vop3 = create_instruction<VOP3A_instruction>(new_op, asVOP3(Format::VOPC), 2, 1);
+ VOP3A_instruction *cmp_vop3 = static_cast<VOP3A_instruction*>(cmp);
+ memcpy(new_vop3->abs, cmp_vop3->abs, sizeof(new_vop3->abs));
+ memcpy(new_vop3->opsel, cmp_vop3->opsel, sizeof(new_vop3->opsel));
+ memcpy(new_vop3->neg, cmp_vop3->neg, sizeof(new_vop3->neg));
+ new_vop3->clamp = cmp_vop3->clamp;
+ new_vop3->omod = cmp_vop3->omod;
+ new_instr = new_vop3;
+ } else {
+ new_instr = create_instruction<VOPC_instruction>(new_op, Format::VOPC, 2, 1);
+ }
+ new_instr->operands[0] = cmp->operands[0];
+ new_instr->operands[1] = cmp->operands[1];
+ new_instr->definitions[0] = instr->definitions[0];
+
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+ ctx.info[instr->definitions[0].tempId()].set_fcmp(new_instr);
+
+ instr.reset(new_instr);
+
+ return true;
+}
+
+/* s_or_b64(v_cmp_neq_f32(a, a), cmp(a, #b)) and b is not NaN -> get_unordered(cmp)(a, b)
+ * s_and_b64(v_cmp_eq_f32(a, a), cmp(a, #b)) and b is not NaN -> get_ordered(cmp)(a, b) */
+bool combine_constant_comparison_ordering(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->opcode != aco_opcode::s_or_b64 && instr->opcode != aco_opcode::s_and_b64)
+ return false;
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ Instruction *nan_test = follow_operand(ctx, instr->operands[0], true);
+ Instruction *cmp = follow_operand(ctx, instr->operands[1], true);
+
+ if (!nan_test || !cmp)
+ return false;
+
+ aco_opcode expected_nan_test = instr->opcode == aco_opcode::s_or_b64 ?
+ aco_opcode::v_cmp_neq_f32 : aco_opcode::v_cmp_eq_f32;
+ if (cmp->opcode == expected_nan_test)
+ std::swap(nan_test, cmp);
+ else if (nan_test->opcode != expected_nan_test)
+ return false;
+
+ if (!is_cmp(cmp->opcode))
+ return false;
+
+ if (!nan_test->operands[0].isTemp() || !nan_test->operands[1].isTemp())
+ return false;
+ if (!cmp->operands[0].isTemp() && !cmp->operands[1].isTemp())
+ return false;
+
+ unsigned prop_nan0 = original_temp_id(ctx, nan_test->operands[0].getTemp());
+ unsigned prop_nan1 = original_temp_id(ctx, nan_test->operands[1].getTemp());
+ if (prop_nan0 != prop_nan1)
+ return false;
+
+ int constant_operand = -1;
+ for (unsigned i = 0; i < 2; i++) {
+ if (cmp->operands[i].isTemp() && original_temp_id(ctx, cmp->operands[i].getTemp()) == prop_nan0) {
+ constant_operand = !i;
+ break;
+ }
+ }
+ if (constant_operand == -1)
+ return false;
+
+ uint32_t constant;
+ if (cmp->operands[constant_operand].isConstant()) {
+ constant = cmp->operands[constant_operand].constantValue();
+ } else if (cmp->operands[constant_operand].isTemp()) {
+ unsigned id = cmp->operands[constant_operand].tempId();
+ if (!ctx.info[id].is_constant() && !ctx.info[id].is_literal())
+ return false;
+ constant = ctx.info[id].val;
+ } else {
+ return false;
+ }
+
+ float constantf;
+ memcpy(&constantf, &constant, 4);
+ if (isnan(constantf))
+ return false;
+
+ if (cmp->operands[0].isTemp())
+ ctx.uses[cmp->operands[0].tempId()]++;
+ if (cmp->operands[1].isTemp())
+ ctx.uses[cmp->operands[1].tempId()]++;
+ decrease_uses(ctx, nan_test);
+ decrease_uses(ctx, cmp);
+
+ aco_opcode new_op = instr->opcode == aco_opcode::s_or_b64 ?
+ get_unordered(cmp->opcode) : get_ordered(cmp->opcode);
+ Instruction *new_instr;
+ if (cmp->isVOP3()) {
+ VOP3A_instruction *new_vop3 = create_instruction<VOP3A_instruction>(new_op, asVOP3(Format::VOPC), 2, 1);
+ VOP3A_instruction *cmp_vop3 = static_cast<VOP3A_instruction*>(cmp);
+ memcpy(new_vop3->abs, cmp_vop3->abs, sizeof(new_vop3->abs));
+ memcpy(new_vop3->opsel, cmp_vop3->opsel, sizeof(new_vop3->opsel));
+ memcpy(new_vop3->neg, cmp_vop3->neg, sizeof(new_vop3->neg));
+ new_vop3->clamp = cmp_vop3->clamp;
+ new_vop3->omod = cmp_vop3->omod;
+ new_instr = new_vop3;
+ } else {
+ new_instr = create_instruction<VOPC_instruction>(new_op, Format::VOPC, 2, 1);
+ }
+ new_instr->operands[0] = cmp->operands[0];
+ new_instr->operands[1] = cmp->operands[1];
+ new_instr->definitions[0] = instr->definitions[0];
+
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+ ctx.info[instr->definitions[0].tempId()].set_fcmp(new_instr);
+
+ instr.reset(new_instr);
+
+ return true;
+}
+
+/* s_not_b64(cmp(a, b) -> get_inverse(cmp)(a, b) */
+bool combine_inverse_comparison(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->opcode != aco_opcode::s_not_b64)
+ return false;
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+ if (!instr->operands[0].isTemp())
+ return false;
+
+ Instruction *cmp = follow_operand(ctx, instr->operands[0]);
+ if (!cmp)
+ return false;
+
+ aco_opcode new_opcode = get_inverse(cmp->opcode);
+ if (new_opcode == aco_opcode::last_opcode)
+ return false;
+
+ if (cmp->operands[0].isTemp())
+ ctx.uses[cmp->operands[0].tempId()]++;
+ if (cmp->operands[1].isTemp())
+ ctx.uses[cmp->operands[1].tempId()]++;
+ decrease_uses(ctx, cmp);
+
+ Instruction *new_instr;
+ if (cmp->isVOP3()) {
+ VOP3A_instruction *new_vop3 = create_instruction<VOP3A_instruction>(new_opcode, asVOP3(Format::VOPC), 2, 1);
+ VOP3A_instruction *cmp_vop3 = static_cast<VOP3A_instruction*>(cmp);
+ memcpy(new_vop3->abs, cmp_vop3->abs, sizeof(new_vop3->abs));
+ memcpy(new_vop3->opsel, cmp_vop3->opsel, sizeof(new_vop3->opsel));
+ memcpy(new_vop3->neg, cmp_vop3->neg, sizeof(new_vop3->neg));
+ new_vop3->clamp = cmp_vop3->clamp;
+ new_vop3->omod = cmp_vop3->omod;
+ new_instr = new_vop3;
+ } else {
+ new_instr = create_instruction<VOPC_instruction>(new_opcode, Format::VOPC, 2, 1);
+ }
+ new_instr->operands[0] = cmp->operands[0];
+ new_instr->operands[1] = cmp->operands[1];
+ new_instr->definitions[0] = instr->definitions[0];
+
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+ ctx.info[instr->definitions[0].tempId()].set_fcmp(new_instr);
+
+ instr.reset(new_instr);
+
+ return true;
+}
+
+/* op1(op2(1, 2), 0) if swap = false
+ * op1(0, op2(1, 2)) if swap = true */
+bool match_op3_for_vop3(opt_ctx &ctx, aco_opcode op1, aco_opcode op2,
+ Instruction* op1_instr, bool swap, const char *shuffle_str,
+ Operand operands[3], bool neg[3], bool abs[3], bool opsel[3],
+ bool *op1_clamp, unsigned *op1_omod,
+ bool *inbetween_neg, bool *inbetween_abs, bool *inbetween_opsel)
+{
+ /* checks */
+ if (op1_instr->opcode != op1)
+ return false;
+
+ Instruction *op2_instr = follow_operand(ctx, op1_instr->operands[swap]);
+ if (!op2_instr || op2_instr->opcode != op2)
+ return false;
+
+ VOP3A_instruction *op1_vop3 = op1_instr->isVOP3() ? static_cast<VOP3A_instruction *>(op1_instr) : NULL;
+ VOP3A_instruction *op2_vop3 = op2_instr->isVOP3() ? static_cast<VOP3A_instruction *>(op2_instr) : NULL;
+
+ /* don't support inbetween clamp/omod */
+ if (op2_vop3 && (op2_vop3->clamp || op2_vop3->omod))
+ return false;
+
+ /* get operands and modifiers and check inbetween modifiers */
+ *op1_clamp = op1_vop3 ? op1_vop3->clamp : false;
+ *op1_omod = op1_vop3 ? op1_vop3->omod : 0u;
+
+ if (inbetween_neg)
+ *inbetween_neg = op1_vop3 ? op1_vop3->neg[swap] : false;
+ else if (op1_vop3 && op1_vop3->neg[swap])
+ return false;
+
+ if (inbetween_abs)
+ *inbetween_abs = op1_vop3 ? op1_vop3->abs[swap] : false;
+ else if (op1_vop3 && op1_vop3->abs[swap])
+ return false;
+
+ if (inbetween_opsel)
+ *inbetween_opsel = op1_vop3 ? op1_vop3->opsel[swap] : false;
+ else if (op1_vop3 && op1_vop3->opsel[swap])
+ return false;
+
+ int shuffle[3];
+ shuffle[shuffle_str[0] - '0'] = 0;
+ shuffle[shuffle_str[1] - '0'] = 1;
+ shuffle[shuffle_str[2] - '0'] = 2;
+
+ operands[shuffle[0]] = op1_instr->operands[!swap];
+ neg[shuffle[0]] = op1_vop3 ? op1_vop3->neg[!swap] : false;
+ abs[shuffle[0]] = op1_vop3 ? op1_vop3->abs[!swap] : false;
+ opsel[shuffle[0]] = op1_vop3 ? op1_vop3->opsel[!swap] : false;
+
+ for (unsigned i = 0; i < 2; i++) {
+ operands[shuffle[i + 1]] = op2_instr->operands[i];
+ neg[shuffle[i + 1]] = op2_vop3 ? op2_vop3->neg[i] : false;
+ abs[shuffle[i + 1]] = op2_vop3 ? op2_vop3->abs[i] : false;
+ opsel[shuffle[i + 1]] = op2_vop3 ? op2_vop3->opsel[i] : false;
+ }
+
+ /* check operands */
+ unsigned sgpr_id = 0;
+ for (unsigned i = 0; i < 3; i++) {
+ Operand op = operands[i];
+ if (op.isLiteral()) {
+ return false;
+ } else if (op.isTemp() && op.getTemp().type() == RegType::sgpr) {
+ if (sgpr_id && sgpr_id != op.tempId())
+ return false;
+ sgpr_id = op.tempId();
+ }
+ }
+
+ return true;
+}
+
+void create_vop3_for_op3(opt_ctx& ctx, aco_opcode opcode, aco_ptr<Instruction>& instr,
+ Operand operands[3], bool neg[3], bool abs[3], bool opsel[3],
+ bool clamp, unsigned omod)
+{
+ VOP3A_instruction *new_instr = create_instruction<VOP3A_instruction>(opcode, Format::VOP3A, 3, 1);
+ memcpy(new_instr->abs, abs, sizeof(bool[3]));
+ memcpy(new_instr->opsel, opsel, sizeof(bool[3]));
+ memcpy(new_instr->neg, neg, sizeof(bool[3]));
+ new_instr->clamp = clamp;
+ new_instr->omod = omod;
+ new_instr->operands[0] = operands[0];
+ new_instr->operands[1] = operands[1];
+ new_instr->operands[2] = operands[2];
+ new_instr->definitions[0] = instr->definitions[0];
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+
+ instr.reset(new_instr);
+}
+
+bool combine_minmax3(opt_ctx& ctx, aco_ptr<Instruction>& instr, aco_opcode new_op)
+{
+ uint32_t omod_clamp = ctx.info[instr->definitions[0].tempId()].label &
+ (label_omod_success | label_clamp_success);
+
+ for (unsigned swap = 0; swap < 2; swap++) {
+ Operand operands[3];
+ bool neg[3], abs[3], opsel[3], clamp, inbetween_neg, inbetween_abs;
+ unsigned omod;
+ if (match_op3_for_vop3(ctx, instr->opcode, instr->opcode, instr.get(), swap,
+ "012", operands, neg, abs, opsel,
+ &clamp, &omod, &inbetween_neg, &inbetween_abs, NULL)) {
+ ctx.uses[instr->operands[swap].tempId()]--;
+ neg[1] ^= inbetween_neg;
+ neg[2] ^= inbetween_neg;
+ abs[1] |= inbetween_abs;
+ abs[2] |= inbetween_abs;
+ create_vop3_for_op3(ctx, new_op, instr, operands, neg, abs, opsel, clamp, omod);
+ if (omod_clamp & label_omod_success)
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ if (omod_clamp & label_clamp_success)
+ ctx.info[instr->definitions[0].tempId()].set_clamp_success(instr.get());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool combine_three_valu_op(opt_ctx& ctx, aco_ptr<Instruction>& instr, aco_opcode op2, aco_opcode new_op, const char *shuffle, uint8_t ops)
+{
+ uint32_t omod_clamp = ctx.info[instr->definitions[0].tempId()].label &
+ (label_omod_success | label_clamp_success);
+
+ for (unsigned swap = 0; swap < 2; swap++) {
+ if (!((1 << swap) & ops))
+ continue;
+
+ Operand operands[3];
+ bool neg[3], abs[3], opsel[3], clamp;
+ unsigned omod;
+ if (match_op3_for_vop3(ctx, instr->opcode, op2,
+ instr.get(), swap, shuffle,
+ operands, neg, abs, opsel,
+ &clamp, &omod, NULL, NULL, NULL)) {
+ ctx.uses[instr->operands[swap].tempId()]--;
+ create_vop3_for_op3(ctx, new_op, instr, operands, neg, abs, opsel, clamp, omod);
+ if (omod_clamp & label_omod_success)
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ if (omod_clamp & label_clamp_success)
+ ctx.info[instr->definitions[0].tempId()].set_clamp_success(instr.get());
+ return true;
+ }
+ }
+ return false;
+}
+
+/* s_not_b32(s_and_b32(a, b)) -> s_nand_b32(a, b)
+ * s_not_b32(s_or_b32(a, b)) -> s_nor_b32(a, b)
+ * s_not_b32(s_xor_b32(a, b)) -> s_xnor_b32(a, b)
+ * s_not_b64(s_and_b64(a, b)) -> s_nand_b64(a, b)
+ * s_not_b64(s_or_b64(a, b)) -> s_nor_b64(a, b)
+ * s_not_b64(s_xor_b64(a, b)) -> s_xnor_b64(a, b) */
+bool combine_salu_not_bitwise(opt_ctx& ctx, aco_ptr<Instruction>& instr)
+{
+ /* checks */
+ if (!instr->operands[0].isTemp())
+ return false;
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ Instruction *op2_instr = follow_operand(ctx, instr->operands[0]);
+ if (!op2_instr)
+ return false;
+ switch (op2_instr->opcode) {
+ case aco_opcode::s_and_b32:
+ case aco_opcode::s_or_b32:
+ case aco_opcode::s_xor_b32:
+ case aco_opcode::s_and_b64:
+ case aco_opcode::s_or_b64:
+ case aco_opcode::s_xor_b64:
+ break;
+ default:
+ return false;
+ }
+
+ /* create instruction */
+ std::swap(instr->definitions[0], op2_instr->definitions[0]);
+ ctx.uses[instr->operands[0].tempId()]--;
+ ctx.info[op2_instr->definitions[0].tempId()].label = 0;
+
+ switch (op2_instr->opcode) {
+ case aco_opcode::s_and_b32:
+ op2_instr->opcode = aco_opcode::s_nand_b32;
+ break;
+ case aco_opcode::s_or_b32:
+ op2_instr->opcode = aco_opcode::s_nor_b32;
+ break;
+ case aco_opcode::s_xor_b32:
+ op2_instr->opcode = aco_opcode::s_xnor_b32;
+ break;
+ case aco_opcode::s_and_b64:
+ op2_instr->opcode = aco_opcode::s_nand_b64;
+ break;
+ case aco_opcode::s_or_b64:
+ op2_instr->opcode = aco_opcode::s_nor_b64;
+ break;
+ case aco_opcode::s_xor_b64:
+ op2_instr->opcode = aco_opcode::s_xnor_b64;
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/* s_and_b32(a, s_not_b32(b)) -> s_andn2_b32(a, b)
+ * s_or_b32(a, s_not_b32(b)) -> s_orn2_b32(a, b)
+ * s_and_b64(a, s_not_b64(b)) -> s_andn2_b64(a, b)
+ * s_or_b64(a, s_not_b64(b)) -> s_orn2_b64(a, b) */
+bool combine_salu_n2(opt_ctx& ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ for (unsigned i = 0; i < 2; i++) {
+ Instruction *op2_instr = follow_operand(ctx, instr->operands[i]);
+ if (!op2_instr || (op2_instr->opcode != aco_opcode::s_not_b32 && op2_instr->opcode != aco_opcode::s_not_b64))
+ continue;
+
+ ctx.uses[instr->operands[i].tempId()]--;
+ instr->operands[0] = instr->operands[!i];
+ instr->operands[1] = op2_instr->operands[0];
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+
+ switch (instr->opcode) {
+ case aco_opcode::s_and_b32:
+ instr->opcode = aco_opcode::s_andn2_b32;
+ break;
+ case aco_opcode::s_or_b32:
+ instr->opcode = aco_opcode::s_orn2_b32;
+ break;
+ case aco_opcode::s_and_b64:
+ instr->opcode = aco_opcode::s_andn2_b64;
+ break;
+ case aco_opcode::s_or_b64:
+ instr->opcode = aco_opcode::s_orn2_b64;
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+/* s_add_{i32,u32}(a, s_lshl_b32(b, <n>)) -> s_lshl<n>_add_u32(a, b) */
+bool combine_salu_lshl_add(opt_ctx& ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->definitions[1].isTemp() && ctx.uses[instr->definitions[1].tempId()])
+ return false;
+
+ for (unsigned i = 0; i < 2; i++) {
+ Instruction *op2_instr = follow_operand(ctx, instr->operands[i]);
+ if (!op2_instr || op2_instr->opcode != aco_opcode::s_lshl_b32 || !op2_instr->operands[1].isConstant())
+ continue;
+
+ uint32_t shift = op2_instr->operands[1].constantValue();
+ if (shift < 1 || shift > 4)
+ continue;
+
+ ctx.uses[instr->operands[i].tempId()]--;
+ instr->operands[1] = instr->operands[!i];
+ instr->operands[0] = op2_instr->operands[0];
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+
+ instr->opcode = ((aco_opcode[]){aco_opcode::s_lshl1_add_u32,
+ aco_opcode::s_lshl2_add_u32,
+ aco_opcode::s_lshl3_add_u32,
+ aco_opcode::s_lshl4_add_u32})[shift - 1];
+
+ return true;
+ }
+ return false;
+}
+
+bool get_minmax_info(aco_opcode op, aco_opcode *min, aco_opcode *max, aco_opcode *min3, aco_opcode *max3, aco_opcode *med3, bool *some_gfx9_only)
+{
+ switch (op) {
+ #define MINMAX(type, gfx9) \
+ case aco_opcode::v_min_##type:\
+ case aco_opcode::v_max_##type:\
+ case aco_opcode::v_med3_##type:\
+ *min = aco_opcode::v_min_##type;\
+ *max = aco_opcode::v_max_##type;\
+ *med3 = aco_opcode::v_med3_##type;\
+ *min3 = aco_opcode::v_min3_##type;\
+ *max3 = aco_opcode::v_max3_##type;\
+ *some_gfx9_only = gfx9;\
+ return true;
+ MINMAX(f32, false)
+ MINMAX(u32, false)
+ MINMAX(i32, false)
+ MINMAX(f16, true)
+ MINMAX(u16, true)
+ MINMAX(i16, true)
+ #undef MINMAX
+ default:
+ return false;
+ }
+}
+
+/* v_min_{f,u,i}{16,32}(v_max_{f,u,i}{16,32}(a, lb), ub) -> v_med3_{f,u,i}{16,32}(a, lb, ub) when ub > lb
+ * v_max_{f,u,i}{16,32}(v_min_{f,u,i}{16,32}(a, ub), lb) -> v_med3_{f,u,i}{16,32}(a, lb, ub) when ub > lb */
+bool combine_clamp(opt_ctx& ctx, aco_ptr<Instruction>& instr,
+ aco_opcode min, aco_opcode max, aco_opcode med)
+{
+ aco_opcode other_op;
+ if (instr->opcode == min)
+ other_op = max;
+ else if (instr->opcode == max)
+ other_op = min;
+ else
+ return false;
+
+ uint32_t omod_clamp = ctx.info[instr->definitions[0].tempId()].label &
+ (label_omod_success | label_clamp_success);
+
+ for (unsigned swap = 0; swap < 2; swap++) {
+ Operand operands[3];
+ bool neg[3], abs[3], opsel[3], clamp, inbetween_neg, inbetween_abs;
+ unsigned omod;
+ if (match_op3_for_vop3(ctx, instr->opcode, other_op, instr.get(), swap,
+ "012", operands, neg, abs, opsel,
+ &clamp, &omod, &inbetween_neg, &inbetween_abs, NULL)) {
+ int const0_idx = -1, const1_idx = -1;
+ uint32_t const0 = 0, const1 = 0;
+ for (int i = 0; i < 3; i++) {
+ uint32_t val;
+ if (operands[i].isConstant()) {
+ val = operands[i].constantValue();
+ } else if (operands[i].isTemp() && ctx.uses[operands[i].tempId()] == 1 &&
+ ctx.info[operands[i].tempId()].is_constant_or_literal()) {
+ val = ctx.info[operands[i].tempId()].val;
+ } else {
+ continue;
+ }
+ if (const0_idx >= 0) {
+ const1_idx = i;
+ const1 = val;
+ } else {
+ const0_idx = i;
+ const0 = val;
+ }
+ }
+ if (const0_idx < 0 || const1_idx < 0)
+ continue;
+
+ if (opsel[const0_idx])
+ const0 >>= 16;
+ if (opsel[const1_idx])
+ const1 >>= 16;
+
+ int lower_idx = const0_idx;
+ switch (min) {
+ case aco_opcode::v_min_f32:
+ case aco_opcode::v_min_f16: {
+ float const0_f, const1_f;
+ if (min == aco_opcode::v_min_f32) {
+ memcpy(&const0_f, &const0, 4);
+ memcpy(&const1_f, &const1, 4);
+ } else {
+ const0_f = _mesa_half_to_float(const0);
+ const1_f = _mesa_half_to_float(const1);
+ }
+ if (abs[const0_idx]) const0_f = fabsf(const0_f);
+ if (abs[const1_idx]) const1_f = fabsf(const1_f);
+ if (neg[const0_idx]) const0_f = -const0_f;
+ if (neg[const1_idx]) const1_f = -const1_f;
+ lower_idx = const0_f < const1_f ? const0_idx : const1_idx;
+ break;
+ }
+ case aco_opcode::v_min_u32: {
+ lower_idx = const0 < const1 ? const0_idx : const1_idx;
+ break;
+ }
+ case aco_opcode::v_min_u16: {
+ lower_idx = (uint16_t)const0 < (uint16_t)const1 ? const0_idx : const1_idx;
+ break;
+ }
+ case aco_opcode::v_min_i32: {
+ int32_t const0_i = const0 & 0x80000000u ? -2147483648 + (int32_t)(const0 & 0x7fffffffu) : const0;
+ int32_t const1_i = const1 & 0x80000000u ? -2147483648 + (int32_t)(const1 & 0x7fffffffu) : const1;
+ lower_idx = const0_i < const1_i ? const0_idx : const1_idx;
+ break;
+ }
+ case aco_opcode::v_min_i16: {
+ int16_t const0_i = const0 & 0x8000u ? -32768 + (int16_t)(const0 & 0x7fffu) : const0;
+ int16_t const1_i = const1 & 0x8000u ? -32768 + (int16_t)(const1 & 0x7fffu) : const1;
+ lower_idx = const0_i < const1_i ? const0_idx : const1_idx;
+ break;
+ }
+ default:
+ break;
+ }
+ int upper_idx = lower_idx == const0_idx ? const1_idx : const0_idx;
+
+ if (instr->opcode == min) {
+ if (upper_idx != 0 || lower_idx == 0)
+ return false;
+ } else {
+ if (upper_idx == 0 || lower_idx != 0)
+ return false;
+ }
+
+ neg[1] ^= inbetween_neg;
+ neg[2] ^= inbetween_neg;
+ abs[1] |= inbetween_abs;
+ abs[2] |= inbetween_abs;
+
+ ctx.uses[instr->operands[swap].tempId()]--;
+ create_vop3_for_op3(ctx, med, instr, operands, neg, abs, opsel, clamp, omod);
+ if (omod_clamp & label_omod_success)
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ if (omod_clamp & label_clamp_success)
+ ctx.info[instr->definitions[0].tempId()].set_clamp_success(instr.get());
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void apply_sgprs(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ /* apply sgprs */
+ uint32_t sgpr_idx = 0;
+ uint32_t sgpr_info_id = 0;
+ bool has_sgpr = false;
+ uint32_t sgpr_ssa_id = 0;
+ /* find 'best' possible sgpr */
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (instr->operands[i].isLiteral()) {
+ has_sgpr = true;
+ break;
+ }
+ if (!instr->operands[i].isTemp())
+ continue;
+ if (instr->operands[i].getTemp().type() == RegType::sgpr) {
+ has_sgpr = true;
+ sgpr_ssa_id = instr->operands[i].tempId();
+ continue;
+ }
+ ssa_info& info = ctx.info[instr->operands[i].tempId()];
+ if (info.is_temp() && info.temp.type() == RegType::sgpr) {
+ uint16_t uses = ctx.uses[instr->operands[i].tempId()];
+ if (sgpr_info_id == 0 || uses < ctx.uses[sgpr_info_id]) {
+ sgpr_idx = i;
+ sgpr_info_id = instr->operands[i].tempId();
+ }
+ }
+ }
+ if (!has_sgpr && sgpr_info_id != 0) {
+ ssa_info& info = ctx.info[sgpr_info_id];
+ if (sgpr_idx == 0 || instr->isVOP3()) {
+ instr->operands[sgpr_idx] = Operand(info.temp);
+ ctx.uses[sgpr_info_id]--;
+ ctx.uses[info.temp.id()]++;
+ } else if (can_swap_operands(instr)) {
+ instr->operands[sgpr_idx] = instr->operands[0];
+ instr->operands[0] = Operand(info.temp);
+ ctx.uses[sgpr_info_id]--;
+ ctx.uses[info.temp.id()]++;
+ } else if (can_use_VOP3(instr)) {
+ to_VOP3(ctx, instr);
+ instr->operands[sgpr_idx] = Operand(info.temp);
+ ctx.uses[sgpr_info_id]--;
+ ctx.uses[info.temp.id()]++;
+ }
+
+ /* we can have two sgprs on one instruction if it is the same sgpr! */
+ } else if (sgpr_info_id != 0 &&
+ sgpr_ssa_id == sgpr_info_id &&
+ ctx.uses[sgpr_info_id] == 1 &&
+ can_use_VOP3(instr)) {
+ to_VOP3(ctx, instr);
+ instr->operands[sgpr_idx] = Operand(ctx.info[sgpr_info_id].temp);
+ ctx.uses[sgpr_info_id]--;
+ ctx.uses[ctx.info[sgpr_info_id].temp.id()]++;
+ }
+}
+
+bool apply_omod_clamp(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ /* check if we could apply omod on predecessor */
+ if (instr->opcode == aco_opcode::v_mul_f32) {
+ if (instr->operands[1].isTemp() && ctx.info[instr->operands[1].tempId()].is_omod_success()) {
+
+ /* omod was successfully applied */
+ /* if the omod instruction is v_mad, we also have to change the original add */
+ if (ctx.info[instr->operands[1].tempId()].is_mad()) {
+ Instruction* add_instr = ctx.mad_infos[ctx.info[instr->operands[1].tempId()].val].add_instr.get();
+ if (ctx.info[instr->definitions[0].tempId()].is_clamp())
+ static_cast<VOP3A_instruction*>(add_instr)->clamp = true;
+ add_instr->definitions[0] = instr->definitions[0];
+ }
+
+ Instruction* omod_instr = ctx.info[instr->operands[1].tempId()].instr;
+ /* check if we have an additional clamp modifier */
+ if (ctx.info[instr->definitions[0].tempId()].is_clamp() && ctx.uses[instr->definitions[0].tempId()] == 1) {
+ static_cast<VOP3A_instruction*>(omod_instr)->clamp = true;
+ ctx.info[instr->definitions[0].tempId()].set_clamp_success(omod_instr);
+ }
+ /* change definition ssa-id of modified instruction */
+ omod_instr->definitions[0] = instr->definitions[0];
+
+ /* change the definition of instr to something unused, e.g. the original omod def */
+ instr->definitions[0] = Definition(instr->operands[1].getTemp());
+ ctx.uses[instr->definitions[0].tempId()] = 0;
+ return true;
+ }
+ if (!ctx.info[instr->definitions[0].tempId()].label) {
+ /* in all other cases, label this instruction as option for multiply-add */
+ ctx.info[instr->definitions[0].tempId()].set_mul(instr.get());
+ }
+ }
+
+ /* check if we could apply clamp on predecessor */
+ if (instr->opcode == aco_opcode::v_med3_f32) {
+ unsigned idx = 0;
+ bool found_zero = false, found_one = false;
+ for (unsigned i = 0; i < 3; i++)
+ {
+ if (instr->operands[i].constantEquals(0))
+ found_zero = true;
+ else if (instr->operands[i].constantEquals(0x3f800000)) /* 1.0 */
+ found_one = true;
+ else
+ idx = i;
+ }
+ if (found_zero && found_one && instr->operands[idx].isTemp() &&
+ ctx.info[instr->operands[idx].tempId()].is_clamp_success()) {
+ /* clamp was successfully applied */
+ /* if the clamp instruction is v_mad, we also have to change the original add */
+ if (ctx.info[instr->operands[idx].tempId()].is_mad()) {
+ Instruction* add_instr = ctx.mad_infos[ctx.info[instr->operands[idx].tempId()].val].add_instr.get();
+ add_instr->definitions[0] = instr->definitions[0];
+ }
+ Instruction* clamp_instr = ctx.info[instr->operands[idx].tempId()].instr;
+ /* change definition ssa-id of modified instruction */
+ clamp_instr->definitions[0] = instr->definitions[0];
+
+ /* change the definition of instr to something unused, e.g. the original omod def */
+ instr->definitions[0] = Definition(instr->operands[idx].getTemp());
+ ctx.uses[instr->definitions[0].tempId()] = 0;
+ return true;
+ }
+ }
+
+ /* apply omod / clamp modifiers if the def is used only once and the instruction can have modifiers */
+ if (!instr->definitions.empty() && ctx.uses[instr->definitions[0].tempId()] == 1 &&
+ can_use_VOP3(instr) && instr_info.can_use_output_modifiers[(int)instr->opcode]) {
+ if(ctx.info[instr->definitions[0].tempId()].is_omod2()) {
+ to_VOP3(ctx, instr);
+ static_cast<VOP3A_instruction*>(instr.get())->omod = 1;
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ } else if (ctx.info[instr->definitions[0].tempId()].is_omod4()) {
+ to_VOP3(ctx, instr);
+ static_cast<VOP3A_instruction*>(instr.get())->omod = 2;
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ } else if (ctx.info[instr->definitions[0].tempId()].is_omod5()) {
+ to_VOP3(ctx, instr);
+ static_cast<VOP3A_instruction*>(instr.get())->omod = 3;
+ ctx.info[instr->definitions[0].tempId()].set_omod_success(instr.get());
+ } else if (ctx.info[instr->definitions[0].tempId()].is_clamp()) {
+ to_VOP3(ctx, instr);
+ static_cast<VOP3A_instruction*>(instr.get())->clamp = true;
+ ctx.info[instr->definitions[0].tempId()].set_clamp_success(instr.get());
+ }
+ }
+
+ return false;
+}
+
+// TODO: we could possibly move the whole label_instruction pass to combine_instruction:
+// this would mean that we'd have to fix the instruction uses while value propagation
+
+void combine_instruction(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ if (instr->definitions.empty() || !ctx.uses[instr->definitions[0].tempId()])
+ return;
+
+ if (instr->isVALU()) {
+ apply_sgprs(ctx, instr);
+ if (apply_omod_clamp(ctx, instr))
+ return;
+ }
+
+ /* TODO: There are still some peephole optimizations that could be done:
+ * - abs(a - b) -> s_absdiff_i32
+ * - various patterns for s_bitcmp{0,1}_b32 and s_bitset{0,1}_b32
+ * - patterns for v_alignbit_b32 and v_alignbyte_b32
+ * These aren't probably too interesting though.
+ * There are also patterns for v_cmp_class_f{16,32,64}. This is difficult but
+ * probably more useful than the previously mentioned optimizations.
+ * The various comparison optimizations also currently only work with 32-bit
+ * floats. */
+
+ /* neg(mul(a, b)) -> mul(neg(a), b) */
+ if (ctx.info[instr->definitions[0].tempId()].is_neg() && ctx.uses[instr->operands[1].tempId()] == 1) {
+ Temp val = ctx.info[instr->definitions[0].tempId()].temp;
+
+ if (!ctx.info[val.id()].is_mul())
+ return;
+
+ Instruction* mul_instr = ctx.info[val.id()].instr;
+
+ if (mul_instr->operands[0].isLiteral())
+ return;
+ if (mul_instr->isVOP3() && static_cast<VOP3A_instruction*>(mul_instr)->clamp)
+ return;
+
+ /* convert to mul(neg(a), b) */
+ ctx.uses[mul_instr->definitions[0].tempId()]--;
+ Definition def = instr->definitions[0];
+ /* neg(abs(mul(a, b))) -> mul(neg(abs(a)), abs(b)) */
+ bool is_abs = ctx.info[instr->definitions[0].tempId()].is_abs();
+ instr.reset(create_instruction<VOP3A_instruction>(aco_opcode::v_mul_f32, asVOP3(Format::VOP2), 2, 1));
+ instr->operands[0] = mul_instr->operands[0];
+ instr->operands[1] = mul_instr->operands[1];
+ instr->definitions[0] = def;
+ VOP3A_instruction* new_mul = static_cast<VOP3A_instruction*>(instr.get());
+ if (mul_instr->isVOP3()) {
+ VOP3A_instruction* mul = static_cast<VOP3A_instruction*>(mul_instr);
+ new_mul->neg[0] = mul->neg[0] && !is_abs;
+ new_mul->neg[1] = mul->neg[1] && !is_abs;
+ new_mul->abs[0] = mul->abs[0] || is_abs;
+ new_mul->abs[1] = mul->abs[1] || is_abs;
+ new_mul->omod = mul->omod;
+ }
+ new_mul->neg[0] ^= true;
+ new_mul->clamp = false;
+
+ ctx.info[instr->definitions[0].tempId()].set_mul(instr.get());
+ return;
+ }
+ /* combine mul+add -> mad */
+ else if (instr->opcode == aco_opcode::v_add_f32 ||
+ instr->opcode == aco_opcode::v_sub_f32 ||
+ instr->opcode == aco_opcode::v_subrev_f32) {
+
+ uint32_t uses_src0 = UINT32_MAX;
+ uint32_t uses_src1 = UINT32_MAX;
+ Instruction* mul_instr = nullptr;
+ unsigned add_op_idx;
+ /* check if any of the operands is a multiplication */
+ if (instr->operands[0].isTemp() && ctx.info[instr->operands[0].tempId()].is_mul())
+ uses_src0 = ctx.uses[instr->operands[0].tempId()];
+ if (instr->operands[1].isTemp() && ctx.info[instr->operands[1].tempId()].is_mul())
+ uses_src1 = ctx.uses[instr->operands[1].tempId()];
+
+ /* find the 'best' mul instruction to combine with the add */
+ if (uses_src0 < uses_src1) {
+ mul_instr = ctx.info[instr->operands[0].tempId()].instr;
+ add_op_idx = 1;
+ } else if (uses_src1 < uses_src0) {
+ mul_instr = ctx.info[instr->operands[1].tempId()].instr;
+ add_op_idx = 0;
+ } else if (uses_src0 != UINT32_MAX) {
+ /* tiebreaker: quite random what to pick */
+ if (ctx.info[instr->operands[0].tempId()].instr->operands[0].isLiteral()) {
+ mul_instr = ctx.info[instr->operands[1].tempId()].instr;
+ add_op_idx = 0;
+ } else {
+ mul_instr = ctx.info[instr->operands[0].tempId()].instr;
+ add_op_idx = 1;
+ }
+ }
+ if (mul_instr) {
+ Operand op[3] = {Operand(v1), Operand(v1), Operand(v1)};
+ bool neg[3] = {false, false, false};
+ bool abs[3] = {false, false, false};
+ unsigned omod = 0;
+ bool clamp = false;
+ bool need_vop3 = false;
+ int num_sgpr = 0;
+ op[0] = mul_instr->operands[0];
+ op[1] = mul_instr->operands[1];
+ op[2] = instr->operands[add_op_idx];
+ for (unsigned i = 0; i < 3; i++)
+ {
+ if (op[i].isLiteral())
+ return;
+ if (op[i].isTemp() && op[i].getTemp().type() == RegType::sgpr)
+ num_sgpr++;
+ if (!(i == 0 || (op[i].isTemp() && op[i].getTemp().type() == RegType::vgpr)))
+ need_vop3 = true;
+ }
+ // TODO: would be better to check this before selecting a mul instr?
+ if (num_sgpr > 1)
+ return;
+
+ if (mul_instr->isVOP3()) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*> (mul_instr);
+ neg[0] = vop3->neg[0];
+ neg[1] = vop3->neg[1];
+ abs[0] = vop3->abs[0];
+ abs[1] = vop3->abs[1];
+ need_vop3 = true;
+ /* we cannot use these modifiers between mul and add */
+ if (vop3->clamp || vop3->omod)
+ return;
+ }
+
+ /* convert to mad */
+ ctx.uses[mul_instr->definitions[0].tempId()]--;
+ if (ctx.uses[mul_instr->definitions[0].tempId()]) {
+ if (op[0].isTemp())
+ ctx.uses[op[0].tempId()]++;
+ if (op[1].isTemp())
+ ctx.uses[op[1].tempId()]++;
+ }
+
+ if (instr->isVOP3()) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*> (instr.get());
+ neg[2] = vop3->neg[add_op_idx];
+ abs[2] = vop3->abs[add_op_idx];
+ omod = vop3->omod;
+ clamp = vop3->clamp;
+ /* abs of the multiplication result */
+ if (vop3->abs[1 - add_op_idx]) {
+ neg[0] = false;
+ neg[1] = false;
+ abs[0] = true;
+ abs[1] = true;
+ }
+ /* neg of the multiplication result */
+ neg[1] = neg[1] ^ vop3->neg[1 - add_op_idx];
+ need_vop3 = true;
+ }
+ if (instr->opcode == aco_opcode::v_sub_f32) {
+ neg[1 + add_op_idx] = neg[1 + add_op_idx] ^ true;
+ need_vop3 = true;
+ } else if (instr->opcode == aco_opcode::v_subrev_f32) {
+ neg[2 - add_op_idx] = neg[2 - add_op_idx] ^ true;
+ need_vop3 = true;
+ }
+
+ aco_ptr<VOP3A_instruction> mad{create_instruction<VOP3A_instruction>(aco_opcode::v_mad_f32, Format::VOP3A, 3, 1)};
+ for (unsigned i = 0; i < 3; i++)
+ {
+ mad->operands[i] = op[i];
+ mad->neg[i] = neg[i];
+ mad->abs[i] = abs[i];
+ }
+ mad->omod = omod;
+ mad->clamp = clamp;
+ mad->definitions[0] = instr->definitions[0];
+
+ /* mark this ssa_def to be re-checked for profitability and literals */
+ ctx.mad_infos.emplace_back(std::move(instr), mul_instr->definitions[0].tempId(), need_vop3);
+ ctx.info[mad->definitions[0].tempId()].set_mad(mad.get(), ctx.mad_infos.size() - 1);
+ instr.reset(mad.release());
+ return;
+ }
+ }
+ /* v_mul_f32(v_cndmask_b32(0, 1.0, cond), a) -> v_cndmask_b32(0, a, cond) */
+ else if (instr->opcode == aco_opcode::v_mul_f32 && !instr->isVOP3()) {
+ for (unsigned i = 0; i < 2; i++) {
+ if (instr->operands[i].isTemp() && ctx.info[instr->operands[i].tempId()].is_b2f() &&
+ ctx.uses[instr->operands[i].tempId()] == 1 &&
+ instr->operands[!i].isTemp() && instr->operands[!i].getTemp().type() == RegType::vgpr) {
+ ctx.uses[instr->operands[i].tempId()]--;
+ ctx.uses[ctx.info[instr->operands[i].tempId()].temp.id()]++;
+
+ aco_ptr<VOP2_instruction> new_instr{create_instruction<VOP2_instruction>(aco_opcode::v_cndmask_b32, Format::VOP2, 3, 1)};
+ new_instr->operands[0] = Operand(0u);
+ new_instr->operands[1] = instr->operands[!i];
+ new_instr->operands[2] = Operand(ctx.info[instr->operands[i].tempId()].temp);
+ new_instr->definitions[0] = instr->definitions[0];
+ instr.reset(new_instr.release());
+ ctx.info[instr->definitions[0].tempId()].label = 0;
+ return;
+ }
+ }
+ } else if (instr->opcode == aco_opcode::v_or_b32 && ctx.program->chip_class >= GFX9) {
+ if (combine_three_valu_op(ctx, instr, aco_opcode::v_or_b32, aco_opcode::v_or3_b32, "012", 1 | 2)) ;
+ else if (combine_three_valu_op(ctx, instr, aco_opcode::v_and_b32, aco_opcode::v_and_or_b32, "120", 1 | 2)) ;
+ else combine_three_valu_op(ctx, instr, aco_opcode::v_lshlrev_b32, aco_opcode::v_lshl_or_b32, "210", 1 | 2);
+ } else if (instr->opcode == aco_opcode::v_add_u32 && ctx.program->chip_class >= GFX9) {
+ if (combine_three_valu_op(ctx, instr, aco_opcode::v_xor_b32, aco_opcode::v_xad_u32, "120", 1 | 2)) ;
+ else if (combine_three_valu_op(ctx, instr, aco_opcode::v_add_u32, aco_opcode::v_add3_u32, "012", 1 | 2)) ;
+ else combine_three_valu_op(ctx, instr, aco_opcode::v_lshlrev_b32, aco_opcode::v_lshl_add_u32, "210", 1 | 2);
+ } else if (instr->opcode == aco_opcode::v_lshlrev_b32 && ctx.program->chip_class >= GFX9) {
+ combine_three_valu_op(ctx, instr, aco_opcode::v_add_u32, aco_opcode::v_add_lshl_u32, "120", 2);
+ } else if ((instr->opcode == aco_opcode::s_add_u32 || instr->opcode == aco_opcode::s_add_i32) && ctx.program->chip_class >= GFX9) {
+ combine_salu_lshl_add(ctx, instr);
+ } else if (instr->opcode == aco_opcode::s_not_b32) {
+ combine_salu_not_bitwise(ctx, instr);
+ } else if (instr->opcode == aco_opcode::s_not_b64) {
+ if (combine_inverse_comparison(ctx, instr)) ;
+ else combine_salu_not_bitwise(ctx, instr);
+ } else if (instr->opcode == aco_opcode::s_and_b32 || instr->opcode == aco_opcode::s_or_b32) {
+ combine_salu_n2(ctx, instr);
+ } else if (instr->opcode == aco_opcode::s_and_b64 || instr->opcode == aco_opcode::s_or_b64) {
+ if (combine_ordering_test(ctx, instr)) ;
+ else if (combine_comparison_ordering(ctx, instr)) ;
+ else if (combine_constant_comparison_ordering(ctx, instr)) ;
+ else combine_salu_n2(ctx, instr);
+ } else {
+ aco_opcode min, max, min3, max3, med3;
+ bool some_gfx9_only;
+ if (get_minmax_info(instr->opcode, &min, &max, &min3, &max3, &med3, &some_gfx9_only) &&
+ (!some_gfx9_only || ctx.program->chip_class >= GFX9)) {
+ if (combine_minmax3(ctx, instr, instr->opcode == min ? min3 : max3)) ;
+ else combine_clamp(ctx, instr, min, max, med3);
+ }
+ }
+}
+
+
+void select_instruction(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ const uint32_t threshold = 4;
+
+ /* Dead Code Elimination:
+ * We remove instructions if they define temporaries which all are unused */
+ const bool is_used = instr->definitions.empty() ||
+ std::any_of(instr->definitions.begin(), instr->definitions.end(),
+ [&ctx](const Definition& def) { return ctx.uses[def.tempId()]; });
+ if (!is_used) {
+ instr.reset();
+ return;
+ }
+
+ /* convert split_vector into extract_vector if only one definition is ever used */
+ if (instr->opcode == aco_opcode::p_split_vector) {
+ unsigned num_used = 0;
+ unsigned idx = 0;
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ if (ctx.uses[instr->definitions[i].tempId()]) {
+ num_used++;
+ idx = i;
+ }
+ }
+ if (num_used == 1) {
+ aco_ptr<Pseudo_instruction> extract{create_instruction<Pseudo_instruction>(aco_opcode::p_extract_vector, Format::PSEUDO, 2, 1)};
+ extract->operands[0] = instr->operands[0];
+ extract->operands[1] = Operand((uint32_t) idx);
+ extract->definitions[0] = instr->definitions[idx];
+ instr.reset(extract.release());
+ }
+ }
+
+ /* re-check mad instructions */
+ if (instr->opcode == aco_opcode::v_mad_f32 && ctx.info[instr->definitions[0].tempId()].is_mad()) {
+ mad_info* info = &ctx.mad_infos[ctx.info[instr->definitions[0].tempId()].val];
+ /* first, check profitability */
+ if (ctx.uses[info->mul_temp_id]) {
+ ctx.uses[info->mul_temp_id]++;
+ instr.swap(info->add_instr);
+
+ /* second, check possible literals */
+ } else if (!info->needs_vop3) {
+ uint32_t literal_idx = 0;
+ uint32_t literal_uses = UINT32_MAX;
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (!instr->operands[i].isTemp())
+ continue;
+ /* if one of the operands is sgpr, we cannot add a literal somewhere else */
+ if (instr->operands[i].getTemp().type() == RegType::sgpr) {
+ if (ctx.info[instr->operands[i].tempId()].is_literal()) {
+ literal_uses = ctx.uses[instr->operands[i].tempId()];
+ literal_idx = i;
+ } else {
+ literal_uses = UINT32_MAX;
+ }
+ break;
+ }
+ else if (ctx.info[instr->operands[i].tempId()].is_literal() &&
+ ctx.uses[instr->operands[i].tempId()] < literal_uses) {
+ literal_uses = ctx.uses[instr->operands[i].tempId()];
+ literal_idx = i;
+ }
+ }
+ if (literal_uses < threshold) {
+ ctx.uses[instr->operands[literal_idx].tempId()]--;
+ info->check_literal = true;
+ info->literal_idx = literal_idx;
+ }
+ }
+ return;
+ }
+
+ /* check for literals */
+ /* we do not apply the literals yet as we don't know if it is profitable */
+ if (instr->isSALU()) {
+ uint32_t literal_idx = 0;
+ uint32_t literal_uses = UINT32_MAX;
+ bool has_literal = false;
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (instr->operands[i].isLiteral()) {
+ has_literal = true;
+ break;
+ }
+ if (!instr->operands[i].isTemp())
+ continue;
+ if (ctx.info[instr->operands[i].tempId()].is_literal() &&
+ ctx.uses[instr->operands[i].tempId()] < literal_uses) {
+ literal_uses = ctx.uses[instr->operands[i].tempId()];
+ literal_idx = i;
+ }
+ }
+ if (!has_literal && literal_uses < threshold) {
+ ctx.uses[instr->operands[literal_idx].tempId()]--;
+ if (ctx.uses[instr->operands[literal_idx].tempId()] == 0)
+ instr->operands[literal_idx] = Operand(ctx.info[instr->operands[literal_idx].tempId()].val);
+ }
+ } else if (instr->isVALU() && valu_can_accept_literal(ctx, instr) &&
+ instr->operands[0].isTemp() &&
+ ctx.info[instr->operands[0].tempId()].is_literal() &&
+ ctx.uses[instr->operands[0].tempId()] < threshold) {
+ ctx.uses[instr->operands[0].tempId()]--;
+ if (ctx.uses[instr->operands[0].tempId()] == 0)
+ instr->operands[0] = Operand(ctx.info[instr->operands[0].tempId()].val);
+ }
+
+}
+
+
+void apply_literals(opt_ctx &ctx, aco_ptr<Instruction>& instr)
+{
+ /* Cleanup Dead Instructions */
+ if (!instr)
+ return;
+
+ /* apply literals on SALU */
+ if (instr->isSALU()) {
+ for (Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ if (op.isLiteral())
+ break;
+ if (ctx.info[op.tempId()].is_literal() &&
+ ctx.uses[op.tempId()] == 0)
+ op = Operand(ctx.info[op.tempId()].val);
+ }
+ }
+
+ /* apply literals on VALU */
+ else if (instr->isVALU() && !instr->isVOP3() &&
+ instr->operands[0].isTemp() &&
+ ctx.info[instr->operands[0].tempId()].is_literal() &&
+ ctx.uses[instr->operands[0].tempId()] == 0) {
+ instr->operands[0] = Operand(ctx.info[instr->operands[0].tempId()].val);
+ }
+
+ /* apply literals on MAD */
+ else if (instr->opcode == aco_opcode::v_mad_f32 && ctx.info[instr->definitions[0].tempId()].is_mad()) {
+ mad_info* info = &ctx.mad_infos[ctx.info[instr->definitions[0].tempId()].val];
+ aco_ptr<Instruction> new_mad;
+ if (info->check_literal && ctx.uses[instr->operands[info->literal_idx].tempId()] == 0) {
+ if (info->literal_idx == 2) { /* add literal -> madak */
+ new_mad.reset(create_instruction<VOP2_instruction>(aco_opcode::v_madak_f32, Format::VOP2, 3, 1));
+ new_mad->operands[0] = instr->operands[0];
+ new_mad->operands[1] = instr->operands[1];
+ } else { /* mul literal -> madmk */
+ new_mad.reset(create_instruction<VOP2_instruction>(aco_opcode::v_madmk_f32, Format::VOP2, 3, 1));
+ new_mad->operands[0] = instr->operands[1 - info->literal_idx];
+ new_mad->operands[1] = instr->operands[2];
+ }
+ new_mad->operands[2] = Operand(ctx.info[instr->operands[info->literal_idx].tempId()].val);
+ new_mad->definitions[0] = instr->definitions[0];
+ instr.swap(new_mad);
+ }
+ }
+
+ ctx.instructions.emplace_back(std::move(instr));
+}
+
+
+void optimize(Program* program)
+{
+ opt_ctx ctx;
+ ctx.program = program;
+ std::vector<ssa_info> info(program->peekAllocationId());
+ ctx.info = info.data();
+
+ /* 1. Bottom-Up DAG pass (forward) to label all ssa-defs */
+ for (Block& block : program->blocks) {
+ for (aco_ptr<Instruction>& instr : block.instructions)
+ label_instruction(ctx, instr);
+ }
+
+ ctx.uses = std::move(dead_code_analysis(program));
+
+ /* 2. Combine v_mad, omod, clamp and propagate sgpr on VALU instructions */
+ for (Block& block : program->blocks) {
+ for (aco_ptr<Instruction>& instr : block.instructions)
+ combine_instruction(ctx, instr);
+ }
+
+ /* 3. Top-Down DAG pass (backward) to select instructions (includes DCE) */
+ for (std::vector<Block>::reverse_iterator it = program->blocks.rbegin(); it != program->blocks.rend(); ++it) {
+ Block* block = &(*it);
+ for (std::vector<aco_ptr<Instruction>>::reverse_iterator it = block->instructions.rbegin(); it != block->instructions.rend(); ++it)
+ select_instruction(ctx, *it);
+ }
+
+ /* 4. Add literals to instructions */
+ for (Block& block : program->blocks) {
+ ctx.instructions.clear();
+ for (aco_ptr<Instruction>& instr : block.instructions)
+ apply_literals(ctx, instr);
+ block.instructions.swap(ctx.instructions);
+ }
+
+}
+
+}
diff --git a/src/amd/compiler/aco_print_asm.cpp b/src/amd/compiler/aco_print_asm.cpp
new file mode 100644
index 00000000000..31079aa1c4a
--- /dev/null
+++ b/src/amd/compiler/aco_print_asm.cpp
@@ -0,0 +1,104 @@
+
+#include <iomanip>
+#include "aco_ir.h"
+#include "llvm-c/Disassembler.h"
+#include "ac_llvm_util.h"
+
+#include <llvm/ADT/StringRef.h>
+
+namespace aco {
+
+void print_asm(Program *program, std::vector<uint32_t>& binary,
+ unsigned exec_size, enum radeon_family family, std::ostream& out)
+{
+ std::vector<bool> referenced_blocks(program->blocks.size());
+ referenced_blocks[0] = true;
+ for (Block& block : program->blocks) {
+ for (unsigned succ : block.linear_succs)
+ referenced_blocks[succ] = true;
+ }
+
+ std::vector<std::tuple<uint64_t, llvm::StringRef, uint8_t>> symbols;
+ std::vector<std::array<char,16>> block_names;
+ block_names.reserve(program->blocks.size());
+ for (Block& block : program->blocks) {
+ if (!referenced_blocks[block.index])
+ continue;
+ std::array<char, 16> name;
+ sprintf(name.data(), "BB%u", block.index);
+ block_names.push_back(name);
+ symbols.emplace_back(block.offset * 4, llvm::StringRef(block_names[block_names.size() - 1].data()), 0);
+ }
+
+ LLVMDisasmContextRef disasm = LLVMCreateDisasmCPU("amdgcn-mesa-mesa3d",
+ ac_get_llvm_processor_name(family),
+ &symbols, 0, NULL, NULL);
+
+ char outline[1024];
+ size_t pos = 0;
+ bool invalid = false;
+ unsigned next_block = 0;
+ while (pos < exec_size) {
+ while (next_block < program->blocks.size() && pos == program->blocks[next_block].offset) {
+ if (referenced_blocks[next_block])
+ out << "BB" << std::dec << next_block << ":" << std::endl;
+ next_block++;
+ }
+
+ size_t l = LLVMDisasmInstruction(disasm, (uint8_t *) &binary[pos],
+ (exec_size - pos) * sizeof(uint32_t), pos * 4,
+ outline, sizeof(outline));
+
+ size_t new_pos;
+ const int align_width = 60;
+ if (program->chip_class == GFX9 && !l && ((binary[pos] & 0xffff8000) == 0xd1348000)) { /* not actually an invalid instruction */
+ out << std::left << std::setw(align_width) << std::setfill(' ') << "\tv_add_u32_e64 + clamp";
+ new_pos = pos + 2;
+ } else if (!l) {
+ out << std::left << std::setw(align_width) << std::setfill(' ') << "(invalid instruction)";
+ new_pos = pos + 1;
+ invalid = true;
+ } else {
+ out << std::left << std::setw(align_width) << std::setfill(' ') << outline;
+ assert(l % 4 == 0);
+ new_pos = pos + l / 4;
+ }
+ out << std::right;
+
+ out << " ;";
+ for (; pos < new_pos; pos++)
+ out << " " << std::setfill('0') << std::setw(8) << std::hex << binary[pos];
+ out << std::endl;
+ }
+ out << std::setfill(' ') << std::setw(0) << std::dec;
+ assert(next_block == program->blocks.size());
+
+ LLVMDisasmDispose(disasm);
+
+ if (program->constant_data.size()) {
+ out << std::endl << "/* constant data */" << std::endl;
+ for (unsigned i = 0; i < program->constant_data.size(); i += 32) {
+ out << '[' << std::setw(6) << std::setfill('0') << std::dec << i << ']';
+ unsigned line_size = std::min<size_t>(program->constant_data.size() - i, 32);
+ for (unsigned j = 0; j < line_size; j += 4) {
+ unsigned size = std::min<size_t>(program->constant_data.size() - (i + j), 4);
+ uint32_t v = 0;
+ memcpy(&v, &program->constant_data[i + j], size);
+ out << " " << std::setw(8) << std::setfill('0') << std::hex << v;
+ }
+ out << std::endl;
+ }
+ }
+
+ out << std::setfill(' ') << std::setw(0) << std::dec;
+
+ if (invalid) {
+ /* Invalid instructions usually lead to GPU hangs, which can make
+ * getting the actual invalid instruction hard. Abort here so that we
+ * can find the problem.
+ */
+ abort();
+ }
+}
+
+}
diff --git a/src/amd/compiler/aco_print_ir.cpp b/src/amd/compiler/aco_print_ir.cpp
new file mode 100644
index 00000000000..517ddc235ce
--- /dev/null
+++ b/src/amd/compiler/aco_print_ir.cpp
@@ -0,0 +1,575 @@
+#include "aco_ir.h"
+#include "aco_builder.h"
+
+#include "sid.h"
+
+namespace aco {
+
+static const char *reduce_ops[] = {
+ [iadd32] = "iadd32",
+ [iadd64] = "iadd64",
+ [imul32] = "imul32",
+ [imul64] = "imul64",
+ [fadd32] = "fadd32",
+ [fadd64] = "fadd64",
+ [fmul32] = "fmul32",
+ [fmul64] = "fmul64",
+ [imin32] = "imin32",
+ [imin64] = "imin64",
+ [imax32] = "imax32",
+ [imax64] = "imax64",
+ [umin32] = "umin32",
+ [umin64] = "umin64",
+ [umax32] = "umax32",
+ [umax64] = "umax64",
+ [fmin32] = "fmin32",
+ [fmin64] = "fmin64",
+ [fmax32] = "fmax32",
+ [fmax64] = "fmax64",
+ [iand32] = "iand32",
+ [iand64] = "iand64",
+ [ior32] = "ior32",
+ [ior64] = "ior64",
+ [ixor32] = "ixor32",
+ [ixor64] = "ixor64",
+};
+
+static void print_reg_class(const RegClass rc, FILE *output)
+{
+ switch (rc) {
+ case RegClass::s1: fprintf(output, " s1: "); return;
+ case RegClass::s2: fprintf(output, " s2: "); return;
+ case RegClass::s3: fprintf(output, " s3: "); return;
+ case RegClass::s4: fprintf(output, " s4: "); return;
+ case RegClass::s6: fprintf(output, " s6: "); return;
+ case RegClass::s8: fprintf(output, " s8: "); return;
+ case RegClass::s16: fprintf(output, "s16: "); return;
+ case RegClass::v1: fprintf(output, " v1: "); return;
+ case RegClass::v2: fprintf(output, " v2: "); return;
+ case RegClass::v3: fprintf(output, " v3: "); return;
+ case RegClass::v4: fprintf(output, " v4: "); return;
+ case RegClass::v5: fprintf(output, " v5: "); return;
+ case RegClass::v6: fprintf(output, " v6: "); return;
+ case RegClass::v7: fprintf(output, " v7: "); return;
+ case RegClass::v8: fprintf(output, " v8: "); return;
+ case RegClass::v1_linear: fprintf(output, " v1: "); return;
+ case RegClass::v2_linear: fprintf(output, " v2: "); return;
+ }
+}
+
+void print_physReg(unsigned reg, unsigned size, FILE *output)
+{
+ if (reg == 124) {
+ fprintf(output, ":m0");
+ } else if (reg == 106) {
+ fprintf(output, ":vcc");
+ } else if (reg == 253) {
+ fprintf(output, ":scc");
+ } else if (reg == 126) {
+ fprintf(output, ":exec");
+ } else {
+ bool is_vgpr = reg / 256;
+ reg = reg % 256;
+ fprintf(output, ":%c[%d", is_vgpr ? 'v' : 's', reg);
+ if (size > 1)
+ fprintf(output, "-%d]", reg + size -1);
+ else
+ fprintf(output, "]");
+ }
+}
+
+static void print_constant(uint8_t reg, FILE *output)
+{
+ if (reg >= 128 && reg <= 192) {
+ fprintf(output, "%d", reg - 128);
+ return;
+ } else if (reg >= 192 && reg <= 208) {
+ fprintf(output, "%d", 192 - reg);
+ return;
+ }
+
+ switch (reg) {
+ case 240:
+ fprintf(output, "0.5");
+ break;
+ case 241:
+ fprintf(output, "-0.5");
+ break;
+ case 242:
+ fprintf(output, "1.0");
+ break;
+ case 243:
+ fprintf(output, "-1.0");
+ break;
+ case 244:
+ fprintf(output, "2.0");
+ break;
+ case 245:
+ fprintf(output, "-2.0");
+ break;
+ case 246:
+ fprintf(output, "4.0");
+ break;
+ case 247:
+ fprintf(output, "-4.0");
+ break;
+ case 248:
+ fprintf(output, "1/(2*PI)");
+ break;
+ }
+}
+
+static void print_operand(const Operand *operand, FILE *output)
+{
+ if (operand->isLiteral()) {
+ fprintf(output, "0x%x", operand->constantValue());
+ } else if (operand->isConstant()) {
+ print_constant(operand->physReg().reg, output);
+ } else if (operand->isUndefined()) {
+ print_reg_class(operand->regClass(), output);
+ fprintf(output, "undef");
+ } else {
+ fprintf(output, "%%%d", operand->tempId());
+
+ if (operand->isFixed())
+ print_physReg(operand->physReg(), operand->size(), output);
+ }
+}
+
+static void print_definition(const Definition *definition, FILE *output)
+{
+ print_reg_class(definition->regClass(), output);
+ fprintf(output, "%%%d", definition->tempId());
+
+ if (definition->isFixed())
+ print_physReg(definition->physReg(), definition->size(), output);
+}
+
+static void print_barrier_reorder(bool can_reorder, barrier_interaction barrier, FILE *output)
+{
+ if (can_reorder)
+ fprintf(output, " reorder");
+
+ if (barrier & barrier_buffer)
+ fprintf(output, " buffer");
+ if (barrier & barrier_image)
+ fprintf(output, " image");
+ if (barrier & barrier_atomic)
+ fprintf(output, " atomic");
+ if (barrier & barrier_shared)
+ fprintf(output, " shared");
+}
+
+static void print_instr_format_specific(struct Instruction *instr, FILE *output)
+{
+ switch (instr->format) {
+ case Format::SOPK: {
+ SOPK_instruction* sopk = static_cast<SOPK_instruction*>(instr);
+ fprintf(output, " imm:%d", sopk->imm & 0x8000 ? (sopk->imm - 65536) : sopk->imm);
+ break;
+ }
+ case Format::SOPP: {
+ SOPP_instruction* sopp = static_cast<SOPP_instruction*>(instr);
+ uint16_t imm = sopp->imm;
+ switch (instr->opcode) {
+ case aco_opcode::s_waitcnt: {
+ /* we usually should check the chip class for vmcnt/lgkm, but
+ * insert_waitcnt() should fill it in regardless. */
+ unsigned vmcnt = (imm & 0xF) | ((imm & (0x3 << 14)) >> 10);
+ if (vmcnt != 63) fprintf(output, " vmcnt(%d)", vmcnt);
+ if (((imm >> 4) & 0x7) < 0x7) fprintf(output, " expcnt(%d)", (imm >> 4) & 0x7);
+ if (((imm >> 8) & 0x3F) < 0x3F) fprintf(output, " lgkmcnt(%d)", (imm >> 8) & 0x3F);
+ break;
+ }
+ case aco_opcode::s_endpgm:
+ case aco_opcode::s_endpgm_saved:
+ case aco_opcode::s_endpgm_ordered_ps_done:
+ case aco_opcode::s_wakeup:
+ case aco_opcode::s_barrier:
+ case aco_opcode::s_icache_inv:
+ case aco_opcode::s_ttracedata:
+ case aco_opcode::s_set_gpr_idx_off: {
+ break;
+ }
+ default: {
+ if (imm)
+ fprintf(output, " imm:%u", imm);
+ break;
+ }
+ }
+ if (sopp->block != -1)
+ fprintf(output, " block:BB%d", sopp->block);
+ break;
+ }
+ case Format::SMEM: {
+ SMEM_instruction* smem = static_cast<SMEM_instruction*>(instr);
+ if (smem->glc)
+ fprintf(output, " glc");
+ if (smem->nv)
+ fprintf(output, " nv");
+ print_barrier_reorder(smem->can_reorder, smem->barrier, output);
+ break;
+ }
+ case Format::VINTRP: {
+ Interp_instruction* vintrp = static_cast<Interp_instruction*>(instr);
+ fprintf(output, " attr%d.%c", vintrp->attribute, "xyzw"[vintrp->component]);
+ break;
+ }
+ case Format::DS: {
+ DS_instruction* ds = static_cast<DS_instruction*>(instr);
+ if (ds->offset0)
+ fprintf(output, " offset0:%u", ds->offset0);
+ if (ds->offset1)
+ fprintf(output, " offset1:%u", ds->offset1);
+ if (ds->gds)
+ fprintf(output, " gds");
+ break;
+ }
+ case Format::MUBUF: {
+ MUBUF_instruction* mubuf = static_cast<MUBUF_instruction*>(instr);
+ if (mubuf->offset)
+ fprintf(output, " offset:%u", mubuf->offset);
+ if (mubuf->offen)
+ fprintf(output, " offen");
+ if (mubuf->idxen)
+ fprintf(output, " idxen");
+ if (mubuf->glc)
+ fprintf(output, " glc");
+ if (mubuf->slc)
+ fprintf(output, " slc");
+ if (mubuf->tfe)
+ fprintf(output, " tfe");
+ if (mubuf->lds)
+ fprintf(output, " lds");
+ if (mubuf->disable_wqm)
+ fprintf(output, " disable_wqm");
+ print_barrier_reorder(mubuf->can_reorder, mubuf->barrier, output);
+ break;
+ }
+ case Format::MIMG: {
+ MIMG_instruction* mimg = static_cast<MIMG_instruction*>(instr);
+ unsigned identity_dmask = !instr->definitions.empty() ?
+ (1 << instr->definitions[0].size()) - 1 :
+ 0xf;
+ if ((mimg->dmask & identity_dmask) != identity_dmask)
+ fprintf(output, " dmask:%s%s%s%s",
+ mimg->dmask & 0x1 ? "x" : "",
+ mimg->dmask & 0x2 ? "y" : "",
+ mimg->dmask & 0x4 ? "z" : "",
+ mimg->dmask & 0x8 ? "w" : "");
+ if (mimg->unrm)
+ fprintf(output, " unrm");
+ if (mimg->glc)
+ fprintf(output, " glc");
+ if (mimg->slc)
+ fprintf(output, " slc");
+ if (mimg->tfe)
+ fprintf(output, " tfe");
+ if (mimg->da)
+ fprintf(output, " da");
+ if (mimg->lwe)
+ fprintf(output, " lwe");
+ if (mimg->r128 || mimg->a16)
+ fprintf(output, " r128/a16");
+ if (mimg->d16)
+ fprintf(output, " d16");
+ if (mimg->disable_wqm)
+ fprintf(output, " disable_wqm");
+ print_barrier_reorder(mimg->can_reorder, mimg->barrier, output);
+ break;
+ }
+ case Format::EXP: {
+ Export_instruction* exp = static_cast<Export_instruction*>(instr);
+ unsigned identity_mask = exp->compressed ? 0x5 : 0xf;
+ if ((exp->enabled_mask & identity_mask) != identity_mask)
+ fprintf(output, " en:%c%c%c%c",
+ exp->enabled_mask & 0x1 ? 'r' : '*',
+ exp->enabled_mask & 0x2 ? 'g' : '*',
+ exp->enabled_mask & 0x4 ? 'b' : '*',
+ exp->enabled_mask & 0x8 ? 'a' : '*');
+ if (exp->compressed)
+ fprintf(output, " compr");
+ if (exp->done)
+ fprintf(output, " done");
+ if (exp->valid_mask)
+ fprintf(output, " vm");
+
+ if (exp->dest <= V_008DFC_SQ_EXP_MRT + 7)
+ fprintf(output, " mrt%d", exp->dest - V_008DFC_SQ_EXP_MRT);
+ else if (exp->dest == V_008DFC_SQ_EXP_MRTZ)
+ fprintf(output, " mrtz");
+ else if (exp->dest == V_008DFC_SQ_EXP_NULL)
+ fprintf(output, " null");
+ else if (exp->dest >= V_008DFC_SQ_EXP_POS && exp->dest <= V_008DFC_SQ_EXP_POS + 3)
+ fprintf(output, " pos%d", exp->dest - V_008DFC_SQ_EXP_POS);
+ else if (exp->dest >= V_008DFC_SQ_EXP_PARAM && exp->dest <= V_008DFC_SQ_EXP_PARAM + 31)
+ fprintf(output, " param%d", exp->dest - V_008DFC_SQ_EXP_PARAM);
+ break;
+ }
+ case Format::PSEUDO_BRANCH: {
+ Pseudo_branch_instruction* branch = static_cast<Pseudo_branch_instruction*>(instr);
+ /* Note: BB0 cannot be a branch target */
+ if (branch->target[0] != 0)
+ fprintf(output, " BB%d", branch->target[0]);
+ if (branch->target[1] != 0)
+ fprintf(output, ", BB%d", branch->target[1]);
+ break;
+ }
+ case Format::PSEUDO_REDUCTION: {
+ Pseudo_reduction_instruction* reduce = static_cast<Pseudo_reduction_instruction*>(instr);
+ fprintf(output, " op:%s", reduce_ops[reduce->reduce_op]);
+ if (reduce->cluster_size)
+ fprintf(output, " cluster_size:%u", reduce->cluster_size);
+ break;
+ }
+ case Format::FLAT:
+ case Format::GLOBAL:
+ case Format::SCRATCH: {
+ FLAT_instruction* flat = static_cast<FLAT_instruction*>(instr);
+ if (flat->offset)
+ fprintf(output, " offset:%u", flat->offset);
+ if (flat->glc)
+ fprintf(output, " glc");
+ if (flat->slc)
+ fprintf(output, " slc");
+ if (flat->lds)
+ fprintf(output, " lds");
+ if (flat->nv)
+ fprintf(output, " nv");
+ break;
+ }
+ case Format::MTBUF: {
+ MTBUF_instruction* mtbuf = static_cast<MTBUF_instruction*>(instr);
+ fprintf(output, " dfmt:");
+ switch (mtbuf->dfmt) {
+ case V_008F0C_BUF_DATA_FORMAT_8: fprintf(output, "8"); break;
+ case V_008F0C_BUF_DATA_FORMAT_16: fprintf(output, "16"); break;
+ case V_008F0C_BUF_DATA_FORMAT_8_8: fprintf(output, "8_8"); break;
+ case V_008F0C_BUF_DATA_FORMAT_32: fprintf(output, "32"); break;
+ case V_008F0C_BUF_DATA_FORMAT_16_16: fprintf(output, "16_16"); break;
+ case V_008F0C_BUF_DATA_FORMAT_10_11_11: fprintf(output, "10_11_11"); break;
+ case V_008F0C_BUF_DATA_FORMAT_11_11_10: fprintf(output, "11_11_10"); break;
+ case V_008F0C_BUF_DATA_FORMAT_10_10_10_2: fprintf(output, "10_10_10_2"); break;
+ case V_008F0C_BUF_DATA_FORMAT_2_10_10_10: fprintf(output, "2_10_10_10"); break;
+ case V_008F0C_BUF_DATA_FORMAT_8_8_8_8: fprintf(output, "8_8_8_8"); break;
+ case V_008F0C_BUF_DATA_FORMAT_32_32: fprintf(output, "32_32"); break;
+ case V_008F0C_BUF_DATA_FORMAT_16_16_16_16: fprintf(output, "16_16_16_16"); break;
+ case V_008F0C_BUF_DATA_FORMAT_32_32_32: fprintf(output, "32_32_32"); break;
+ case V_008F0C_BUF_DATA_FORMAT_32_32_32_32: fprintf(output, "32_32_32_32"); break;
+ case V_008F0C_BUF_DATA_FORMAT_RESERVED_15: fprintf(output, "reserved15"); break;
+ }
+ fprintf(output, " nfmt:");
+ switch (mtbuf->nfmt) {
+ case V_008F0C_BUF_NUM_FORMAT_UNORM: fprintf(output, "unorm"); break;
+ case V_008F0C_BUF_NUM_FORMAT_SNORM: fprintf(output, "snorm"); break;
+ case V_008F0C_BUF_NUM_FORMAT_USCALED: fprintf(output, "uscaled"); break;
+ case V_008F0C_BUF_NUM_FORMAT_SSCALED: fprintf(output, "sscaled"); break;
+ case V_008F0C_BUF_NUM_FORMAT_UINT: fprintf(output, "uint"); break;
+ case V_008F0C_BUF_NUM_FORMAT_SINT: fprintf(output, "sint"); break;
+ case V_008F0C_BUF_NUM_FORMAT_SNORM_OGL: fprintf(output, "snorm"); break;
+ case V_008F0C_BUF_NUM_FORMAT_FLOAT: fprintf(output, "float"); break;
+ }
+ if (mtbuf->offset)
+ fprintf(output, " offset:%u", mtbuf->offset);
+ if (mtbuf->offen)
+ fprintf(output, " offen");
+ if (mtbuf->idxen)
+ fprintf(output, " idxen");
+ if (mtbuf->glc)
+ fprintf(output, " glc");
+ if (mtbuf->slc)
+ fprintf(output, " slc");
+ if (mtbuf->tfe)
+ fprintf(output, " tfe");
+ if (mtbuf->disable_wqm)
+ fprintf(output, " disable_wqm");
+ print_barrier_reorder(mtbuf->can_reorder, mtbuf->barrier, output);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ if (instr->isVOP3()) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr);
+ switch (vop3->omod) {
+ case 1:
+ fprintf(output, " *2");
+ break;
+ case 2:
+ fprintf(output, " *4");
+ break;
+ case 3:
+ fprintf(output, " *0.5");
+ break;
+ }
+ if (vop3->clamp)
+ fprintf(output, " clamp");
+ } else if (instr->isDPP()) {
+ DPP_instruction* dpp = static_cast<DPP_instruction*>(instr);
+ if (dpp->dpp_ctrl <= 0xff) {
+ fprintf(output, " quad_perm:[%d,%d,%d,%d]",
+ dpp->dpp_ctrl & 0x3, (dpp->dpp_ctrl >> 2) & 0x3,
+ (dpp->dpp_ctrl >> 4) & 0x3, (dpp->dpp_ctrl >> 6) & 0x3);
+ } else if (dpp->dpp_ctrl >= 0x101 && dpp->dpp_ctrl <= 0x10f) {
+ fprintf(output, " row_shl:%d", dpp->dpp_ctrl & 0xf);
+ } else if (dpp->dpp_ctrl >= 0x111 && dpp->dpp_ctrl <= 0x11f) {
+ fprintf(output, " row_shr:%d", dpp->dpp_ctrl & 0xf);
+ } else if (dpp->dpp_ctrl >= 0x121 && dpp->dpp_ctrl <= 0x12f) {
+ fprintf(output, " row_ror:%d", dpp->dpp_ctrl & 0xf);
+ } else if (dpp->dpp_ctrl == dpp_wf_sl1) {
+ fprintf(output, " wave_shl:1");
+ } else if (dpp->dpp_ctrl == dpp_wf_rl1) {
+ fprintf(output, " wave_rol:1");
+ } else if (dpp->dpp_ctrl == dpp_wf_sr1) {
+ fprintf(output, " wave_shr:1");
+ } else if (dpp->dpp_ctrl == dpp_wf_rr1) {
+ fprintf(output, " wave_ror:1");
+ } else if (dpp->dpp_ctrl == dpp_row_mirror) {
+ fprintf(output, " row_mirror");
+ } else if (dpp->dpp_ctrl == dpp_row_half_mirror) {
+ fprintf(output, " row_half_mirror");
+ } else if (dpp->dpp_ctrl == dpp_row_bcast15) {
+ fprintf(output, " row_bcast:15");
+ } else if (dpp->dpp_ctrl == dpp_row_bcast31) {
+ fprintf(output, " row_bcast:31");
+ } else {
+ fprintf(output, " dpp_ctrl:0x%.3x", dpp->dpp_ctrl);
+ }
+ if (dpp->row_mask != 0xf)
+ fprintf(output, " row_mask:0x%.1x", dpp->row_mask);
+ if (dpp->bank_mask != 0xf)
+ fprintf(output, " bank_mask:0x%.1x", dpp->bank_mask);
+ if (dpp->bound_ctrl)
+ fprintf(output, " bound_ctrl:1");
+ } else if ((int)instr->format & (int)Format::SDWA) {
+ fprintf(output, " (printing unimplemented)");
+ }
+}
+
+void aco_print_instr(struct Instruction *instr, FILE *output)
+{
+ if (!instr->definitions.empty()) {
+ for (unsigned i = 0; i < instr->definitions.size(); ++i) {
+ print_definition(&instr->definitions[i], output);
+ if (i + 1 != instr->definitions.size())
+ fprintf(output, ", ");
+ }
+ fprintf(output, " = ");
+ }
+ fprintf(output, "%s", instr_info.name[(int)instr->opcode]);
+ if (instr->operands.size()) {
+ bool abs[instr->operands.size()];
+ bool neg[instr->operands.size()];
+ if ((int)instr->format & (int)Format::VOP3A) {
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr);
+ for (unsigned i = 0; i < instr->operands.size(); ++i) {
+ abs[i] = vop3->abs[i];
+ neg[i] = vop3->neg[i];
+ }
+ } else if (instr->isDPP()) {
+ DPP_instruction* dpp = static_cast<DPP_instruction*>(instr);
+ assert(instr->operands.size() <= 2);
+ for (unsigned i = 0; i < instr->operands.size(); ++i) {
+ abs[i] = dpp->abs[i];
+ neg[i] = dpp->neg[i];
+ }
+ } else {
+ for (unsigned i = 0; i < instr->operands.size(); ++i) {
+ abs[i] = false;
+ neg[i] = false;
+ }
+ }
+ for (unsigned i = 0; i < instr->operands.size(); ++i) {
+ if (i)
+ fprintf(output, ", ");
+ else
+ fprintf(output, " ");
+
+ if (neg[i])
+ fprintf(output, "-");
+ if (abs[i])
+ fprintf(output, "|");
+ print_operand(&instr->operands[i], output);
+ if (abs[i])
+ fprintf(output, "|");
+ }
+ }
+ print_instr_format_specific(instr, output);
+}
+
+static void print_block_kind(uint16_t kind, FILE *output)
+{
+ if (kind & block_kind_uniform)
+ fprintf(output, "uniform, ");
+ if (kind & block_kind_top_level)
+ fprintf(output, "top-level, ");
+ if (kind & block_kind_loop_preheader)
+ fprintf(output, "loop-preheader, ");
+ if (kind & block_kind_loop_header)
+ fprintf(output, "loop-header, ");
+ if (kind & block_kind_loop_exit)
+ fprintf(output, "loop-exit, ");
+ if (kind & block_kind_continue)
+ fprintf(output, "continue, ");
+ if (kind & block_kind_break)
+ fprintf(output, "break, ");
+ if (kind & block_kind_continue_or_break)
+ fprintf(output, "continue_or_break, ");
+ if (kind & block_kind_discard)
+ fprintf(output, "discard, ");
+ if (kind & block_kind_branch)
+ fprintf(output, "branch, ");
+ if (kind & block_kind_merge)
+ fprintf(output, "merge, ");
+ if (kind & block_kind_invert)
+ fprintf(output, "invert, ");
+ if (kind & block_kind_uses_discard_if)
+ fprintf(output, "discard_if, ");
+ if (kind & block_kind_needs_lowering)
+ fprintf(output, "needs_lowering, ");
+}
+
+void aco_print_block(const struct Block* block, FILE *output)
+{
+ fprintf(output, "BB%d\n", block->index);
+ fprintf(output, "/* logical preds: ");
+ for (unsigned pred : block->logical_preds)
+ fprintf(output, "BB%d, ", pred);
+ fprintf(output, "/ linear preds: ");
+ for (unsigned pred : block->linear_preds)
+ fprintf(output, "BB%d, ", pred);
+ fprintf(output, "/ kind: ");
+ print_block_kind(block->kind, output);
+ fprintf(output, "*/\n");
+ for (auto const& instr : block->instructions) {
+ fprintf(output, "\t");
+ aco_print_instr(instr.get(), output);
+ fprintf(output, "\n");
+ }
+}
+
+void aco_print_program(Program *program, FILE *output)
+{
+ for (Block const& block : program->blocks)
+ aco_print_block(&block, output);
+
+ if (program->constant_data.size()) {
+ fprintf(output, "\n/* constant data */\n");
+ for (unsigned i = 0; i < program->constant_data.size(); i += 32) {
+ fprintf(output, "[%06d] ", i);
+ unsigned line_size = std::min<size_t>(program->constant_data.size() - i, 32);
+ for (unsigned j = 0; j < line_size; j += 4) {
+ unsigned size = std::min<size_t>(program->constant_data.size() - (i + j), 4);
+ uint32_t v = 0;
+ memcpy(&v, &program->constant_data[i + j], size);
+ fprintf(output, " %08x", v);
+ }
+ fprintf(output, "\n");
+ }
+ }
+
+ fprintf(output, "\n");
+}
+
+}
diff --git a/src/amd/compiler/aco_reduce_assign.cpp b/src/amd/compiler/aco_reduce_assign.cpp
new file mode 100644
index 00000000000..663a43c539a
--- /dev/null
+++ b/src/amd/compiler/aco_reduce_assign.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+#include "aco_builder.h"
+
+/*
+ * Insert p_linear_start instructions right before RA to correctly allocate
+ * temporaries for reductions that have to disrespect EXEC by executing in
+ * WWM.
+ */
+
+namespace aco {
+
+void setup_reduce_temp(Program* program)
+{
+ unsigned last_top_level_block_idx = 0;
+ unsigned maxSize = 0;
+
+ std::vector<bool> hasReductions(program->blocks.size());
+ for (Block& block : program->blocks) {
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ if (instr->format != Format::PSEUDO_REDUCTION)
+ continue;
+
+ maxSize = MAX2(maxSize, instr->operands[0].size());
+ hasReductions[block.index] = true;
+ }
+ }
+
+ if (maxSize == 0)
+ return;
+
+ assert(maxSize == 1 || maxSize == 2);
+ Temp reduceTmp(0, RegClass(RegType::vgpr, maxSize).as_linear());
+ Temp vtmp(0, RegClass(RegType::vgpr, maxSize).as_linear());
+ int inserted_at = -1;
+ int vtmp_inserted_at = -1;
+ bool reduceTmp_in_loop = false;
+ bool vtmp_in_loop = false;
+
+ for (Block& block : program->blocks) {
+
+ /* insert p_end_linear_vgpr after the outermost loop */
+ if (reduceTmp_in_loop && block.loop_nest_depth == 0) {
+ assert(inserted_at == (int)last_top_level_block_idx);
+
+ aco_ptr<Instruction> end{create_instruction<Instruction>(aco_opcode::p_end_linear_vgpr, Format::PSEUDO, vtmp_in_loop ? 2 : 1, 0)};
+ end->operands[0] = Operand(reduceTmp);
+ if (vtmp_in_loop)
+ end->operands[1] = Operand(vtmp);
+ /* insert after the phis of the loop exit block */
+ std::vector<aco_ptr<Instruction>>::iterator it = block.instructions.begin();
+ while ((*it)->opcode == aco_opcode::p_linear_phi || (*it)->opcode == aco_opcode::p_phi)
+ ++it;
+ block.instructions.insert(it, std::move(end));
+ reduceTmp_in_loop = false;
+ }
+
+ if (block.kind & block_kind_top_level)
+ last_top_level_block_idx = block.index;
+
+ if (!hasReductions[block.index])
+ continue;
+
+ std::vector<aco_ptr<Instruction>>::iterator it;
+ for (it = block.instructions.begin(); it != block.instructions.end(); ++it) {
+ Instruction *instr = (*it).get();
+ if (instr->format != Format::PSEUDO_REDUCTION)
+ continue;
+
+ ReduceOp op = static_cast<Pseudo_reduction_instruction *>(instr)->reduce_op;
+ reduceTmp_in_loop |= block.loop_nest_depth > 0;
+
+ if ((int)last_top_level_block_idx != inserted_at) {
+ reduceTmp = {program->allocateId(), reduceTmp.regClass()};
+ aco_ptr<Pseudo_instruction> create{create_instruction<Pseudo_instruction>(aco_opcode::p_start_linear_vgpr, Format::PSEUDO, 0, 1)};
+ create->definitions[0] = Definition(reduceTmp);
+ /* find the right place to insert this definition */
+ if (last_top_level_block_idx == block.index) {
+ /* insert right before the current instruction */
+ it = block.instructions.insert(it, std::move(create));
+ it++;
+ /* inserted_at is intentionally not updated here, so later blocks
+ * would insert at the end instead of using this one. */
+ } else {
+ assert(last_top_level_block_idx < block.index);
+ /* insert before the branch at last top level block */
+ std::vector<aco_ptr<Instruction>>& instructions = program->blocks[last_top_level_block_idx].instructions;
+ instructions.insert(std::next(instructions.begin(), instructions.size() - 1), std::move(create));
+ inserted_at = last_top_level_block_idx;
+ }
+ }
+
+ /* same as before, except for the vector temporary instead of the reduce temporary */
+ bool need_vtmp = op == imul32 || op == fadd64 || op == fmul64 ||
+ op == fmin64 || op == fmax64;
+
+ need_vtmp |= static_cast<Pseudo_reduction_instruction *>(instr)->cluster_size == 32;
+ vtmp_in_loop |= need_vtmp && block.loop_nest_depth > 0;
+ if (need_vtmp && (int)last_top_level_block_idx != vtmp_inserted_at) {
+ vtmp = {program->allocateId(), vtmp.regClass()};
+ aco_ptr<Pseudo_instruction> create{create_instruction<Pseudo_instruction>(aco_opcode::p_start_linear_vgpr, Format::PSEUDO, 0, 1)};
+ create->definitions[0] = Definition(vtmp);
+ if (last_top_level_block_idx == block.index) {
+ it = block.instructions.insert(it, std::move(create));
+ it++;
+ } else {
+ assert(last_top_level_block_idx < block.index);
+ std::vector<aco_ptr<Instruction>>& instructions = program->blocks[last_top_level_block_idx].instructions;
+ instructions.insert(std::next(instructions.begin(), instructions.size() - 1), std::move(create));
+ vtmp_inserted_at = last_top_level_block_idx;
+ }
+ }
+
+ instr->operands[1] = Operand(reduceTmp);
+ if (need_vtmp)
+ instr->operands[2] = Operand(vtmp);
+
+ /* scalar temporary */
+ Builder bld(program);
+ instr->definitions[1] = bld.def(s2);
+
+ /* scalar identity temporary */
+ if (instr->opcode == aco_opcode::p_exclusive_scan &&
+ (op == imin32 || op == imin64 ||
+ op == imax32 || op == imax64 ||
+ op == fmin32 || op == fmin64 ||
+ op == fmax32 || op == fmax64 ||
+ op == fmul64)) {
+ instr->definitions[2] = bld.def(RegClass(RegType::sgpr, instr->operands[0].size()));
+ }
+
+ /* vcc clobber */
+ if (op == iadd32 && program->chip_class < GFX9)
+ instr->definitions[4] = Definition(vcc, s2);
+ }
+ }
+}
+
+};
+
diff --git a/src/amd/compiler/aco_register_allocation.cpp b/src/amd/compiler/aco_register_allocation.cpp
new file mode 100644
index 00000000000..d55f1febc65
--- /dev/null
+++ b/src/amd/compiler/aco_register_allocation.cpp
@@ -0,0 +1,1924 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors:
+ * Daniel Schürmann ([email protected])
+ * Bas Nieuwenhuizen ([email protected])
+ *
+ */
+
+#include <algorithm>
+#include <map>
+#include <unordered_map>
+#include <functional>
+
+#include "aco_ir.h"
+#include "sid.h"
+
+namespace aco {
+namespace {
+
+struct ra_ctx {
+ std::bitset<512> war_hint;
+ Program* program;
+ std::unordered_map<unsigned, std::pair<PhysReg, RegClass>> assignments;
+ std::map<unsigned, Temp> orig_names;
+ unsigned max_used_sgpr = 0;
+ unsigned max_used_vgpr = 0;
+ std::bitset<64> defs_done; /* see MAX_ARGS in aco_instruction_selection_setup.cpp */
+
+ ra_ctx(Program* program) : program(program) {}
+};
+
+
+/* helper function for debugging */
+#if 0
+void print_regs(ra_ctx& ctx, bool vgprs, std::array<uint32_t, 512>& reg_file)
+{
+ unsigned max = vgprs ? ctx.program->max_reg_demand.vgpr : ctx.program->max_reg_demand.sgpr;
+ unsigned lb = vgprs ? 256 : 0;
+ unsigned ub = lb + max;
+ char reg_char = vgprs ? 'v' : 's';
+
+ /* print markers */
+ printf(" ");
+ for (unsigned i = lb; i < ub; i += 3) {
+ printf("%.2u ", i - lb);
+ }
+ printf("\n");
+
+ /* print usage */
+ printf("%cgprs: ", reg_char);
+ unsigned free_regs = 0;
+ unsigned prev = 0;
+ bool char_select = false;
+ for (unsigned i = lb; i < ub; i++) {
+ if (reg_file[i] == 0xFFFF) {
+ printf("~");
+ } else if (reg_file[i]) {
+ if (reg_file[i] != prev) {
+ prev = reg_file[i];
+ char_select = !char_select;
+ }
+ printf(char_select ? "#" : "@");
+ } else {
+ free_regs++;
+ printf(".");
+ }
+ }
+ printf("\n");
+
+ printf("%u/%u used, %u/%u free\n", max - free_regs, max, free_regs, max);
+
+ /* print assignments */
+ prev = 0;
+ unsigned size = 0;
+ for (unsigned i = lb; i < ub; i++) {
+ if (reg_file[i] != prev) {
+ if (prev && size > 1)
+ printf("-%d]\n", i - 1 - lb);
+ else if (prev)
+ printf("]\n");
+ prev = reg_file[i];
+ if (prev && prev != 0xFFFF) {
+ if (ctx.orig_names.count(reg_file[i]) && ctx.orig_names[reg_file[i]].id() != reg_file[i])
+ printf("%%%u (was %%%d) = %c[%d", reg_file[i], ctx.orig_names[reg_file[i]].id(), reg_char, i - lb);
+ else
+ printf("%%%u = %c[%d", reg_file[i], reg_char, i - lb);
+ }
+ size = 1;
+ } else {
+ size++;
+ }
+ }
+ if (prev && size > 1)
+ printf("-%d]\n", ub - lb - 1);
+ else if (prev)
+ printf("]\n");
+}
+#endif
+
+
+void adjust_max_used_regs(ra_ctx& ctx, RegClass rc, unsigned reg)
+{
+ unsigned max_addressible_sgpr = ctx.program->sgpr_limit;
+ unsigned size = rc.size();
+ if (rc.type() == RegType::vgpr) {
+ assert(reg >= 256);
+ unsigned hi = reg - 256 + size - 1;
+ ctx.max_used_vgpr = std::max(ctx.max_used_vgpr, hi);
+ } else if (reg + rc.size() <= max_addressible_sgpr) {
+ unsigned hi = reg + size - 1;
+ ctx.max_used_sgpr = std::max(ctx.max_used_sgpr, std::min(hi, max_addressible_sgpr));
+ }
+}
+
+
+void update_renames(ra_ctx& ctx, std::array<uint32_t, 512>& reg_file,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ aco_ptr<Instruction>& instr)
+{
+ /* allocate id's and rename operands: this is done transparently here */
+ for (std::pair<Operand, Definition>& copy : parallelcopies) {
+ /* the definitions with id are not from this function and already handled */
+ if (copy.second.isTemp())
+ continue;
+
+ // FIXME: if a definition got moved, change the target location and remove the parallelcopy
+ copy.second.setTemp(Temp(ctx.program->allocateId(), copy.second.regClass()));
+ ctx.assignments[copy.second.tempId()] = {copy.second.physReg(), copy.second.regClass()};
+ for (unsigned i = copy.second.physReg().reg; i < copy.second.physReg() + copy.second.size(); i++)
+ reg_file[i] = copy.second.tempId();
+ /* check if we moved an operand */
+ for (Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ if (op.tempId() == copy.first.tempId()) {
+ bool omit_renaming = instr->opcode == aco_opcode::p_create_vector && !op.isKill();
+ for (std::pair<Operand, Definition>& pc : parallelcopies) {
+ PhysReg def_reg = pc.second.physReg();
+ omit_renaming &= def_reg > copy.first.physReg() ?
+ (copy.first.physReg() + copy.first.size() <= def_reg.reg) :
+ (def_reg + pc.second.size() <= copy.first.physReg().reg);
+ }
+ if (omit_renaming)
+ continue;
+ op.setTemp(copy.second.getTemp());
+ op.setFixed(copy.second.physReg());
+ }
+ }
+ }
+}
+
+std::pair<PhysReg, bool> get_reg_simple(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ uint32_t lb, uint32_t ub,
+ uint32_t size, uint32_t stride,
+ RegClass rc)
+{
+ /* best fit algorithm: find the smallest gap to fit in the variable */
+ if (stride == 1) {
+ unsigned best_pos = 0xFFFF;
+ unsigned gap_size = 0xFFFF;
+ unsigned next_pos = 0xFFFF;
+
+ for (unsigned current_reg = lb; current_reg < ub; current_reg++) {
+ if (reg_file[current_reg] != 0 || ctx.war_hint[current_reg]) {
+ if (next_pos == 0xFFFF)
+ continue;
+
+ /* check if the variable fits */
+ if (next_pos + size > current_reg) {
+ next_pos = 0xFFFF;
+ continue;
+ }
+
+ /* check if the tested gap is smaller */
+ if (current_reg - next_pos < gap_size) {
+ best_pos = next_pos;
+ gap_size = current_reg - next_pos;
+ }
+ next_pos = 0xFFFF;
+ continue;
+ }
+
+ if (next_pos == 0xFFFF)
+ next_pos = current_reg;
+ }
+
+ /* final check */
+ if (next_pos != 0xFFFF &&
+ next_pos + size <= ub &&
+ ub - next_pos < gap_size) {
+ best_pos = next_pos;
+ gap_size = ub - next_pos;
+ }
+ if (best_pos != 0xFFFF) {
+ adjust_max_used_regs(ctx, rc, best_pos);
+ return {PhysReg{best_pos}, true};
+ }
+ return {{}, false};
+ }
+
+ bool found = false;
+ unsigned reg_lo = lb;
+ unsigned reg_hi = lb + size - 1;
+ while (!found && reg_lo + size <= ub) {
+ if (reg_file[reg_lo] != 0) {
+ reg_lo += stride;
+ continue;
+ }
+ reg_hi = reg_lo + size - 1;
+ found = true;
+ for (unsigned reg = reg_lo + 1; found && reg <= reg_hi; reg++) {
+ if (reg_file[reg] != 0 || ctx.war_hint[reg])
+ found = false;
+ }
+ if (found) {
+ adjust_max_used_regs(ctx, rc, reg_lo);
+ return {PhysReg{reg_lo}, true};
+ }
+
+ reg_lo += stride;
+ }
+
+ return {{}, false};
+}
+
+bool get_regs_for_copies(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ std::set<std::pair<unsigned, unsigned>> vars,
+ uint32_t lb, uint32_t ub,
+ aco_ptr<Instruction>& instr,
+ uint32_t def_reg_lo,
+ uint32_t def_reg_hi)
+{
+
+ /* variables are sorted from small sized to large */
+ /* NOTE: variables are also sorted by ID. this only affects a very small number of shaders slightly though. */
+ for (std::set<std::pair<unsigned, unsigned>>::reverse_iterator it = vars.rbegin(); it != vars.rend(); ++it) {
+ unsigned id = it->second;
+ std::pair<PhysReg, RegClass> var = ctx.assignments[id];
+ uint32_t size = it->first;
+ uint32_t stride = 1;
+ if (var.second.type() == RegType::sgpr) {
+ if (size == 2)
+ stride = 2;
+ if (size > 3)
+ stride = 4;
+ }
+
+ /* check if this is a dead operand, then we can re-use the space from the definition */
+ bool is_dead_operand = false;
+ for (unsigned i = 0; !is_phi(instr) && !is_dead_operand && i < instr->operands.size(); i++) {
+ if (instr->operands[i].isTemp() && instr->operands[i].isKill() && instr->operands[i].tempId() == id)
+ is_dead_operand = true;
+ }
+
+ std::pair<PhysReg, bool> res;
+ if (is_dead_operand) {
+ if (instr->opcode == aco_opcode::p_create_vector) {
+ for (unsigned i = 0, offset = 0; i < instr->operands.size(); offset += instr->operands[i].size(), i++) {
+ if (instr->operands[i].isTemp() && instr->operands[i].tempId() == id) {
+ for (unsigned j = 0; j < size; j++)
+ assert(reg_file[def_reg_lo + offset + j] == 0);
+ res = {PhysReg{def_reg_lo + offset}, true};
+ break;
+ }
+ }
+ } else {
+ res = get_reg_simple(ctx, reg_file, def_reg_lo, def_reg_hi + 1, size, stride, var.second);
+ }
+ } else {
+ res = get_reg_simple(ctx, reg_file, lb, def_reg_lo, size, stride, var.second);
+ if (!res.second) {
+ unsigned lb = (def_reg_hi + stride) & ~(stride - 1);
+ res = get_reg_simple(ctx, reg_file, lb, ub, size, stride, var.second);
+ }
+ }
+
+ if (res.second) {
+ /* mark the area as blocked */
+ for (unsigned i = res.first.reg; i < res.first + size; i++)
+ reg_file[i] = 0xFFFFFFFF;
+ /* create parallelcopy pair (without definition id) */
+ Temp tmp = Temp(id, var.second);
+ Operand pc_op = Operand(tmp);
+ pc_op.setFixed(var.first);
+ Definition pc_def = Definition(res.first, pc_op.regClass());
+ parallelcopies.emplace_back(pc_op, pc_def);
+ continue;
+ }
+
+ unsigned best_pos = lb;
+ unsigned num_moves = 0xFF;
+ unsigned num_vars = 0;
+
+ /* we use a sliding window to find potential positions */
+ unsigned reg_lo = lb;
+ unsigned reg_hi = lb + size - 1;
+ for (reg_lo = lb, reg_hi = lb + size - 1; reg_hi < ub; reg_lo += stride, reg_hi += stride) {
+ if (!is_dead_operand && ((reg_lo >= def_reg_lo && reg_lo <= def_reg_hi) ||
+ (reg_hi >= def_reg_lo && reg_hi <= def_reg_hi)))
+ continue;
+
+ /* second, check that we have at most k=num_moves elements in the window
+ * and no element is larger than the currently processed one */
+ unsigned k = 0;
+ unsigned n = 0;
+ unsigned last_var = 0;
+ bool found = true;
+ for (unsigned j = reg_lo; found && j <= reg_hi; j++) {
+ if (reg_file[j] == 0 || reg_file[j] == last_var)
+ continue;
+
+ /* 0xFFFF signals that this area is already blocked! */
+ if (reg_file[j] == 0xFFFFFFFF || k > num_moves) {
+ found = false;
+ break;
+ }
+ /* we cannot split live ranges of linear vgprs */
+ if (ctx.assignments[reg_file[j]].second & (1 << 6)) {
+ found = false;
+ break;
+ }
+ bool is_kill = false;
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isKill() && op.tempId() == reg_file[j]) {
+ is_kill = true;
+ break;
+ }
+ }
+ if (!is_kill && ctx.assignments[reg_file[j]].second.size() >= size) {
+ found = false;
+ break;
+ }
+
+ k += ctx.assignments[reg_file[j]].second.size();
+ last_var = reg_file[j];
+ n++;
+ if (k > num_moves || (k == num_moves && n <= num_vars)) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ best_pos = reg_lo;
+ num_moves = k;
+ num_vars = n;
+ }
+ }
+
+ /* FIXME: we messed up and couldn't find space for the variables to be copied */
+ if (num_moves == 0xFF)
+ return false;
+
+ reg_lo = best_pos;
+ reg_hi = best_pos + size - 1;
+
+ /* collect variables and block reg file */
+ std::set<std::pair<unsigned, unsigned>> new_vars;
+ for (unsigned j = reg_lo; j <= reg_hi; j++) {
+ if (reg_file[j] != 0) {
+ unsigned size = ctx.assignments[reg_file[j]].second.size();
+ unsigned id = reg_file[j];
+ new_vars.emplace(size, id);
+ for (unsigned k = 0; k < size; k++)
+ reg_file[ctx.assignments[id].first + k] = 0;
+ }
+ }
+
+ /* mark the area as blocked */
+ for (unsigned i = reg_lo; i <= reg_hi; i++)
+ reg_file[i] = 0xFFFFFFFF;
+
+ if (!get_regs_for_copies(ctx, reg_file, parallelcopies, new_vars, lb, ub, instr, def_reg_lo, def_reg_hi))
+ return false;
+
+ adjust_max_used_regs(ctx, var.second, reg_lo);
+
+ /* create parallelcopy pair (without definition id) */
+ Temp tmp = Temp(id, var.second);
+ Operand pc_op = Operand(tmp);
+ pc_op.setFixed(var.first);
+ Definition pc_def = Definition(PhysReg{reg_lo}, pc_op.regClass());
+ parallelcopies.emplace_back(pc_op, pc_def);
+ }
+
+ return true;
+}
+
+
+std::pair<PhysReg, bool> get_reg_impl(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ uint32_t lb, uint32_t ub,
+ uint32_t size, uint32_t stride,
+ RegClass rc,
+ aco_ptr<Instruction>& instr)
+{
+ unsigned regs_free = 0;
+ /* check how many free regs we have */
+ for (unsigned j = lb; j < ub; j++) {
+ if (reg_file[j] == 0)
+ regs_free++;
+ }
+
+ /* mark and count killed operands */
+ unsigned killed_ops = 0;
+ for (unsigned j = 0; !is_phi(instr) && j < instr->operands.size(); j++) {
+ if (instr->operands[j].isTemp() &&
+ instr->operands[j].isFirstKill() &&
+ instr->operands[j].physReg() >= lb &&
+ instr->operands[j].physReg() < ub) {
+ assert(instr->operands[j].isFixed());
+ assert(reg_file[instr->operands[j].physReg().reg] == 0);
+ for (unsigned k = 0; k < instr->operands[j].size(); k++)
+ reg_file[instr->operands[j].physReg() + k] = 0xFFFFFFFF;
+ killed_ops += instr->operands[j].getTemp().size();
+ }
+ }
+
+ assert(regs_free >= size);
+ /* we might have to move dead operands to dst in order to make space */
+ unsigned op_moves = 0;
+
+ if (size > (regs_free - killed_ops))
+ op_moves = size - (regs_free - killed_ops);
+
+ /* find the best position to place the definition */
+ unsigned best_pos = lb;
+ unsigned num_moves = 0xFF;
+ unsigned num_vars = 0;
+
+ /* we use a sliding window to check potential positions */
+ unsigned reg_lo = lb;
+ unsigned reg_hi = lb + size - 1;
+ for (reg_lo = lb, reg_hi = lb + size - 1; reg_hi < ub; reg_lo += stride, reg_hi += stride) {
+ /* first check the edges: this is what we have to fix to allow for num_moves > size */
+ if (reg_lo > lb && reg_file[reg_lo] != 0 && reg_file[reg_lo] == reg_file[reg_lo - 1])
+ continue;
+ if (reg_hi < ub - 1 && reg_file[reg_hi] != 0 && reg_file[reg_hi] == reg_file[reg_hi + 1])
+ continue;
+
+ /* second, check that we have at most k=num_moves elements in the window
+ * and no element is larger than the currently processed one */
+ unsigned k = op_moves;
+ unsigned n = 0;
+ unsigned remaining_op_moves = op_moves;
+ unsigned last_var = 0;
+ bool found = true;
+ bool aligned = rc == RegClass::v4 && reg_lo % 4 == 0;
+ for (unsigned j = reg_lo; found && j <= reg_hi; j++) {
+ if (reg_file[j] == 0 || reg_file[j] == last_var)
+ continue;
+
+ /* dead operands effectively reduce the number of estimated moves */
+ if (remaining_op_moves && reg_file[j] == 0xFFFFFFFF) {
+ k--;
+ remaining_op_moves--;
+ continue;
+ }
+
+ if (ctx.assignments[reg_file[j]].second.size() >= size) {
+ found = false;
+ break;
+ }
+
+
+ /* we cannot split live ranges of linear vgprs */
+ if (ctx.assignments[reg_file[j]].second & (1 << 6)) {
+ found = false;
+ break;
+ }
+
+ k += ctx.assignments[reg_file[j]].second.size();
+ n++;
+ last_var = reg_file[j];
+ }
+
+ if (!found || k > num_moves)
+ continue;
+ if (k == num_moves && n < num_vars)
+ continue;
+ if (!aligned && k == num_moves && n == num_vars)
+ continue;
+
+ if (found) {
+ best_pos = reg_lo;
+ num_moves = k;
+ num_vars = n;
+ }
+ }
+
+ if (num_moves == 0xFF) {
+ /* remove killed operands from reg_file once again */
+ for (unsigned i = 0; !is_phi(instr) && i < instr->operands.size(); i++) {
+ if (instr->operands[i].isTemp() && instr->operands[i].isFirstKill()) {
+ for (unsigned k = 0; k < instr->operands[i].getTemp().size(); k++)
+ reg_file[instr->operands[i].physReg() + k] = 0;
+ }
+ }
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Definition def = instr->definitions[i];
+ if (def.isTemp() && def.isFixed() && ctx.defs_done.test(i)) {
+ for (unsigned k = 0; k < def.getTemp().size(); k++)
+ reg_file[def.physReg() + k] = def.tempId();
+ }
+ }
+ return {{}, false};
+ }
+
+ std::array<uint32_t, 512> register_file = reg_file;
+
+ /* now, we figured the placement for our definition */
+ std::set<std::pair<unsigned, unsigned>> vars;
+ for (unsigned j = best_pos; j < best_pos + size; j++) {
+ if (reg_file[j] != 0xFFFFFFFF && reg_file[j] != 0)
+ vars.emplace(ctx.assignments[reg_file[j]].second.size(), reg_file[j]);
+ reg_file[j] = 0;
+ }
+
+ if (instr->opcode == aco_opcode::p_create_vector) {
+ /* move killed operands which aren't yet at the correct position */
+ for (unsigned i = 0, offset = 0; i < instr->operands.size(); offset += instr->operands[i].size(), i++) {
+ if (instr->operands[i].isTemp() && instr->operands[i].isFirstKill() &&
+ instr->operands[i].getTemp().type() == rc.type()) {
+
+ if (instr->operands[i].physReg() != best_pos + offset) {
+ vars.emplace(instr->operands[i].size(), instr->operands[i].tempId());
+ for (unsigned j = 0; j < instr->operands[i].size(); j++)
+ reg_file[instr->operands[i].physReg() + j] = 0;
+ } else {
+ for (unsigned j = 0; j < instr->operands[i].size(); j++)
+ reg_file[instr->operands[i].physReg() + j] = instr->operands[i].tempId();
+ }
+ }
+ }
+ } else {
+ /* re-enable the killed operands */
+ for (unsigned j = 0; !is_phi(instr) && j < instr->operands.size(); j++) {
+ if (instr->operands[j].isTemp() && instr->operands[j].isFirstKill()) {
+ for (unsigned k = 0; k < instr->operands[j].getTemp().size(); k++)
+ reg_file[instr->operands[j].physReg() + k] = instr->operands[j].tempId();
+ }
+ }
+ }
+
+ std::vector<std::pair<Operand, Definition>> pc;
+ if (!get_regs_for_copies(ctx, reg_file, pc, vars, lb, ub, instr, best_pos, best_pos + size - 1)) {
+ reg_file = std::move(register_file);
+ /* remove killed operands from reg_file once again */
+ if (!is_phi(instr)) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill()) {
+ for (unsigned k = 0; k < op.getTemp().size(); k++)
+ reg_file[op.physReg() + k] = 0;
+ }
+ }
+ }
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Definition& def = instr->definitions[i];
+ if (def.isTemp() && def.isFixed() && ctx.defs_done.test(i)) {
+ for (unsigned k = 0; k < def.getTemp().size(); k++)
+ reg_file[def.physReg() + k] = def.tempId();
+ }
+ }
+ return {{}, false};
+ }
+
+ parallelcopies.insert(parallelcopies.end(), pc.begin(), pc.end());
+
+ /* we set the definition regs == 0. the actual caller is responsible for correct setting */
+ for (unsigned i = 0; i < size; i++)
+ reg_file[best_pos + i] = 0;
+
+ update_renames(ctx, reg_file, parallelcopies, instr);
+
+ /* remove killed operands from reg_file once again */
+ for (unsigned i = 0; !is_phi(instr) && i < instr->operands.size(); i++) {
+ if (!instr->operands[i].isTemp() || !instr->operands[i].isFixed())
+ continue;
+ assert(!instr->operands[i].isUndefined());
+ if (instr->operands[i].isFirstKill()) {
+ for (unsigned j = 0; j < instr->operands[i].getTemp().size(); j++)
+ reg_file[instr->operands[i].physReg() + j] = 0;
+ }
+ }
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Definition def = instr->definitions[i];
+ if (def.isTemp() && def.isFixed() && ctx.defs_done.test(i)) {
+ for (unsigned k = 0; k < def.getTemp().size(); k++)
+ reg_file[def.physReg() + k] = def.tempId();
+ }
+ }
+
+ adjust_max_used_regs(ctx, rc, best_pos);
+ return {PhysReg{best_pos}, true};
+}
+
+PhysReg get_reg(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ RegClass rc,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ aco_ptr<Instruction>& instr)
+{
+ uint32_t size = rc.size();
+ uint32_t stride = 1;
+ uint32_t lb, ub;
+ if (rc.type() == RegType::vgpr) {
+ lb = 256;
+ ub = 256 + ctx.program->max_reg_demand.vgpr;
+ } else {
+ lb = 0;
+ ub = ctx.program->max_reg_demand.sgpr;
+ if (size == 2)
+ stride = 2;
+ else if (size >= 4)
+ stride = 4;
+ }
+
+ std::pair<PhysReg, bool> res = {{}, false};
+ /* try to find space without live-range splits */
+ if (rc.type() == RegType::vgpr && (size == 4 || size == 8))
+ res = get_reg_simple(ctx, reg_file, lb, ub, size, 4, rc);
+ if (!res.second)
+ res = get_reg_simple(ctx, reg_file, lb, ub, size, stride, rc);
+ if (res.second)
+ return res.first;
+
+ /* try to find space with live-range splits */
+ res = get_reg_impl(ctx, reg_file, parallelcopies, lb, ub, size, stride, rc, instr);
+
+ if (res.second)
+ return res.first;
+
+ unsigned regs_free = 0;
+ for (unsigned i = lb; i < ub; i++) {
+ if (!reg_file[i])
+ regs_free++;
+ }
+
+ /* We should only fail here because keeping under the limit would require
+ * too many moves. */
+ assert(regs_free >= size);
+
+ /* try using more registers */
+ uint16_t max_addressible_sgpr = ctx.program->sgpr_limit;
+ if (rc.type() == RegType::vgpr && ctx.program->max_reg_demand.vgpr < 256) {
+ update_vgpr_sgpr_demand(ctx.program, RegisterDemand(ctx.program->max_reg_demand.vgpr + 1, ctx.program->max_reg_demand.sgpr));
+ return get_reg(ctx, reg_file, rc, parallelcopies, instr);
+ } else if (rc.type() == RegType::sgpr && ctx.program->max_reg_demand.sgpr < max_addressible_sgpr) {
+ update_vgpr_sgpr_demand(ctx.program, RegisterDemand(ctx.program->max_reg_demand.vgpr, ctx.program->max_reg_demand.sgpr + 1));
+ return get_reg(ctx, reg_file, rc, parallelcopies, instr);
+ }
+
+ //FIXME: if nothing helps, shift-rotate the registers to make space
+
+ unreachable("did not find a register");
+}
+
+
+std::pair<PhysReg, bool> get_reg_vec(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ RegClass rc)
+{
+ uint32_t size = rc.size();
+ uint32_t stride = 1;
+ uint32_t lb, ub;
+ if (rc.type() == RegType::vgpr) {
+ lb = 256;
+ ub = 256 + ctx.program->max_reg_demand.vgpr;
+ } else {
+ lb = 0;
+ ub = ctx.program->max_reg_demand.sgpr;
+ if (size == 2)
+ stride = 2;
+ else if (size >= 4)
+ stride = 4;
+ }
+ return get_reg_simple(ctx, reg_file, lb, ub, size, stride, rc);
+}
+
+
+PhysReg get_reg_create_vector(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ RegClass rc,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ aco_ptr<Instruction>& instr)
+{
+ /* create_vector instructions have different costs w.r.t. register coalescing */
+ uint32_t size = rc.size();
+ uint32_t stride = 1;
+ uint32_t lb, ub;
+ if (rc.type() == RegType::vgpr) {
+ lb = 256;
+ ub = 256 + ctx.program->max_reg_demand.vgpr;
+ } else {
+ lb = 0;
+ ub = ctx.program->max_reg_demand.sgpr;
+ if (size == 2)
+ stride = 2;
+ else if (size >= 4)
+ stride = 4;
+ }
+
+ unsigned best_pos = -1;
+ unsigned num_moves = 0xFF;
+ bool best_war_hint = true;
+
+ /* test for each operand which definition placement causes the least shuffle instructions */
+ for (unsigned i = 0, offset = 0; i < instr->operands.size(); offset += instr->operands[i].size(), i++) {
+ // TODO: think about, if we can alias live operands on the same register
+ if (!instr->operands[i].isTemp() || !instr->operands[i].isKill() || instr->operands[i].getTemp().type() != rc.type())
+ continue;
+
+ if (offset > instr->operands[i].physReg())
+ continue;
+
+ unsigned reg_lo = instr->operands[i].physReg() - offset;
+ unsigned reg_hi = reg_lo + size - 1;
+ unsigned k = 0;
+
+ /* no need to check multiple times */
+ if (reg_lo == best_pos)
+ continue;
+
+ /* check borders */
+ // TODO: this can be improved */
+ if (reg_lo < lb || reg_hi >= ub || reg_lo % stride != 0)
+ continue;
+ if (reg_lo > lb && reg_file[reg_lo] != 0 && reg_file[reg_lo] == reg_file[reg_lo - 1])
+ continue;
+ if (reg_hi < ub - 1 && reg_file[reg_hi] != 0 && reg_file[reg_hi] == reg_file[reg_hi + 1])
+ continue;
+
+ /* count variables to be moved and check war_hint */
+ bool war_hint = false;
+ for (unsigned j = reg_lo; j <= reg_hi; j++) {
+ if (reg_file[j] != 0)
+ k++;
+ war_hint |= ctx.war_hint[j];
+ }
+
+ /* count operands in wrong positions */
+ for (unsigned j = 0, offset = 0; j < instr->operands.size(); offset += instr->operands[j].size(), j++) {
+ if (j == i ||
+ !instr->operands[j].isTemp() ||
+ instr->operands[j].getTemp().type() != rc.type())
+ continue;
+ if (instr->operands[j].physReg() != reg_lo + offset)
+ k += instr->operands[j].size();
+ }
+ bool aligned = rc == RegClass::v4 && reg_lo % 4 == 0;
+ if (k > num_moves || (!aligned && k == num_moves) || (war_hint && !best_war_hint))
+ continue;
+
+ best_pos = reg_lo;
+ num_moves = k;
+ best_war_hint = war_hint;
+ }
+
+ if (num_moves >= size)
+ return get_reg(ctx, reg_file, rc, parallelcopies, instr);
+
+ /* collect variables to be moved */
+ std::set<std::pair<unsigned, unsigned>> vars;
+ for (unsigned i = best_pos; i < best_pos + size; i++) {
+ if (reg_file[i] != 0)
+ vars.emplace(ctx.assignments[reg_file[i]].second.size(), reg_file[i]);
+ reg_file[i] = 0;
+ }
+
+ /* move killed operands which aren't yet at the correct position */
+ for (unsigned i = 0, offset = 0; i < instr->operands.size(); offset += instr->operands[i].size(), i++) {
+ if (instr->operands[i].isTemp() && instr->operands[i].isFirstKill() && instr->operands[i].getTemp().type() == rc.type()) {
+ if (instr->operands[i].physReg() != best_pos + offset) {
+ vars.emplace(instr->operands[i].size(), instr->operands[i].tempId());
+ } else {
+ for (unsigned j = 0; j < instr->operands[i].size(); j++)
+ reg_file[instr->operands[i].physReg() + j] = instr->operands[i].tempId();
+ }
+ }
+ }
+
+ ASSERTED bool success = false;
+ success = get_regs_for_copies(ctx, reg_file, parallelcopies, vars, lb, ub, instr, best_pos, best_pos + size - 1);
+ assert(success);
+
+ update_renames(ctx, reg_file, parallelcopies, instr);
+ adjust_max_used_regs(ctx, rc, best_pos);
+ return PhysReg{best_pos};
+}
+
+bool get_reg_specified(ra_ctx& ctx,
+ std::array<uint32_t, 512>& reg_file,
+ RegClass rc,
+ std::vector<std::pair<Operand, Definition>>& parallelcopies,
+ aco_ptr<Instruction>& instr,
+ PhysReg reg)
+{
+ uint32_t size = rc.size();
+ uint32_t stride = 1;
+ uint32_t lb, ub;
+
+ if (rc.type() == RegType::vgpr) {
+ lb = 256;
+ ub = 256 + ctx.program->max_reg_demand.vgpr;
+ } else {
+ if (size == 2)
+ stride = 2;
+ else if (size >= 4)
+ stride = 4;
+ if (reg % stride != 0)
+ return false;
+ lb = 0;
+ ub = ctx.program->max_reg_demand.sgpr;
+ }
+
+ uint32_t reg_lo = reg.reg;
+ uint32_t reg_hi = reg + (size - 1);
+
+ if (reg_lo < lb || reg_hi >= ub || reg_lo > reg_hi)
+ return false;
+
+ for (unsigned i = reg_lo; i <= reg_hi; i++) {
+ if (reg_file[i] != 0)
+ return false;
+ }
+ adjust_max_used_regs(ctx, rc, reg_lo);
+ return true;
+}
+
+void handle_pseudo(ra_ctx& ctx,
+ const std::array<uint32_t, 512>& reg_file,
+ Instruction* instr)
+{
+ if (instr->format != Format::PSEUDO)
+ return;
+
+ /* all instructions which use handle_operands() need this information */
+ switch (instr->opcode) {
+ case aco_opcode::p_extract_vector:
+ case aco_opcode::p_create_vector:
+ case aco_opcode::p_split_vector:
+ case aco_opcode::p_parallelcopy:
+ case aco_opcode::p_wqm:
+ break;
+ default:
+ return;
+ }
+
+ /* if all definitions are vgpr, no need to care for SCC */
+ bool writes_sgpr = false;
+ for (Definition& def : instr->definitions) {
+ if (def.getTemp().type() == RegType::sgpr) {
+ writes_sgpr = true;
+ break;
+ }
+ }
+ if (!writes_sgpr)
+ return;
+
+ Pseudo_instruction *pi = (Pseudo_instruction *)instr;
+ if (reg_file[scc.reg]) {
+ pi->tmp_in_scc = true;
+
+ int reg = ctx.max_used_sgpr;
+ for (; reg >= 0 && reg_file[reg]; reg--)
+ ;
+ if (reg < 0) {
+ reg = ctx.max_used_sgpr + 1;
+ for (; reg < ctx.program->max_reg_demand.sgpr && reg_file[reg]; reg++)
+ ;
+ assert(reg < ctx.program->max_reg_demand.sgpr);
+ }
+
+ adjust_max_used_regs(ctx, s1, reg);
+ pi->scratch_sgpr = PhysReg{(unsigned)reg};
+ } else {
+ pi->tmp_in_scc = false;
+ }
+}
+
+bool operand_can_use_reg(aco_ptr<Instruction>& instr, unsigned idx, PhysReg reg)
+{
+ switch (instr->format) {
+ case Format::SMEM:
+ return reg != scc &&
+ reg != exec &&
+ (reg != m0 || idx == 1 || idx == 3) && /* offset can be m0 */
+ (reg != vcc || (instr->definitions.empty() && idx == 2)); /* sdata can be vcc */
+ default:
+ // TODO: there are more instructions with restrictions on registers
+ return true;
+ }
+}
+
+} /* end namespace */
+
+
+void register_allocation(Program *program, std::vector<std::set<Temp>> live_out_per_block)
+{
+ ra_ctx ctx(program);
+
+ std::vector<std::unordered_map<unsigned, Temp>> renames(program->blocks.size());
+
+ struct phi_info {
+ Instruction* phi;
+ unsigned block_idx;
+ std::set<Instruction*> uses;
+ };
+
+ bool filled[program->blocks.size()];
+ bool sealed[program->blocks.size()];
+ memset(filled, 0, sizeof filled);
+ memset(sealed, 0, sizeof sealed);
+ std::vector<std::vector<Instruction*>> incomplete_phis(program->blocks.size());
+ std::map<unsigned, phi_info> phi_map;
+ std::map<unsigned, unsigned> affinities;
+ std::function<Temp(Temp,unsigned)> read_variable;
+ std::function<Temp(Temp,Block*)> handle_live_in;
+ std::function<Temp(std::map<unsigned, phi_info>::iterator)> try_remove_trivial_phi;
+
+ read_variable = [&](Temp val, unsigned block_idx) -> Temp {
+ std::unordered_map<unsigned, Temp>::iterator it = renames[block_idx].find(val.id());
+ assert(it != renames[block_idx].end());
+ return it->second;
+ };
+
+ handle_live_in = [&](Temp val, Block *block) -> Temp {
+ std::vector<unsigned>& preds = val.is_linear() ? block->linear_preds : block->logical_preds;
+ if (preds.size() == 0 && block->index != 0) {
+ renames[block->index][val.id()] = val;
+ return val;
+ }
+ assert(preds.size() > 0);
+
+ Temp new_val;
+ if (!sealed[block->index]) {
+ /* consider rename from already processed predecessor */
+ Temp tmp = read_variable(val, preds[0]);
+
+ /* if the block is not sealed yet, we create an incomplete phi (which might later get removed again) */
+ new_val = Temp{program->allocateId(), val.regClass()};
+ aco_opcode opcode = val.is_linear() ? aco_opcode::p_linear_phi : aco_opcode::p_phi;
+ aco_ptr<Instruction> phi{create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, preds.size(), 1)};
+ phi->definitions[0] = Definition(new_val);
+ for (unsigned i = 0; i < preds.size(); i++)
+ phi->operands[i] = Operand(val);
+ if (tmp.regClass() == new_val.regClass())
+ affinities[new_val.id()] = tmp.id();
+
+ phi_map.emplace(new_val.id(), phi_info{phi.get(), block->index});
+ incomplete_phis[block->index].emplace_back(phi.get());
+ block->instructions.insert(block->instructions.begin(), std::move(phi));
+
+ } else if (preds.size() == 1) {
+ /* if the block has only one predecessor, just look there for the name */
+ new_val = read_variable(val, preds[0]);
+ } else {
+ /* there are multiple predecessors and the block is sealed */
+ Temp ops[preds.size()];
+
+ /* we start assuming that the name is the same from all predecessors */
+ renames[block->index][val.id()] = val;
+ bool needs_phi = false;
+
+ /* get the rename from each predecessor and check if they are the same */
+ for (unsigned i = 0; i < preds.size(); i++) {
+ ops[i] = read_variable(val, preds[i]);
+ if (i == 0)
+ new_val = ops[i];
+ else
+ needs_phi |= !(new_val == ops[i]);
+ }
+
+ if (needs_phi) {
+ /* the variable has been renamed differently in the predecessors: we need to insert a phi */
+ aco_opcode opcode = val.is_linear() ? aco_opcode::p_linear_phi : aco_opcode::p_phi;
+ aco_ptr<Instruction> phi{create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, preds.size(), 1)};
+ new_val = Temp{program->allocateId(), val.regClass()};
+ phi->definitions[0] = Definition(new_val);
+ for (unsigned i = 0; i < preds.size(); i++) {
+ phi->operands[i] = Operand(ops[i]);
+ phi->operands[i].setFixed(ctx.assignments[ops[i].id()].first);
+ if (ops[i].regClass() == new_val.regClass())
+ affinities[new_val.id()] = ops[i].id();
+ }
+ phi_map.emplace(new_val.id(), phi_info{phi.get(), block->index});
+ block->instructions.insert(block->instructions.begin(), std::move(phi));
+ }
+ }
+
+ renames[block->index][val.id()] = new_val;
+ renames[block->index][new_val.id()] = new_val;
+ ctx.orig_names[new_val.id()] = val;
+ return new_val;
+ };
+
+ try_remove_trivial_phi = [&] (std::map<unsigned, phi_info>::iterator info) -> Temp {
+ assert(info->second.block_idx != 0);
+ Instruction* phi = info->second.phi;
+ Temp same = Temp();
+
+ Definition def = phi->definitions[0];
+ /* a phi node is trivial if all operands are the same as the definition of the phi */
+ for (const Operand& op : phi->operands) {
+ const Temp t = op.getTemp();
+ if (t == same || t == def.getTemp())
+ continue;
+ if (!(same == Temp()) || !(op.physReg() == def.physReg())) {
+ /* phi is not trivial */
+ return def.getTemp();
+ }
+ same = t;
+ }
+ assert(!(same == Temp() || same == def.getTemp()));
+
+ /* reroute all uses to same and remove phi */
+ std::vector<std::map<unsigned, phi_info>::iterator> phi_users;
+ std::map<unsigned, phi_info>::iterator same_phi_info = phi_map.find(same.id());
+ for (Instruction* instr : info->second.uses) {
+ assert(phi != instr);
+ /* recursively try to remove trivial phis */
+ if (is_phi(instr)) {
+ /* ignore if the phi was already flagged trivial */
+ if (instr->definitions.empty())
+ continue;
+
+ std::map<unsigned, phi_info>::iterator it = phi_map.find(instr->definitions[0].tempId());
+ if (it != phi_map.end() && it != info)
+ phi_users.emplace_back(it);
+ }
+ for (Operand& op : instr->operands) {
+ if (op.isTemp() && op.tempId() == def.tempId()) {
+ op.setTemp(same);
+ if (same_phi_info != phi_map.end())
+ same_phi_info->second.uses.emplace(instr);
+ }
+ }
+ }
+
+ auto it = ctx.orig_names.find(same.id());
+ unsigned orig_var = it != ctx.orig_names.end() ? it->second.id() : same.id();
+ for (unsigned i = 0; i < program->blocks.size(); i++) {
+ auto it = renames[i].find(orig_var);
+ if (it != renames[i].end() && it->second == def.getTemp())
+ renames[i][orig_var] = same;
+ }
+
+ unsigned block_idx = info->second.block_idx;
+ phi->definitions.clear(); /* this indicates that the phi can be removed */
+ phi_map.erase(info);
+ for (auto it : phi_users) {
+ if (sealed[it->second.block_idx])
+ try_remove_trivial_phi(it);
+ }
+
+ /* due to the removal of other phis, the name might have changed once again! */
+ return renames[block_idx][orig_var];
+ };
+
+ std::map<unsigned, Instruction*> vectors;
+ std::vector<std::vector<Temp>> phi_ressources;
+ std::map<unsigned, unsigned> temp_to_phi_ressources;
+
+ for (std::vector<Block>::reverse_iterator it = program->blocks.rbegin(); it != program->blocks.rend(); it++) {
+ Block& block = *it;
+
+ /* first, compute the death points of all live vars within the block */
+ std::set<Temp>& live = live_out_per_block[block.index];
+
+ std::vector<aco_ptr<Instruction>>::reverse_iterator rit;
+ for (rit = block.instructions.rbegin(); rit != block.instructions.rend(); ++rit) {
+ aco_ptr<Instruction>& instr = *rit;
+ if (!is_phi(instr)) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp())
+ live.emplace(op.getTemp());
+ }
+ if (instr->opcode == aco_opcode::p_create_vector) {
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.getTemp().type() == instr->definitions[0].getTemp().type())
+ vectors[op.tempId()] = instr.get();
+ }
+ }
+ } else if (!instr->definitions[0].isKill() && !instr->definitions[0].isFixed()) {
+ /* collect information about affinity-related temporaries */
+ std::vector<Temp> affinity_related;
+ /* affinity_related[0] is the last seen affinity-related temp */
+ affinity_related.emplace_back(instr->definitions[0].getTemp());
+ affinity_related.emplace_back(instr->definitions[0].getTemp());
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.regClass() == instr->definitions[0].regClass()) {
+ affinity_related.emplace_back(op.getTemp());
+ temp_to_phi_ressources[op.tempId()] = phi_ressources.size();
+ }
+ }
+ phi_ressources.emplace_back(std::move(affinity_related));
+ }
+
+ /* erase from live */
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp()) {
+ live.erase(def.getTemp());
+ std::map<unsigned, unsigned>::iterator it = temp_to_phi_ressources.find(def.tempId());
+ if (it != temp_to_phi_ressources.end() && def.regClass() == phi_ressources[it->second][0].regClass())
+ phi_ressources[it->second][0] = def.getTemp();
+ }
+ }
+ }
+ }
+ /* create affinities */
+ for (std::vector<Temp>& vec : phi_ressources) {
+ assert(vec.size() > 1);
+ for (unsigned i = 1; i < vec.size(); i++)
+ if (vec[i].id() != vec[0].id())
+ affinities[vec[i].id()] = vec[0].id();
+ }
+
+ /* state of register file after phis */
+ std::vector<std::bitset<128>> sgpr_live_in(program->blocks.size());
+
+ for (Block& block : program->blocks) {
+ std::set<Temp>& live = live_out_per_block[block.index];
+ /* initialize register file */
+ assert(block.index != 0 || live.empty());
+ std::array<uint32_t, 512> register_file = {0};
+ ctx.war_hint.reset();
+
+ for (Temp t : live) {
+ Temp renamed = handle_live_in(t, &block);
+ if (ctx.assignments.find(renamed.id()) != ctx.assignments.end()) {
+ for (unsigned i = 0; i < t.size(); i++)
+ register_file[ctx.assignments[renamed.id()].first + i] = renamed.id();
+ }
+ }
+
+ std::vector<aco_ptr<Instruction>> instructions;
+ std::vector<aco_ptr<Instruction>>::iterator it;
+
+ /* this is a slight adjustment from the paper as we already have phi nodes:
+ * We consider them incomplete phis and only handle the definition. */
+
+ /* handle fixed phi definitions */
+ for (it = block.instructions.begin(); it != block.instructions.end(); ++it) {
+ aco_ptr<Instruction>& phi = *it;
+ if (!is_phi(phi))
+ break;
+ Definition& definition = phi->definitions[0];
+ if (!definition.isFixed())
+ continue;
+
+ /* check if a dead exec mask phi is needed */
+ if (definition.isKill()) {
+ for (Operand& op : phi->operands) {
+ assert(op.isTemp());
+ if (ctx.assignments.find(op.tempId()) == ctx.assignments.end() ||
+ ctx.assignments[op.tempId()].first != exec) {
+ definition.setKill(false);
+ break;
+ }
+ }
+ }
+
+ if (definition.isKill())
+ continue;
+
+ assert(definition.physReg() == exec);
+ for (unsigned i = 0; i < definition.size(); i++) {
+ assert(register_file[definition.physReg() + i] == 0);
+ register_file[definition.physReg() + i] = definition.tempId();
+ }
+ ctx.assignments[definition.tempId()] = {definition.physReg(), definition.regClass()};
+ }
+
+ /* look up the affinities */
+ for (it = block.instructions.begin(); it != block.instructions.end(); ++it) {
+ aco_ptr<Instruction>& phi = *it;
+ if (!is_phi(phi))
+ break;
+ Definition& definition = phi->definitions[0];
+ if (definition.isKill() || definition.isFixed())
+ continue;
+
+ if (affinities.find(definition.tempId()) != affinities.end() &&
+ ctx.assignments.find(affinities[definition.tempId()]) != ctx.assignments.end()) {
+ assert(ctx.assignments[affinities[definition.tempId()]].second == definition.regClass());
+ PhysReg reg = ctx.assignments[affinities[definition.tempId()]].first;
+ bool try_use_special_reg = reg == scc || reg == exec;
+ if (try_use_special_reg) {
+ for (const Operand& op : phi->operands) {
+ if (!op.isTemp() ||
+ ctx.assignments.find(op.tempId()) == ctx.assignments.end() ||
+ !(ctx.assignments[op.tempId()].first == reg)) {
+ try_use_special_reg = false;
+ break;
+ }
+ }
+ if (!try_use_special_reg)
+ continue;
+ }
+ bool reg_free = true;
+ for (unsigned i = reg.reg; reg_free && i < reg + definition.size(); i++) {
+ if (register_file[i] != 0)
+ reg_free = false;
+ }
+ /* only assign if register is still free */
+ if (reg_free) {
+ definition.setFixed(reg);
+ for (unsigned i = 0; i < definition.size(); i++)
+ register_file[definition.physReg() + i] = definition.tempId();
+ ctx.assignments[definition.tempId()] = {definition.physReg(), definition.regClass()};
+ }
+ }
+ }
+
+ /* find registers for phis without affinity or where the register was blocked */
+ for (it = block.instructions.begin();it != block.instructions.end(); ++it) {
+ aco_ptr<Instruction>& phi = *it;
+ if (!is_phi(phi))
+ break;
+
+ Definition& definition = phi->definitions[0];
+ if (definition.isKill())
+ continue;
+
+ renames[block.index][definition.tempId()] = definition.getTemp();
+
+ if (!definition.isFixed()) {
+ std::vector<std::pair<Operand, Definition>> parallelcopy;
+ /* try to find a register that is used by at least one operand */
+ for (const Operand& op : phi->operands) {
+ if (!op.isTemp() ||
+ ctx.assignments.find(op.tempId()) == ctx.assignments.end())
+ continue;
+ PhysReg reg = ctx.assignments[op.tempId()].first;
+ /* we tried this already on the previous loop */
+ if (reg == scc || reg == exec)
+ continue;
+ if (get_reg_specified(ctx, register_file, definition.regClass(), parallelcopy, phi, reg)) {
+ definition.setFixed(reg);
+ break;
+ }
+ }
+ if (!definition.isFixed())
+ definition.setFixed(get_reg(ctx, register_file, definition.regClass(), parallelcopy, phi));
+
+ /* process parallelcopy */
+ for (std::pair<Operand, Definition> pc : parallelcopy) {
+ /* rename */
+ std::map<unsigned, Temp>::iterator orig_it = ctx.orig_names.find(pc.first.tempId());
+ Temp orig = pc.first.getTemp();
+ if (orig_it != ctx.orig_names.end())
+ orig = orig_it->second;
+ else
+ ctx.orig_names[pc.second.tempId()] = orig;
+ renames[block.index][orig.id()] = pc.second.getTemp();
+ renames[block.index][pc.second.tempId()] = pc.second.getTemp();
+
+ /* see if it's a copy from a previous phi */
+ //TODO: prefer moving some previous phis over live-ins
+ //TODO: somehow prevent phis fixed before the RA from being updated (shouldn't be a problem in practice since they can only be fixed to exec)
+ Instruction *prev_phi = NULL;
+ for (auto it2 = instructions.begin(); it2 != instructions.end(); ++it2) {
+ if ((*it2)->definitions[0].tempId() == pc.first.tempId())
+ prev_phi = it2->get();
+ }
+ if (prev_phi) {
+ /* if so, just update that phi */
+ prev_phi->definitions[0] = pc.second;
+ continue;
+ }
+
+ /* otherwise, this is a live-in and we need to create a new phi
+ * to move it in this block's predecessors */
+ aco_opcode opcode = pc.first.getTemp().is_linear() ? aco_opcode::p_linear_phi : aco_opcode::p_phi;
+ std::vector<unsigned>& preds = pc.first.getTemp().is_linear() ? block.linear_preds : block.logical_preds;
+ aco_ptr<Instruction> new_phi{create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, preds.size(), 1)};
+ new_phi->definitions[0] = pc.second;
+ for (unsigned i = 0; i < preds.size(); i++)
+ new_phi->operands[i] = Operand(pc.first);
+ instructions.emplace_back(std::move(new_phi));
+ }
+
+ for (unsigned i = 0; i < definition.size(); i++)
+ register_file[definition.physReg() + i] = definition.tempId();
+ ctx.assignments[definition.tempId()] = {definition.physReg(), definition.regClass()};
+ }
+ live.emplace(definition.getTemp());
+
+ /* update phi affinities */
+ for (const Operand& op : phi->operands) {
+ if (op.isTemp() && op.regClass() == phi->definitions[0].regClass())
+ affinities[op.tempId()] = definition.tempId();
+ }
+
+ instructions.emplace_back(std::move(*it));
+ }
+
+ /* fill in sgpr_live_in */
+ for (unsigned i = 0; i < ctx.max_used_sgpr; i++)
+ sgpr_live_in[block.index][i] = register_file[i];
+ sgpr_live_in[block.index][127] = register_file[scc.reg];
+
+ /* Handle all other instructions of the block */
+ for (; it != block.instructions.end(); ++it) {
+ aco_ptr<Instruction>& instr = *it;
+
+ /* parallelcopies from p_phi are inserted here which means
+ * live ranges of killed operands end here as well */
+ if (instr->opcode == aco_opcode::p_logical_end) {
+ /* no need to process this instruction any further */
+ if (block.logical_succs.size() != 1) {
+ instructions.emplace_back(std::move(instr));
+ continue;
+ }
+
+ Block& succ = program->blocks[block.logical_succs[0]];
+ unsigned idx = 0;
+ for (; idx < succ.logical_preds.size(); idx++) {
+ if (succ.logical_preds[idx] == block.index)
+ break;
+ }
+ for (aco_ptr<Instruction>& phi : succ.instructions) {
+ if (phi->opcode == aco_opcode::p_phi) {
+ if (phi->operands[idx].isTemp() &&
+ phi->operands[idx].getTemp().type() == RegType::sgpr &&
+ phi->operands[idx].isFirstKill()) {
+ Temp phi_op = read_variable(phi->operands[idx].getTemp(), block.index);
+ PhysReg reg = ctx.assignments[phi_op.id()].first;
+ assert(register_file[reg] == phi_op.id());
+ register_file[reg] = 0;
+ }
+ } else if (phi->opcode != aco_opcode::p_linear_phi) {
+ break;
+ }
+ }
+ instructions.emplace_back(std::move(instr));
+ continue;
+ }
+
+ std::vector<std::pair<Operand, Definition>> parallelcopy;
+
+ assert(!is_phi(instr));
+
+ /* handle operands */
+ for (unsigned i = 0; i < instr->operands.size(); ++i) {
+ auto& operand = instr->operands[i];
+ if (!operand.isTemp())
+ continue;
+
+ /* rename operands */
+ operand.setTemp(read_variable(operand.getTemp(), block.index));
+
+ /* check if the operand is fixed */
+ if (operand.isFixed()) {
+
+ if (operand.physReg() == ctx.assignments[operand.tempId()].first) {
+ /* we are fine: the operand is already assigned the correct reg */
+
+ } else {
+ /* check if target reg is blocked, and move away the blocking var */
+ if (register_file[operand.physReg().reg]) {
+ uint32_t blocking_id = register_file[operand.physReg().reg];
+ RegClass rc = ctx.assignments[blocking_id].second;
+ Operand pc_op = Operand(Temp{blocking_id, rc});
+ pc_op.setFixed(operand.physReg());
+ Definition pc_def = Definition(Temp{program->allocateId(), pc_op.regClass()});
+ /* find free reg */
+ PhysReg reg = get_reg(ctx, register_file, pc_op.regClass(), parallelcopy, instr);
+ pc_def.setFixed(reg);
+ ctx.assignments[pc_def.tempId()] = {reg, pc_def.regClass()};
+ for (unsigned i = 0; i < operand.size(); i++) {
+ register_file[pc_op.physReg() + i] = 0;
+ register_file[pc_def.physReg() + i] = pc_def.tempId();
+ }
+ parallelcopy.emplace_back(pc_op, pc_def);
+
+ /* handle renames of previous operands */
+ for (unsigned j = 0; j < i; j++) {
+ Operand& op = instr->operands[j];
+ if (op.isTemp() && op.tempId() == blocking_id) {
+ op = Operand(pc_def.getTemp());
+ op.setFixed(reg);
+ }
+ }
+ }
+ /* move operand to fixed reg and create parallelcopy pair */
+ Operand pc_op = operand;
+ Temp tmp = Temp{program->allocateId(), operand.regClass()};
+ Definition pc_def = Definition(tmp);
+ pc_def.setFixed(operand.physReg());
+ pc_op.setFixed(ctx.assignments[operand.tempId()].first);
+ operand.setTemp(tmp);
+ ctx.assignments[tmp.id()] = {pc_def.physReg(), pc_def.regClass()};
+ operand.setFixed(pc_def.physReg());
+ for (unsigned i = 0; i < operand.size(); i++) {
+ register_file[pc_op.physReg() + i] = 0;
+ register_file[pc_def.physReg() + i] = tmp.id();
+ }
+ parallelcopy.emplace_back(pc_op, pc_def);
+ }
+ } else {
+ assert(ctx.assignments.find(operand.tempId()) != ctx.assignments.end());
+ PhysReg reg = ctx.assignments[operand.tempId()].first;
+
+ if (operand_can_use_reg(instr, i, reg)) {
+ operand.setFixed(ctx.assignments[operand.tempId()].first);
+ } else {
+ Operand pc_op = operand;
+ pc_op.setFixed(reg);
+ PhysReg new_reg = get_reg(ctx, register_file, operand.regClass(), parallelcopy, instr);
+ Definition pc_def = Definition(program->allocateId(), new_reg, pc_op.regClass());
+ ctx.assignments[pc_def.tempId()] = {reg, pc_def.regClass()};
+ for (unsigned i = 0; i < operand.size(); i++) {
+ register_file[pc_op.physReg() + i] = 0;
+ register_file[pc_def.physReg() + i] = pc_def.tempId();
+ }
+ parallelcopy.emplace_back(pc_op, pc_def);
+ operand.setFixed(new_reg);
+ }
+
+ if (instr->format == Format::EXP ||
+ (instr->isVMEM() && i == 3 && program->chip_class == GFX6) ||
+ (instr->format == Format::DS && static_cast<DS_instruction*>(instr.get())->gds)) {
+ for (unsigned j = 0; j < operand.size(); j++)
+ ctx.war_hint.set(operand.physReg().reg + j);
+ }
+ }
+ std::map<unsigned, phi_info>::iterator phi = phi_map.find(operand.getTemp().id());
+ if (phi != phi_map.end())
+ phi->second.uses.emplace(instr.get());
+
+ }
+ /* remove dead vars from register file */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill())
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0;
+ }
+
+ /* try to optimize v_mad_f32 -> v_mac_f32 */
+ if (instr->opcode == aco_opcode::v_mad_f32 &&
+ instr->operands[2].isTemp() &&
+ instr->operands[2].isKill() &&
+ instr->operands[2].getTemp().type() == RegType::vgpr &&
+ instr->operands[1].isTemp() &&
+ instr->operands[1].getTemp().type() == RegType::vgpr) { /* TODO: swap src0 and src1 in this case */
+ VOP3A_instruction* vop3 = static_cast<VOP3A_instruction*>(instr.get());
+ bool can_use_mac = !(vop3->abs[0] || vop3->abs[1] || vop3->abs[2] ||
+ vop3->opsel[0] || vop3->opsel[1] || vop3->opsel[2] ||
+ vop3->neg[0] || vop3->neg[1] || vop3->neg[2] ||
+ vop3->clamp || vop3->omod);
+ if (can_use_mac) {
+ instr->format = Format::VOP2;
+ instr->opcode = aco_opcode::v_mac_f32;
+ }
+ }
+
+ /* handle definitions which must have the same register as an operand */
+ if (instr->opcode == aco_opcode::v_interp_p2_f32 ||
+ instr->opcode == aco_opcode::v_mac_f32 ||
+ instr->opcode == aco_opcode::v_writelane_b32) {
+ instr->definitions[0].setFixed(instr->operands[2].physReg());
+ } else if (instr->opcode == aco_opcode::s_addk_i32 ||
+ instr->opcode == aco_opcode::s_mulk_i32) {
+ instr->definitions[0].setFixed(instr->operands[0].physReg());
+ } else if ((instr->format == Format::MUBUF ||
+ instr->format == Format::MIMG) &&
+ instr->definitions.size() == 1 &&
+ instr->operands.size() == 4) {
+ instr->definitions[0].setFixed(instr->operands[3].physReg());
+ }
+
+ ctx.defs_done.reset();
+
+ /* handle fixed definitions first */
+ for (unsigned i = 0; i < instr->definitions.size(); ++i) {
+ auto& definition = instr->definitions[i];
+ if (!definition.isFixed())
+ continue;
+
+ adjust_max_used_regs(ctx, definition.regClass(), definition.physReg());
+ /* check if the target register is blocked */
+ if (register_file[definition.physReg().reg] != 0) {
+ /* create parallelcopy pair to move blocking var */
+ Temp tmp = {register_file[definition.physReg()], ctx.assignments[register_file[definition.physReg()]].second};
+ Operand pc_op = Operand(tmp);
+ pc_op.setFixed(ctx.assignments[register_file[definition.physReg().reg]].first);
+ RegClass rc = pc_op.regClass();
+ tmp = Temp{program->allocateId(), rc};
+ Definition pc_def = Definition(tmp);
+
+ /* re-enable the killed operands, so that we don't move the blocking var there */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill())
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0xFFFF;
+ }
+
+ /* find a new register for the blocking variable */
+ PhysReg reg = get_reg(ctx, register_file, rc, parallelcopy, instr);
+ /* once again, disable killed operands */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill())
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0;
+ }
+ for (unsigned k = 0; k < i; k++) {
+ if (instr->definitions[k].isTemp() && ctx.defs_done.test(k) && !instr->definitions[k].isKill())
+ for (unsigned j = 0; j < instr->definitions[k].size(); j++)
+ register_file[instr->definitions[k].physReg() + j] = instr->definitions[k].tempId();
+ }
+ pc_def.setFixed(reg);
+
+ /* finish assignment of parallelcopy */
+ ctx.assignments[pc_def.tempId()] = {reg, pc_def.regClass()};
+ parallelcopy.emplace_back(pc_op, pc_def);
+
+ /* add changes to reg_file */
+ for (unsigned i = 0; i < pc_op.size(); i++) {
+ register_file[pc_op.physReg() + i] = 0x0;
+ register_file[pc_def.physReg() + i] = pc_def.tempId();
+ }
+ }
+ ctx.defs_done.set(i);
+
+ if (!definition.isTemp())
+ continue;
+
+ /* set live if it has a kill point */
+ if (!definition.isKill())
+ live.emplace(definition.getTemp());
+
+ ctx.assignments[definition.tempId()] = {definition.physReg(), definition.regClass()};
+ renames[block.index][definition.tempId()] = definition.getTemp();
+ for (unsigned j = 0; j < definition.size(); j++)
+ register_file[definition.physReg() + j] = definition.tempId();
+ }
+
+ /* handle all other definitions */
+ for (unsigned i = 0; i < instr->definitions.size(); ++i) {
+ auto& definition = instr->definitions[i];
+
+ if (definition.isFixed() || !definition.isTemp())
+ continue;
+
+ /* find free reg */
+ if (definition.hasHint() && register_file[definition.physReg().reg] == 0)
+ definition.setFixed(definition.physReg());
+ else if (instr->opcode == aco_opcode::p_split_vector) {
+ PhysReg reg = PhysReg{instr->operands[0].physReg() + i * definition.size()};
+ if (!get_reg_specified(ctx, register_file, definition.regClass(), parallelcopy, instr, reg))
+ reg = get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr);
+ definition.setFixed(reg);
+ } else if (instr->opcode == aco_opcode::p_wqm) {
+ PhysReg reg;
+ if (instr->operands[0].isKill() && instr->operands[0].getTemp().type() == definition.getTemp().type()) {
+ reg = instr->operands[0].physReg();
+ assert(register_file[reg.reg] == 0);
+ } else {
+ reg = get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr);
+ }
+ definition.setFixed(reg);
+ } else if (instr->opcode == aco_opcode::p_extract_vector) {
+ PhysReg reg;
+ if (instr->operands[0].isKill() &&
+ instr->operands[0].getTemp().type() == definition.getTemp().type()) {
+ reg = instr->operands[0].physReg();
+ reg.reg += definition.size() * instr->operands[1].constantValue();
+ assert(register_file[reg.reg] == 0);
+ } else {
+ reg = get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr);
+ }
+ definition.setFixed(reg);
+ } else if (instr->opcode == aco_opcode::p_create_vector) {
+ PhysReg reg = get_reg_create_vector(ctx, register_file, definition.regClass(),
+ parallelcopy, instr);
+ definition.setFixed(reg);
+ } else if (affinities.find(definition.tempId()) != affinities.end() &&
+ ctx.assignments.find(affinities[definition.tempId()]) != ctx.assignments.end()) {
+ PhysReg reg = ctx.assignments[affinities[definition.tempId()]].first;
+ if (get_reg_specified(ctx, register_file, definition.regClass(), parallelcopy, instr, reg))
+ definition.setFixed(reg);
+ else
+ definition.setFixed(get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr));
+
+ } else if (vectors.find(definition.tempId()) != vectors.end()) {
+ Instruction* vec = vectors[definition.tempId()];
+ unsigned offset = 0;
+ for (const Operand& op : vec->operands) {
+ if (op.isTemp() && op.tempId() == definition.tempId())
+ break;
+ else
+ offset += op.size();
+ }
+ unsigned k = 0;
+ for (const Operand& op : vec->operands) {
+ if (op.isTemp() &&
+ op.tempId() != definition.tempId() &&
+ op.getTemp().type() == definition.getTemp().type() &&
+ ctx.assignments.find(op.tempId()) != ctx.assignments.end()) {
+ PhysReg reg = ctx.assignments[op.tempId()].first;
+ reg.reg = reg - k + offset;
+ if (get_reg_specified(ctx, register_file, definition.regClass(), parallelcopy, instr, reg)) {
+ definition.setFixed(reg);
+ break;
+ }
+ }
+ k += op.size();
+ }
+ if (!definition.isFixed()) {
+ std::pair<PhysReg, bool> res = get_reg_vec(ctx, register_file, vec->definitions[0].regClass());
+ PhysReg reg = res.first;
+ if (res.second) {
+ reg.reg += offset;
+ } else {
+ reg = get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr);
+ }
+ definition.setFixed(reg);
+ }
+ } else
+ definition.setFixed(get_reg(ctx, register_file, definition.regClass(), parallelcopy, instr));
+
+ assert(definition.isFixed() && ((definition.getTemp().type() == RegType::vgpr && definition.physReg() >= 256) ||
+ (definition.getTemp().type() != RegType::vgpr && definition.physReg() < 256)));
+ ctx.defs_done.set(i);
+
+ /* set live if it has a kill point */
+ if (!definition.isKill())
+ live.emplace(definition.getTemp());
+
+ ctx.assignments[definition.tempId()] = {definition.physReg(), definition.regClass()};
+ renames[block.index][definition.tempId()] = definition.getTemp();
+ for (unsigned j = 0; j < definition.size(); j++)
+ register_file[definition.physReg() + j] = definition.tempId();
+ }
+
+ handle_pseudo(ctx, register_file, instr.get());
+
+ /* kill definitions */
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp() && def.isKill()) {
+ for (unsigned j = 0; j < def.size(); j++) {
+ register_file[def.physReg() + j] = 0;
+ }
+ }
+ }
+
+ /* emit parallelcopy */
+ if (!parallelcopy.empty()) {
+ aco_ptr<Pseudo_instruction> pc;
+ pc.reset(create_instruction<Pseudo_instruction>(aco_opcode::p_parallelcopy, Format::PSEUDO, parallelcopy.size(), parallelcopy.size()));
+ bool temp_in_scc = register_file[scc.reg];
+ bool sgpr_operands_alias_defs = false;
+ uint64_t sgpr_operands[4] = {0, 0, 0, 0};
+ for (unsigned i = 0; i < parallelcopy.size(); i++) {
+ if (temp_in_scc && parallelcopy[i].first.isTemp() && parallelcopy[i].first.getTemp().type() == RegType::sgpr) {
+ if (!sgpr_operands_alias_defs) {
+ unsigned reg = parallelcopy[i].first.physReg().reg;
+ unsigned size = parallelcopy[i].first.getTemp().size();
+ sgpr_operands[reg / 64u] |= ((1u << size) - 1) << (reg % 64u);
+
+ reg = parallelcopy[i].second.physReg().reg;
+ size = parallelcopy[i].second.getTemp().size();
+ if (sgpr_operands[reg / 64u] & ((1u << size) - 1) << (reg % 64u))
+ sgpr_operands_alias_defs = true;
+ }
+ }
+
+ pc->operands[i] = parallelcopy[i].first;
+ pc->definitions[i] = parallelcopy[i].second;
+
+ /* it might happen that the operand is already renamed. we have to restore the original name. */
+ std::map<unsigned, Temp>::iterator it = ctx.orig_names.find(pc->operands[i].tempId());
+ if (it != ctx.orig_names.end())
+ pc->operands[i].setTemp(it->second);
+ unsigned orig_id = pc->operands[i].tempId();
+ ctx.orig_names[pc->definitions[i].tempId()] = pc->operands[i].getTemp();
+
+ pc->operands[i].setTemp(read_variable(pc->operands[i].getTemp(), block.index));
+ renames[block.index][orig_id] = pc->definitions[i].getTemp();
+ renames[block.index][pc->definitions[i].tempId()] = pc->definitions[i].getTemp();
+ std::map<unsigned, phi_info>::iterator phi = phi_map.find(pc->operands[i].tempId());
+ if (phi != phi_map.end())
+ phi->second.uses.emplace(pc.get());
+ }
+
+ if (temp_in_scc && sgpr_operands_alias_defs) {
+ /* disable definitions and re-enable operands */
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp() && !def.isKill()) {
+ for (unsigned j = 0; j < def.size(); j++) {
+ register_file[def.physReg() + j] = 0x0;
+ }
+ }
+ }
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill()) {
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0xFFFF;
+ }
+ }
+
+ handle_pseudo(ctx, register_file, pc.get());
+
+ /* re-enable live vars */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill())
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0x0;
+ }
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp() && !def.isKill()) {
+ for (unsigned j = 0; j < def.size(); j++) {
+ register_file[def.physReg() + j] = def.tempId();
+ }
+ }
+ }
+ } else {
+ pc->tmp_in_scc = false;
+ }
+
+ instructions.emplace_back(std::move(pc));
+ }
+
+ /* some instructions need VOP3 encoding if operand/definition is not assigned to VCC */
+ bool instr_needs_vop3 = !instr->isVOP3() &&
+ ((instr->format == Format::VOPC && !(instr->definitions[0].physReg() == vcc)) ||
+ (instr->opcode == aco_opcode::v_cndmask_b32 && !(instr->operands[2].physReg() == vcc)) ||
+ ((instr->opcode == aco_opcode::v_add_co_u32 ||
+ instr->opcode == aco_opcode::v_addc_co_u32 ||
+ instr->opcode == aco_opcode::v_sub_co_u32 ||
+ instr->opcode == aco_opcode::v_subb_co_u32 ||
+ instr->opcode == aco_opcode::v_subrev_co_u32 ||
+ instr->opcode == aco_opcode::v_subbrev_co_u32) &&
+ !(instr->definitions[1].physReg() == vcc)) ||
+ ((instr->opcode == aco_opcode::v_addc_co_u32 ||
+ instr->opcode == aco_opcode::v_subb_co_u32 ||
+ instr->opcode == aco_opcode::v_subbrev_co_u32) &&
+ !(instr->operands[2].physReg() == vcc)));
+ if (instr_needs_vop3) {
+
+ /* if the first operand is a literal, we have to move it to a reg */
+ if (instr->operands.size() && instr->operands[0].isLiteral()) {
+ bool can_sgpr = true;
+ /* check, if we have to move to vgpr */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.getTemp().type() == RegType::sgpr) {
+ can_sgpr = false;
+ break;
+ }
+ }
+ aco_ptr<Instruction> mov;
+ if (can_sgpr)
+ mov.reset(create_instruction<SOP1_instruction>(aco_opcode::s_mov_b32, Format::SOP1, 1, 1));
+ else
+ mov.reset(create_instruction<VOP1_instruction>(aco_opcode::v_mov_b32, Format::VOP1, 1, 1));
+ mov->operands[0] = instr->operands[0];
+ Temp tmp = {program->allocateId(), can_sgpr ? s1 : v1};
+ mov->definitions[0] = Definition(tmp);
+ /* disable definitions and re-enable operands */
+ for (const Definition& def : instr->definitions) {
+ for (unsigned j = 0; j < def.size(); j++) {
+ register_file[def.physReg() + j] = 0x0;
+ }
+ }
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill()) {
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0xFFFF;
+ }
+ }
+ mov->definitions[0].setFixed(get_reg(ctx, register_file, tmp.regClass(), parallelcopy, mov));
+ instr->operands[0] = Operand(tmp);
+ instr->operands[0].setFixed(mov->definitions[0].physReg());
+ instructions.emplace_back(std::move(mov));
+ /* re-enable live vars */
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && op.isFirstKill())
+ for (unsigned j = 0; j < op.size(); j++)
+ register_file[op.physReg() + j] = 0x0;
+ }
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp() && !def.isKill()) {
+ for (unsigned j = 0; j < def.size(); j++) {
+ register_file[def.physReg() + j] = def.tempId();
+ }
+ }
+ }
+ }
+
+ /* change the instruction to VOP3 to enable an arbitrary register pair as dst */
+ aco_ptr<Instruction> tmp = std::move(instr);
+ Format format = asVOP3(tmp->format);
+ instr.reset(create_instruction<VOP3A_instruction>(tmp->opcode, format, tmp->operands.size(), tmp->definitions.size()));
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ Operand& operand = tmp->operands[i];
+ instr->operands[i] = operand;
+ /* keep phi_map up to date */
+ if (operand.isTemp()) {
+ std::map<unsigned, phi_info>::iterator phi = phi_map.find(operand.tempId());
+ if (phi != phi_map.end()) {
+ phi->second.uses.erase(tmp.get());
+ phi->second.uses.emplace(instr.get());
+ }
+ }
+ }
+ std::copy(tmp->definitions.begin(), tmp->definitions.end(), instr->definitions.begin());
+ }
+ instructions.emplace_back(std::move(*it));
+
+ } /* end for Instr */
+
+ block.instructions = std::move(instructions);
+
+ filled[block.index] = true;
+ for (unsigned succ_idx : block.linear_succs) {
+ Block& succ = program->blocks[succ_idx];
+ /* seal block if all predecessors are filled */
+ bool all_filled = true;
+ for (unsigned pred_idx : succ.linear_preds) {
+ if (!filled[pred_idx]) {
+ all_filled = false;
+ break;
+ }
+ }
+ if (all_filled) {
+ /* finish incomplete phis and check if they became trivial */
+ for (Instruction* phi : incomplete_phis[succ_idx]) {
+ std::vector<unsigned> preds = phi->definitions[0].getTemp().is_linear() ? succ.linear_preds : succ.logical_preds;
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ phi->operands[i].setTemp(read_variable(phi->operands[i].getTemp(), preds[i]));
+ phi->operands[i].setFixed(ctx.assignments[phi->operands[i].tempId()].first);
+ }
+ try_remove_trivial_phi(phi_map.find(phi->definitions[0].tempId()));
+ }
+ /* complete the original phi nodes, but no need to check triviality */
+ for (aco_ptr<Instruction>& instr : succ.instructions) {
+ if (!is_phi(instr))
+ break;
+ std::vector<unsigned> preds = instr->opcode == aco_opcode::p_phi ? succ.logical_preds : succ.linear_preds;
+
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ auto& operand = instr->operands[i];
+ if (!operand.isTemp())
+ continue;
+ operand.setTemp(read_variable(operand.getTemp(), preds[i]));
+ operand.setFixed(ctx.assignments[operand.tempId()].first);
+ std::map<unsigned, phi_info>::iterator phi = phi_map.find(operand.getTemp().id());
+ if (phi != phi_map.end())
+ phi->second.uses.emplace(instr.get());
+ }
+ }
+ sealed[succ_idx] = true;
+ }
+ }
+ } /* end for BB */
+
+ /* remove trivial phis */
+ for (Block& block : program->blocks) {
+ auto end = std::find_if(block.instructions.begin(), block.instructions.end(),
+ [](aco_ptr<Instruction>& instr) { return !is_phi(instr);});
+ auto middle = std::remove_if(block.instructions.begin(), end,
+ [](const aco_ptr<Instruction>& instr) { return instr->definitions.empty();});
+ block.instructions.erase(middle, end);
+ }
+
+ /* find scc spill registers which may be needed for parallelcopies created by phis */
+ for (Block& block : program->blocks) {
+ if (block.linear_preds.size() <= 1)
+ continue;
+
+ std::bitset<128> regs = sgpr_live_in[block.index];
+ if (!regs[127])
+ continue;
+
+ /* choose a register */
+ int16_t reg = 0;
+ for (; reg < ctx.program->max_reg_demand.sgpr && regs[reg]; reg++)
+ ;
+ assert(reg < ctx.program->max_reg_demand.sgpr);
+ adjust_max_used_regs(ctx, s1, reg);
+
+ /* update predecessors */
+ for (unsigned& pred_index : block.linear_preds) {
+ Block& pred = program->blocks[pred_index];
+ pred.scc_live_out = true;
+ pred.scratch_sgpr = PhysReg{(uint16_t)reg};
+ }
+ }
+
+ /* num_gpr = rnd_up(max_used_gpr + 1) */
+ program->config->num_vgprs = (ctx.max_used_vgpr + 1 + 3) & ~3;
+ if (program->family == CHIP_TONGA || program->family == CHIP_ICELAND) {
+ assert(ctx.max_used_sgpr <= 93);
+ ctx.max_used_sgpr = 93; /* workaround hardware bug */
+ }
+ program->config->num_sgprs = (ctx.max_used_sgpr + 1 + 2 + 7) & ~7; /* + 2 sgprs for vcc */
+}
+
+}
diff --git a/src/amd/compiler/aco_scheduler.cpp b/src/amd/compiler/aco_scheduler.cpp
new file mode 100644
index 00000000000..0cd67a979e0
--- /dev/null
+++ b/src/amd/compiler/aco_scheduler.cpp
@@ -0,0 +1,835 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+#include <unordered_set>
+#include <algorithm>
+
+#include "vulkan/radv_shader.h" // for radv_nir_compiler_options
+#include "amdgfxregs.h"
+
+#define SMEM_WINDOW_SIZE (350 - ctx.num_waves * 35)
+#define VMEM_WINDOW_SIZE (1024 - ctx.num_waves * 64)
+#define POS_EXP_WINDOW_SIZE 512
+#define SMEM_MAX_MOVES (80 - ctx.num_waves * 8)
+#define VMEM_MAX_MOVES (128 - ctx.num_waves * 4)
+#define POS_EXP_MAX_MOVES 512
+
+namespace aco {
+
+struct sched_ctx {
+ std::vector<bool> depends_on;
+ std::vector<bool> RAR_dependencies;
+ RegisterDemand max_registers;
+ int16_t num_waves;
+ int16_t last_SMEM_stall;
+ int last_SMEM_dep_idx;
+};
+
+/* This scheduler is a simple bottom-up pass based on ideas from
+ * "A Novel Lightweight Instruction Scheduling Algorithm for Just-In-Time Compiler"
+ * from Xiaohua Shi and Peng Guo.
+ * The basic approach is to iterate over all instructions. When a memory instruction
+ * is encountered it tries to move independent instructions from above and below
+ * between the memory instruction and it's first user.
+ * The novelty is that this scheduler cares for the current register pressure:
+ * Instructions will only be moved if the register pressure won't exceed a certain bound.
+ */
+
+template <typename T>
+void move_element(T& list, size_t idx, size_t before) {
+ if (idx < before) {
+ auto begin = std::next(list.begin(), idx);
+ auto end = std::next(list.begin(), before);
+ std::rotate(begin, begin + 1, end);
+ } else if (idx > before) {
+ auto begin = std::next(list.begin(), before);
+ auto end = std::next(list.begin(), idx + 1);
+ std::rotate(begin, end - 1, end);
+ }
+}
+
+static RegisterDemand getLiveChanges(aco_ptr<Instruction>& instr)
+{
+ RegisterDemand changes;
+ for (const Definition& def : instr->definitions) {
+ if (!def.isTemp() || def.isKill())
+ continue;
+ changes += def.getTemp();
+ }
+
+ for (const Operand& op : instr->operands) {
+ if (!op.isTemp() || !op.isFirstKill())
+ continue;
+ changes -= op.getTemp();
+ }
+
+ return changes;
+}
+
+static RegisterDemand getTempRegisters(aco_ptr<Instruction>& instr)
+{
+ RegisterDemand temp_registers;
+ for (const Definition& def : instr->definitions) {
+ if (!def.isTemp() || !def.isKill())
+ continue;
+ temp_registers += def.getTemp();
+ }
+ return temp_registers;
+}
+
+static bool is_spill_reload(aco_ptr<Instruction>& instr)
+{
+ return instr->opcode == aco_opcode::p_spill || instr->opcode == aco_opcode::p_reload;
+}
+
+bool can_move_instr(aco_ptr<Instruction>& instr, Instruction* current, int moving_interaction)
+{
+ /* don't move exports so that they stay closer together */
+ if (instr->format == Format::EXP)
+ return false;
+
+ /* handle barriers */
+
+ /* TODO: instead of stopping, maybe try to move the barriers and any
+ * instructions interacting with them instead? */
+ if (instr->format != Format::PSEUDO_BARRIER) {
+ if (instr->opcode == aco_opcode::s_barrier) {
+ bool can_reorder = false;
+ switch (current->format) {
+ case Format::SMEM:
+ can_reorder = static_cast<SMEM_instruction*>(current)->can_reorder;
+ break;
+ case Format::MUBUF:
+ can_reorder = static_cast<MUBUF_instruction*>(current)->can_reorder;
+ break;
+ case Format::MIMG:
+ can_reorder = static_cast<MIMG_instruction*>(current)->can_reorder;
+ break;
+ default:
+ break;
+ }
+ return can_reorder && moving_interaction == barrier_none;
+ } else {
+ return true;
+ }
+ }
+
+ int interaction = get_barrier_interaction(current);
+ interaction |= moving_interaction;
+
+ switch (instr->opcode) {
+ case aco_opcode::p_memory_barrier_atomic:
+ return !(interaction & barrier_atomic);
+ /* For now, buffer and image barriers are treated the same. this is because of
+ * dEQP-VK.memory_model.message_passing.core11.u32.coherent.fence_fence.atomicwrite.device.payload_nonlocal.buffer.guard_nonlocal.image.comp
+ * which seems to use an image load to determine if the result of a buffer load is valid. So the ordering of the two loads is important.
+ * I /think/ we should probably eventually expand the meaning of a buffer barrier so that all buffer operations before it, must stay before it
+ * and that both image and buffer operations after it, must stay after it. We should also do the same for image barriers.
+ * Or perhaps the problem is that we don't have a combined barrier instruction for both buffers and images, but the CTS test expects us to?
+ * Either way, this solution should work. */
+ case aco_opcode::p_memory_barrier_buffer:
+ case aco_opcode::p_memory_barrier_image:
+ return !(interaction & (barrier_image | barrier_buffer));
+ case aco_opcode::p_memory_barrier_shared:
+ return !(interaction & barrier_shared);
+ case aco_opcode::p_memory_barrier_all:
+ return interaction == barrier_none;
+ default:
+ return false;
+ }
+}
+
+bool can_reorder(Instruction* candidate, bool allow_smem)
+{
+ switch (candidate->format) {
+ case Format::SMEM:
+ return allow_smem || static_cast<SMEM_instruction*>(candidate)->can_reorder;
+ case Format::MUBUF:
+ return static_cast<MUBUF_instruction*>(candidate)->can_reorder;
+ case Format::MIMG:
+ return static_cast<MIMG_instruction*>(candidate)->can_reorder;
+ case Format::MTBUF:
+ return static_cast<MTBUF_instruction*>(candidate)->can_reorder;
+ case Format::FLAT:
+ case Format::GLOBAL:
+ case Format::SCRATCH:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void schedule_SMEM(sched_ctx& ctx, Block* block,
+ std::vector<RegisterDemand>& register_demand,
+ Instruction* current, int idx)
+{
+ assert(idx != 0);
+ int window_size = SMEM_WINDOW_SIZE;
+ int max_moves = SMEM_MAX_MOVES;
+ int16_t k = 0;
+ bool can_reorder_cur = can_reorder(current, false);
+
+ /* create the initial set of values which current depends on */
+ std::fill(ctx.depends_on.begin(), ctx.depends_on.end(), false);
+ for (const Operand& op : current->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+
+ /* maintain how many registers remain free when moving instructions */
+ RegisterDemand register_pressure = register_demand[idx];
+
+ /* first, check if we have instructions before current to move down */
+ int insert_idx = idx + 1;
+ int moving_interaction = barrier_none;
+ bool moving_spill = false;
+
+ for (int candidate_idx = idx - 1; k < max_moves && candidate_idx > (int) idx - window_size; candidate_idx--) {
+ assert(candidate_idx >= 0);
+ aco_ptr<Instruction>& candidate = block->instructions[candidate_idx];
+
+ /* break if we'd make the previous SMEM instruction stall */
+ bool can_stall_prev_smem = idx <= ctx.last_SMEM_dep_idx && candidate_idx < ctx.last_SMEM_dep_idx;
+ if (can_stall_prev_smem && ctx.last_SMEM_stall >= 0)
+ break;
+
+ /* break when encountering another MEM instruction, logical_start or barriers */
+ if (!can_reorder(candidate.get(), false) && !can_reorder_cur)
+ break;
+ if (candidate->opcode == aco_opcode::p_logical_start)
+ break;
+ if (!can_move_instr(candidate, current, moving_interaction))
+ break;
+ register_pressure.update(register_demand[candidate_idx]);
+
+ /* if current depends on candidate, add additional dependencies and continue */
+ bool can_move_down = true;
+ bool writes_exec = false;
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp() && ctx.depends_on[def.tempId()])
+ can_move_down = false;
+ if (def.isFixed() && def.physReg() == exec)
+ writes_exec = true;
+ }
+ if (writes_exec)
+ break;
+
+ if (moving_spill && is_spill_reload(candidate))
+ can_move_down = false;
+ if ((moving_interaction & barrier_shared) && candidate->format == Format::DS)
+ can_move_down = false;
+ moving_interaction |= get_barrier_interaction(candidate.get());
+ moving_spill |= is_spill_reload(candidate);
+ if (!can_move_down) {
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ bool register_pressure_unknown = false;
+ /* check if one of candidate's operands is killed by depending instruction */
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp() && ctx.depends_on[op.tempId()]) {
+ // FIXME: account for difference in register pressure
+ register_pressure_unknown = true;
+ }
+ }
+ if (register_pressure_unknown) {
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ /* check if register pressure is low enough: the diff is negative if register pressure is decreased */
+ const RegisterDemand candidate_diff = getLiveChanges(candidate);
+ const RegisterDemand tempDemand = getTempRegisters(candidate);
+ if (RegisterDemand(register_pressure - candidate_diff).exceeds(ctx.max_registers))
+ break;
+ const RegisterDemand tempDemand2 = getTempRegisters(block->instructions[insert_idx - 1]);
+ const RegisterDemand new_demand = register_demand[insert_idx - 1] - tempDemand2 + tempDemand;
+ if (new_demand.exceeds(ctx.max_registers))
+ break;
+ // TODO: we might want to look further to find a sequence of instructions to move down which doesn't exceed reg pressure
+
+ /* move the candidate below the memory load */
+ move_element(block->instructions, candidate_idx, insert_idx);
+
+ /* update register pressure */
+ move_element(register_demand, candidate_idx, insert_idx);
+ for (int i = candidate_idx; i < insert_idx - 1; i++) {
+ register_demand[i] -= candidate_diff;
+ }
+ register_demand[insert_idx - 1] = new_demand;
+ register_pressure -= candidate_diff;
+
+ if (candidate_idx < ctx.last_SMEM_dep_idx)
+ ctx.last_SMEM_stall++;
+ insert_idx--;
+ k++;
+ }
+
+ /* create the initial set of values which depend on current */
+ std::fill(ctx.depends_on.begin(), ctx.depends_on.end(), false);
+ std::fill(ctx.RAR_dependencies.begin(), ctx.RAR_dependencies.end(), false);
+ for (const Definition& def : current->definitions) {
+ if (def.isTemp())
+ ctx.depends_on[def.tempId()] = true;
+ }
+
+ /* find the first instruction depending on current or find another MEM */
+ insert_idx = idx + 1;
+ moving_interaction = barrier_none;
+ moving_spill = false;
+
+ bool found_dependency = false;
+ /* second, check if we have instructions after current to move up */
+ for (int candidate_idx = idx + 1; k < max_moves && candidate_idx < (int) idx + window_size; candidate_idx++) {
+ assert(candidate_idx < (int) block->instructions.size());
+ aco_ptr<Instruction>& candidate = block->instructions[candidate_idx];
+
+ if (candidate->opcode == aco_opcode::p_logical_end)
+ break;
+ if (!can_move_instr(candidate, current, moving_interaction))
+ break;
+
+ const bool writes_exec = std::any_of(candidate->definitions.begin(), candidate->definitions.end(),
+ [](const Definition& def) { return def.isFixed() && def.physReg() == exec;});
+ if (writes_exec)
+ break;
+
+ /* check if candidate depends on current */
+ bool is_dependency = std::any_of(candidate->operands.begin(), candidate->operands.end(),
+ [&ctx](const Operand& op) { return op.isTemp() && ctx.depends_on[op.tempId()];});
+ if (moving_spill && is_spill_reload(candidate))
+ is_dependency = true;
+ if ((moving_interaction & barrier_shared) && candidate->format == Format::DS)
+ is_dependency = true;
+ moving_interaction |= get_barrier_interaction(candidate.get());
+ moving_spill |= is_spill_reload(candidate);
+ if (is_dependency) {
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp())
+ ctx.depends_on[def.tempId()] = true;
+ }
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.RAR_dependencies[op.tempId()] = true;
+ }
+ if (!found_dependency) {
+ insert_idx = candidate_idx;
+ found_dependency = true;
+ /* init register pressure */
+ register_pressure = register_demand[insert_idx - 1];
+ }
+ }
+
+ if (!can_reorder(candidate.get(), false) && !can_reorder_cur)
+ break;
+
+ if (!found_dependency) {
+ k++;
+ continue;
+ }
+
+ /* update register pressure */
+ register_pressure.update(register_demand[candidate_idx - 1]);
+
+ if (is_dependency)
+ continue;
+ assert(insert_idx != idx);
+
+ // TODO: correctly calculate register pressure for this case
+ bool register_pressure_unknown = false;
+ /* check if candidate uses/kills an operand which is used by a dependency */
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp() && ctx.RAR_dependencies[op.tempId()])
+ register_pressure_unknown = true;
+ }
+ if (register_pressure_unknown) {
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp())
+ ctx.RAR_dependencies[def.tempId()] = true;
+ }
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.RAR_dependencies[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ /* check if register pressure is low enough: the diff is negative if register pressure is decreased */
+ const RegisterDemand candidate_diff = getLiveChanges(candidate);
+ const RegisterDemand temp = getTempRegisters(candidate);
+ if (RegisterDemand(register_pressure + candidate_diff).exceeds(ctx.max_registers))
+ break;
+ const RegisterDemand temp2 = getTempRegisters(block->instructions[insert_idx - 1]);
+ const RegisterDemand new_demand = register_demand[insert_idx - 1] - temp2 + candidate_diff + temp;
+ if (new_demand.exceeds(ctx.max_registers))
+ break;
+
+ /* move the candidate above the insert_idx */
+ move_element(block->instructions, candidate_idx, insert_idx);
+
+ /* update register pressure */
+ move_element(register_demand, candidate_idx, insert_idx);
+ for (int i = insert_idx + 1; i <= candidate_idx; i++) {
+ register_demand[i] += candidate_diff;
+ }
+ register_demand[insert_idx] = new_demand;
+ register_pressure += candidate_diff;
+ insert_idx++;
+ k++;
+ }
+
+ ctx.last_SMEM_dep_idx = found_dependency ? insert_idx : 0;
+ ctx.last_SMEM_stall = 10 - ctx.num_waves - k;
+}
+
+void schedule_VMEM(sched_ctx& ctx, Block* block,
+ std::vector<RegisterDemand>& register_demand,
+ Instruction* current, int idx)
+{
+ assert(idx != 0);
+ int window_size = VMEM_WINDOW_SIZE;
+ int max_moves = VMEM_MAX_MOVES;
+ int16_t k = 0;
+ bool can_reorder_cur = can_reorder(current, false);
+
+ /* create the initial set of values which current depends on */
+ std::fill(ctx.depends_on.begin(), ctx.depends_on.end(), false);
+ for (const Operand& op : current->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+
+ /* maintain how many registers remain free when moving instructions */
+ RegisterDemand register_pressure = register_demand[idx];
+
+ /* first, check if we have instructions before current to move down */
+ int insert_idx = idx + 1;
+ int moving_interaction = barrier_none;
+ bool moving_spill = false;
+
+ for (int candidate_idx = idx - 1; k < max_moves && candidate_idx > (int) idx - window_size; candidate_idx--) {
+ assert(candidate_idx >= 0);
+ aco_ptr<Instruction>& candidate = block->instructions[candidate_idx];
+
+ /* break when encountering another VMEM instruction, logical_start or barriers */
+ if (!can_reorder(candidate.get(), true) && !can_reorder_cur)
+ break;
+ if (candidate->opcode == aco_opcode::p_logical_start)
+ break;
+ if (!can_move_instr(candidate, current, moving_interaction))
+ break;
+
+ /* break if we'd make the previous SMEM instruction stall */
+ bool can_stall_prev_smem = idx <= ctx.last_SMEM_dep_idx && candidate_idx < ctx.last_SMEM_dep_idx;
+ if (can_stall_prev_smem && ctx.last_SMEM_stall >= 0)
+ break;
+ register_pressure.update(register_demand[candidate_idx]);
+
+ /* if current depends on candidate, add additional dependencies and continue */
+ bool can_move_down = true;
+ bool writes_exec = false;
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp() && ctx.depends_on[def.tempId()])
+ can_move_down = false;
+ if (def.isFixed() && def.physReg() == exec)
+ writes_exec = true;
+ }
+ if (writes_exec)
+ break;
+
+ if (moving_spill && is_spill_reload(candidate))
+ can_move_down = false;
+ if ((moving_interaction & barrier_shared) && candidate->format == Format::DS)
+ can_move_down = false;
+ moving_interaction |= get_barrier_interaction(candidate.get());
+ moving_spill |= is_spill_reload(candidate);
+ if (!can_move_down) {
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ bool register_pressure_unknown = false;
+ /* check if one of candidate's operands is killed by depending instruction */
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp() && ctx.depends_on[op.tempId()]) {
+ // FIXME: account for difference in register pressure
+ register_pressure_unknown = true;
+ }
+ }
+ if (register_pressure_unknown) {
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.depends_on[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ /* check if register pressure is low enough: the diff is negative if register pressure is decreased */
+ const RegisterDemand candidate_diff = getLiveChanges(candidate);
+ const RegisterDemand temp = getTempRegisters(candidate);;
+ if (RegisterDemand(register_pressure - candidate_diff).exceeds(ctx.max_registers))
+ break;
+ const RegisterDemand temp2 = getTempRegisters(block->instructions[insert_idx - 1]);
+ const RegisterDemand new_demand = register_demand[insert_idx - 1] - temp2 + temp;
+ if (new_demand.exceeds(ctx.max_registers))
+ break;
+ // TODO: we might want to look further to find a sequence of instructions to move down which doesn't exceed reg pressure
+
+ /* move the candidate below the memory load */
+ move_element(block->instructions, candidate_idx, insert_idx);
+
+ /* update register pressure */
+ move_element(register_demand, candidate_idx, insert_idx);
+ for (int i = candidate_idx; i < insert_idx - 1; i++) {
+ register_demand[i] -= candidate_diff;
+ }
+ register_demand[insert_idx - 1] = new_demand;
+ register_pressure -= candidate_diff;
+ insert_idx--;
+ k++;
+ if (candidate_idx < ctx.last_SMEM_dep_idx)
+ ctx.last_SMEM_stall++;
+ }
+
+ /* create the initial set of values which depend on current */
+ std::fill(ctx.depends_on.begin(), ctx.depends_on.end(), false);
+ std::fill(ctx.RAR_dependencies.begin(), ctx.RAR_dependencies.end(), false);
+ for (const Definition& def : current->definitions) {
+ if (def.isTemp())
+ ctx.depends_on[def.tempId()] = true;
+ }
+
+ /* find the first instruction depending on current or find another VMEM */
+ insert_idx = idx;
+ moving_interaction = barrier_none;
+ moving_spill = false;
+
+ bool found_dependency = false;
+ /* second, check if we have instructions after current to move up */
+ for (int candidate_idx = idx + 1; k < max_moves && candidate_idx < (int) idx + window_size; candidate_idx++) {
+ assert(candidate_idx < (int) block->instructions.size());
+ aco_ptr<Instruction>& candidate = block->instructions[candidate_idx];
+
+ if (candidate->opcode == aco_opcode::p_logical_end)
+ break;
+ if (!can_move_instr(candidate, current, moving_interaction))
+ break;
+
+ const bool writes_exec = std::any_of(candidate->definitions.begin(), candidate->definitions.end(),
+ [](const Definition& def) {return def.isFixed() && def.physReg() == exec; });
+ if (writes_exec)
+ break;
+
+ /* check if candidate depends on current */
+ bool is_dependency = !can_reorder(candidate.get(), true) && !can_reorder_cur;
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp() && ctx.depends_on[op.tempId()]) {
+ is_dependency = true;
+ break;
+ }
+ }
+ if (moving_spill && is_spill_reload(candidate))
+ is_dependency = true;
+ if ((moving_interaction & barrier_shared) && candidate->format == Format::DS)
+ is_dependency = true;
+ moving_interaction |= get_barrier_interaction(candidate.get());
+ moving_spill |= is_spill_reload(candidate);
+ if (is_dependency) {
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp())
+ ctx.depends_on[def.tempId()] = true;
+ }
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.RAR_dependencies[op.tempId()] = true;
+ }
+ if (!found_dependency) {
+ insert_idx = candidate_idx;
+ found_dependency = true;
+ /* init register pressure */
+ register_pressure = register_demand[insert_idx - 1];
+ continue;
+ }
+ }
+
+ /* update register pressure */
+ register_pressure.update(register_demand[candidate_idx - 1]);
+
+ if (is_dependency || !found_dependency)
+ continue;
+ assert(insert_idx != idx);
+
+ bool register_pressure_unknown = false;
+ /* check if candidate uses/kills an operand which is used by a dependency */
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp() && ctx.RAR_dependencies[op.tempId()])
+ register_pressure_unknown = true;
+ }
+ if (register_pressure_unknown) {
+ for (const Definition& def : candidate->definitions) {
+ if (def.isTemp())
+ ctx.RAR_dependencies[def.tempId()] = true;
+ }
+ for (const Operand& op : candidate->operands) {
+ if (op.isTemp())
+ ctx.RAR_dependencies[op.tempId()] = true;
+ }
+ continue;
+ }
+
+ /* check if register pressure is low enough: the diff is negative if register pressure is decreased */
+ const RegisterDemand candidate_diff = getLiveChanges(candidate);
+ const RegisterDemand temp = getTempRegisters(candidate);
+ if (RegisterDemand(register_pressure + candidate_diff).exceeds(ctx.max_registers))
+ break;
+ const RegisterDemand temp2 = getTempRegisters(block->instructions[insert_idx - 1]);
+ const RegisterDemand new_demand = register_demand[insert_idx - 1] - temp2 + candidate_diff + temp;
+ if (new_demand.exceeds(ctx.max_registers))
+ break;
+
+ /* move the candidate above the insert_idx */
+ move_element(block->instructions, candidate_idx, insert_idx);
+
+ /* update register pressure */
+ move_element(register_demand, candidate_idx, insert_idx);
+ for (int i = insert_idx + 1; i <= candidate_idx; i++) {
+ register_demand[i] += candidate_diff;
+ }
+ register_demand[insert_idx] = new_demand;
+ register_pressure += candidate_diff;
+ insert_idx++;
+ k++;
+ }
+}
+
+void schedule_position_export(sched_ctx& ctx, Block* block,
+ std::vector<RegisterDemand>& register_demand,
+ Instruction* current, int idx)
+{
+ assert(idx != 0);
+ int window_size = POS_EXP_WINDOW_SIZE;
+ int max_moves = POS_EXP_MAX_MOVES;
+ int16_t k = 0;
+
+ /* create the initial set of values which current depends on */
+ std::fill(ctx.depends_on.begin(), ctx.depends_on.end(), false);
+ for (unsigned i = 0; i < current->operands.size(); i++) {
+ if (current->operands[i].isTemp())
+ ctx.depends_on[current->operands[i].tempId()] = true;
+ }
+
+ /* maintain how many registers remain free when moving instructions */
+ RegisterDemand register_pressure = register_demand[idx];
+
+ /* first, check if we have instructions before current to move down */
+ int insert_idx = idx + 1;
+ int moving_interaction = barrier_none;
+ bool moving_spill = false;
+
+ for (int candidate_idx = idx - 1; k < max_moves && candidate_idx > (int) idx - window_size; candidate_idx--) {
+ assert(candidate_idx >= 0);
+ aco_ptr<Instruction>& candidate = block->instructions[candidate_idx];
+
+ /* break when encountering logical_start or barriers */
+ if (candidate->opcode == aco_opcode::p_logical_start)
+ break;
+ if (candidate->isVMEM() || candidate->format == Format::SMEM)
+ break;
+ if (!can_move_instr(candidate, current, moving_interaction))
+ break;
+
+ register_pressure.update(register_demand[candidate_idx]);
+
+ /* if current depends on candidate, add additional dependencies and continue */
+ bool can_move_down = true;
+ bool writes_exec = false;
+ for (unsigned i = 0; i < candidate->definitions.size(); i++) {
+ if (candidate->definitions[i].isTemp() && ctx.depends_on[candidate->definitions[i].tempId()])
+ can_move_down = false;
+ if (candidate->definitions[i].isFixed() && candidate->definitions[i].physReg() == exec)
+ writes_exec = true;
+ }
+ if (writes_exec)
+ break;
+
+ if (moving_spill && is_spill_reload(candidate))
+ can_move_down = false;
+ if ((moving_interaction & barrier_shared) && candidate->format == Format::DS)
+ can_move_down = false;
+ moving_interaction |= get_barrier_interaction(candidate.get());
+ moving_spill |= is_spill_reload(candidate);
+ if (!can_move_down) {
+ for (unsigned i = 0; i < candidate->operands.size(); i++) {
+ if (candidate->operands[i].isTemp())
+ ctx.depends_on[candidate->operands[i].tempId()] = true;
+ }
+ continue;
+ }
+
+ bool register_pressure_unknown = false;
+ /* check if one of candidate's operands is killed by depending instruction */
+ for (unsigned i = 0; i < candidate->operands.size(); i++) {
+ if (candidate->operands[i].isTemp() && ctx.depends_on[candidate->operands[i].tempId()]) {
+ // FIXME: account for difference in register pressure
+ register_pressure_unknown = true;
+ }
+ }
+ if (register_pressure_unknown) {
+ for (unsigned i = 0; i < candidate->operands.size(); i++) {
+ if (candidate->operands[i].isTemp())
+ ctx.depends_on[candidate->operands[i].tempId()] = true;
+ }
+ continue;
+ }
+
+ /* check if register pressure is low enough: the diff is negative if register pressure is decreased */
+ const RegisterDemand candidate_diff = getLiveChanges(candidate);
+ const RegisterDemand temp = getTempRegisters(candidate);;
+ if (RegisterDemand(register_pressure - candidate_diff).exceeds(ctx.max_registers))
+ break;
+ const RegisterDemand temp2 = getTempRegisters(block->instructions[insert_idx - 1]);
+ const RegisterDemand new_demand = register_demand[insert_idx - 1] - temp2 + temp;
+ if (new_demand.exceeds(ctx.max_registers))
+ break;
+ // TODO: we might want to look further to find a sequence of instructions to move down which doesn't exceed reg pressure
+
+ /* move the candidate below the export */
+ move_element(block->instructions, candidate_idx, insert_idx);
+
+ /* update register pressure */
+ move_element(register_demand, candidate_idx, insert_idx);
+ for (int i = candidate_idx; i < insert_idx - 1; i++) {
+ register_demand[i] -= candidate_diff;
+ }
+ register_demand[insert_idx - 1] = new_demand;
+ register_pressure -= candidate_diff;
+ insert_idx--;
+ k++;
+ }
+}
+
+void schedule_block(sched_ctx& ctx, Program *program, Block* block, live& live_vars)
+{
+ ctx.last_SMEM_dep_idx = 0;
+ ctx.last_SMEM_stall = INT16_MIN;
+
+ /* go through all instructions and find memory loads */
+ for (unsigned idx = 0; idx < block->instructions.size(); idx++) {
+ Instruction* current = block->instructions[idx].get();
+
+ if (current->definitions.empty())
+ continue;
+
+ if (current->isVMEM())
+ schedule_VMEM(ctx, block, live_vars.register_demand[block->index], current, idx);
+ if (current->format == Format::SMEM)
+ schedule_SMEM(ctx, block, live_vars.register_demand[block->index], current, idx);
+ }
+
+ if ((program->stage & hw_vs) && block->index == program->blocks.size() - 1) {
+ /* Try to move position exports as far up as possible, to reduce register
+ * usage and because ISA reference guides say so. */
+ for (unsigned idx = 0; idx < block->instructions.size(); idx++) {
+ Instruction* current = block->instructions[idx].get();
+
+ if (current->format == Format::EXP) {
+ unsigned target = static_cast<Export_instruction*>(current)->dest;
+ if (target >= V_008DFC_SQ_EXP_POS && target < V_008DFC_SQ_EXP_PARAM)
+ schedule_position_export(ctx, block, live_vars.register_demand[block->index], current, idx);
+ }
+ }
+ }
+
+ /* resummarize the block's register demand */
+ block->register_demand = RegisterDemand();
+ for (unsigned idx = 0; idx < block->instructions.size(); idx++) {
+ block->register_demand.update(live_vars.register_demand[block->index][idx]);
+ }
+}
+
+
+void schedule_program(Program *program, live& live_vars)
+{
+ sched_ctx ctx;
+ ctx.depends_on.resize(program->peekAllocationId());
+ ctx.RAR_dependencies.resize(program->peekAllocationId());
+ /* Allowing the scheduler to reduce the number of waves to as low as 5
+ * improves performance of Thrones of Britannia significantly and doesn't
+ * seem to hurt anything else. */
+ //TODO: maybe use some sort of heuristic instead
+ //TODO: this also increases window-size/max-moves? did I realize that at the time?
+ ctx.num_waves = std::min<uint16_t>(program->num_waves, 5);
+ assert(ctx.num_waves);
+ uint16_t total_sgpr_regs = program->chip_class >= GFX8 ? 800 : 512;
+ uint16_t max_addressible_sgpr = program->sgpr_limit;
+ ctx.max_registers = { int16_t(((256 / ctx.num_waves) & ~3) - 2), std::min<int16_t>(((total_sgpr_regs / ctx.num_waves) & ~7) - 2, max_addressible_sgpr)};
+
+ for (Block& block : program->blocks)
+ schedule_block(ctx, program, &block, live_vars);
+
+ /* update max_reg_demand and num_waves */
+ RegisterDemand new_demand;
+ for (Block& block : program->blocks) {
+ new_demand.update(block.register_demand);
+ }
+ update_vgpr_sgpr_demand(program, new_demand);
+
+ /* if enabled, this code asserts that register_demand is updated correctly */
+ #if 0
+ int prev_num_waves = program->num_waves;
+ const RegisterDemand prev_max_demand = program->max_reg_demand;
+
+ std::vector<RegisterDemand> demands(program->blocks.size());
+ for (unsigned j = 0; j < program->blocks.size(); j++) {
+ demands[j] = program->blocks[j].register_demand;
+ }
+
+ struct radv_nir_compiler_options options;
+ options.chip_class = program->chip_class;
+ live live_vars2 = aco::live_var_analysis(program, &options);
+
+ for (unsigned j = 0; j < program->blocks.size(); j++) {
+ Block &b = program->blocks[j];
+ for (unsigned i = 0; i < b.instructions.size(); i++)
+ assert(live_vars.register_demand[b.index][i] == live_vars2.register_demand[b.index][i]);
+ assert(b.register_demand == demands[j]);
+ }
+
+ assert(program->max_reg_demand == prev_max_demand);
+ assert(program->num_waves == prev_num_waves);
+ #endif
+}
+
+}
diff --git a/src/amd/compiler/aco_spill.cpp b/src/amd/compiler/aco_spill.cpp
new file mode 100644
index 00000000000..92a23bb355c
--- /dev/null
+++ b/src/amd/compiler/aco_spill.cpp
@@ -0,0 +1,1630 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ * Copyright © 2018 Google
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+#include <map>
+#include <stack>
+#include "vulkan/radv_shader.h"
+
+
+/*
+ * Implements the spilling algorithm on SSA-form from
+ * "Register Spilling and Live-Range Splitting for SSA-Form Programs"
+ * by Matthias Braun and Sebastian Hack.
+ */
+
+namespace aco {
+
+namespace {
+
+struct remat_info {
+ Instruction *instr;
+};
+
+struct spill_ctx {
+ RegisterDemand target_pressure;
+ Program* program;
+ std::vector<std::vector<RegisterDemand>> register_demand;
+ std::vector<std::map<Temp, Temp>> renames;
+ std::vector<std::map<Temp, uint32_t>> spills_entry;
+ std::vector<std::map<Temp, uint32_t>> spills_exit;
+ std::vector<bool> processed;
+ std::stack<Block*> loop_header;
+ std::vector<std::map<Temp, std::pair<uint32_t, uint32_t>>> next_use_distances_start;
+ std::vector<std::map<Temp, std::pair<uint32_t, uint32_t>>> next_use_distances_end;
+ std::vector<std::pair<RegClass, std::set<uint32_t>>> interferences;
+ std::vector<std::pair<uint32_t, uint32_t>> affinities;
+ std::vector<bool> is_reloaded;
+ std::map<Temp, remat_info> remat;
+ std::map<Instruction *, bool> remat_used;
+
+ spill_ctx(const RegisterDemand target_pressure, Program* program,
+ std::vector<std::vector<RegisterDemand>> register_demand)
+ : target_pressure(target_pressure), program(program),
+ register_demand(register_demand), renames(program->blocks.size()),
+ spills_entry(program->blocks.size()), spills_exit(program->blocks.size()),
+ processed(program->blocks.size(), false) {}
+
+ uint32_t allocate_spill_id(RegClass rc)
+ {
+ interferences.emplace_back(rc, std::set<uint32_t>());
+ is_reloaded.push_back(false);
+ return next_spill_id++;
+ }
+
+ uint32_t next_spill_id = 0;
+};
+
+int32_t get_dominator(int idx_a, int idx_b, Program* program, bool is_linear)
+{
+
+ if (idx_a == -1)
+ return idx_b;
+ if (idx_b == -1)
+ return idx_a;
+ if (is_linear) {
+ while (idx_a != idx_b) {
+ if (idx_a > idx_b)
+ idx_a = program->blocks[idx_a].linear_idom;
+ else
+ idx_b = program->blocks[idx_b].linear_idom;
+ }
+ } else {
+ while (idx_a != idx_b) {
+ if (idx_a > idx_b)
+ idx_a = program->blocks[idx_a].logical_idom;
+ else
+ idx_b = program->blocks[idx_b].logical_idom;
+ }
+ }
+ assert(idx_a != -1);
+ return idx_a;
+}
+
+void next_uses_per_block(spill_ctx& ctx, unsigned block_idx, std::set<uint32_t>& worklist)
+{
+ Block* block = &ctx.program->blocks[block_idx];
+ std::map<Temp, std::pair<uint32_t, uint32_t>> next_uses = ctx.next_use_distances_end[block_idx];
+
+ /* to compute the next use distance at the beginning of the block, we have to add the block's size */
+ for (std::map<Temp, std::pair<uint32_t, uint32_t>>::iterator it = next_uses.begin(); it != next_uses.end();) {
+ it->second.second = it->second.second + block->instructions.size();
+
+ /* remove the live out exec mask as we really don't want to spill it */
+ if (it->first == block->live_out_exec)
+ it = next_uses.erase(it);
+ else
+ ++it;
+ }
+
+ int idx = block->instructions.size() - 1;
+ while (idx >= 0) {
+ aco_ptr<Instruction>& instr = block->instructions[idx];
+
+ if (instr->opcode == aco_opcode::p_linear_phi ||
+ instr->opcode == aco_opcode::p_phi)
+ break;
+
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp())
+ next_uses.erase(def.getTemp());
+ }
+
+ for (const Operand& op : instr->operands) {
+ /* omit exec mask */
+ if (op.isFixed() && op.physReg() == exec)
+ continue;
+ if (op.isTemp())
+ next_uses[op.getTemp()] = {block_idx, idx};
+ }
+ idx--;
+ }
+
+ assert(block_idx != 0 || next_uses.empty());
+ ctx.next_use_distances_start[block_idx] = next_uses;
+ while (idx >= 0) {
+ aco_ptr<Instruction>& instr = block->instructions[idx];
+ assert(instr->opcode == aco_opcode::p_linear_phi || instr->opcode == aco_opcode::p_phi);
+
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ unsigned pred_idx = instr->opcode == aco_opcode::p_phi ?
+ block->logical_preds[i] :
+ block->linear_preds[i];
+ if (instr->operands[i].isTemp()) {
+ if (ctx.next_use_distances_end[pred_idx].find(instr->operands[i].getTemp()) == ctx.next_use_distances_end[pred_idx].end() ||
+ ctx.next_use_distances_end[pred_idx][instr->operands[i].getTemp()] != std::pair<uint32_t, uint32_t>{block_idx, 0})
+ worklist.insert(pred_idx);
+ ctx.next_use_distances_end[pred_idx][instr->operands[i].getTemp()] = {block_idx, 0};
+ }
+ }
+ next_uses.erase(instr->definitions[0].getTemp());
+ idx--;
+ }
+
+ /* all remaining live vars must be live-out at the predecessors */
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : next_uses) {
+ Temp temp = pair.first;
+ uint32_t distance = pair.second.second;
+ uint32_t dom = pair.second.first;
+ std::vector<unsigned>& preds = temp.is_linear() ? block->linear_preds : block->logical_preds;
+ for (unsigned pred_idx : preds) {
+ if (ctx.program->blocks[pred_idx].loop_nest_depth > block->loop_nest_depth)
+ distance += 0xFFFF;
+ if (ctx.next_use_distances_end[pred_idx].find(temp) != ctx.next_use_distances_end[pred_idx].end()) {
+ dom = get_dominator(dom, ctx.next_use_distances_end[pred_idx][temp].first, ctx.program, temp.is_linear());
+ distance = std::min(ctx.next_use_distances_end[pred_idx][temp].second, distance);
+ }
+ if (ctx.next_use_distances_end[pred_idx][temp] != std::pair<uint32_t, uint32_t>{dom, distance})
+ worklist.insert(pred_idx);
+ ctx.next_use_distances_end[pred_idx][temp] = {dom, distance};
+ }
+ }
+
+}
+
+void compute_global_next_uses(spill_ctx& ctx, std::vector<std::set<Temp>>& live_out)
+{
+ ctx.next_use_distances_start.resize(ctx.program->blocks.size());
+ ctx.next_use_distances_end.resize(ctx.program->blocks.size());
+ std::set<uint32_t> worklist;
+ for (Block& block : ctx.program->blocks)
+ worklist.insert(block.index);
+
+ while (!worklist.empty()) {
+ std::set<unsigned>::reverse_iterator b_it = worklist.rbegin();
+ unsigned block_idx = *b_it;
+ worklist.erase(block_idx);
+ next_uses_per_block(ctx, block_idx, worklist);
+ }
+}
+
+bool should_rematerialize(aco_ptr<Instruction>& instr)
+{
+ /* TODO: rematerialization is only supported for VOP1, SOP1 and PSEUDO */
+ if (instr->format != Format::VOP1 && instr->format != Format::SOP1 && instr->format != Format::PSEUDO)
+ return false;
+ /* TODO: pseudo-instruction rematerialization is only supported for p_create_vector */
+ if (instr->format == Format::PSEUDO && instr->opcode != aco_opcode::p_create_vector)
+ return false;
+
+ for (const Operand& op : instr->operands) {
+ /* TODO: rematerialization using temporaries isn't yet supported */
+ if (op.isTemp())
+ return false;
+ }
+
+ /* TODO: rematerialization with multiple definitions isn't yet supported */
+ if (instr->definitions.size() > 1)
+ return false;
+
+ return true;
+}
+
+aco_ptr<Instruction> do_reload(spill_ctx& ctx, Temp tmp, Temp new_name, uint32_t spill_id)
+{
+ std::map<Temp, remat_info>::iterator remat = ctx.remat.find(tmp);
+ if (remat != ctx.remat.end()) {
+ Instruction *instr = remat->second.instr;
+ assert((instr->format == Format::VOP1 || instr->format == Format::SOP1 || instr->format == Format::PSEUDO) && "unsupported");
+ assert((instr->format != Format::PSEUDO || instr->opcode == aco_opcode::p_create_vector) && "unsupported");
+ assert(instr->definitions.size() == 1 && "unsupported");
+
+ aco_ptr<Instruction> res;
+ if (instr->format == Format::VOP1) {
+ res.reset(create_instruction<VOP1_instruction>(instr->opcode, instr->format, instr->operands.size(), instr->definitions.size()));
+ } else if (instr->format == Format::SOP1) {
+ res.reset(create_instruction<SOP1_instruction>(instr->opcode, instr->format, instr->operands.size(), instr->definitions.size()));
+ } else if (instr->format == Format::PSEUDO) {
+ res.reset(create_instruction<Instruction>(instr->opcode, instr->format, instr->operands.size(), instr->definitions.size()));
+ }
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ res->operands[i] = instr->operands[i];
+ if (instr->operands[i].isTemp()) {
+ assert(false && "unsupported");
+ if (ctx.remat.count(instr->operands[i].getTemp()))
+ ctx.remat_used[ctx.remat[instr->operands[i].getTemp()].instr] = true;
+ }
+ }
+ res->definitions[0] = Definition(new_name);
+ return res;
+ } else {
+ aco_ptr<Pseudo_instruction> reload{create_instruction<Pseudo_instruction>(aco_opcode::p_reload, Format::PSEUDO, 1, 1)};
+ reload->operands[0] = Operand(spill_id);
+ reload->definitions[0] = Definition(new_name);
+ ctx.is_reloaded[spill_id] = true;
+ return reload;
+ }
+}
+
+void get_rematerialize_info(spill_ctx& ctx)
+{
+ for (Block& block : ctx.program->blocks) {
+ bool logical = false;
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ if (instr->opcode == aco_opcode::p_logical_start)
+ logical = true;
+ else if (instr->opcode == aco_opcode::p_logical_end)
+ logical = false;
+ if (logical && should_rematerialize(instr)) {
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp()) {
+ ctx.remat[def.getTemp()] = (remat_info){instr.get()};
+ ctx.remat_used[instr.get()] = false;
+ }
+ }
+ }
+ }
+ }
+}
+
+std::vector<std::map<Temp, uint32_t>> local_next_uses(spill_ctx& ctx, Block* block)
+{
+ std::vector<std::map<Temp, uint32_t>> local_next_uses(block->instructions.size());
+
+ std::map<Temp, uint32_t> next_uses;
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_end[block->index]) {
+ /* omit live out exec mask */
+ if (pair.first == block->live_out_exec)
+ continue;
+
+ next_uses[pair.first] = pair.second.second + block->instructions.size();
+ }
+
+ for (int idx = block->instructions.size() - 1; idx >= 0; idx--) {
+ aco_ptr<Instruction>& instr = block->instructions[idx];
+ if (!instr)
+ break;
+ if (instr->opcode == aco_opcode::p_phi || instr->opcode == aco_opcode::p_linear_phi)
+ break;
+
+ for (const Operand& op : instr->operands) {
+ if (op.isFixed() && op.physReg() == exec)
+ continue;
+ if (op.isTemp())
+ next_uses[op.getTemp()] = idx;
+ }
+ for (const Definition& def : instr->definitions) {
+ if (def.isTemp())
+ next_uses.erase(def.getTemp());
+ }
+ local_next_uses[idx] = next_uses;
+ }
+ return local_next_uses;
+}
+
+
+RegisterDemand init_live_in_vars(spill_ctx& ctx, Block* block, unsigned block_idx)
+{
+ RegisterDemand spilled_registers;
+
+ /* first block, nothing was spilled before */
+ if (block_idx == 0)
+ return {0, 0};
+
+ /* loop header block */
+ if (block->loop_nest_depth > ctx.program->blocks[block_idx - 1].loop_nest_depth) {
+ assert(block->linear_preds[0] == block_idx - 1);
+ assert(block->logical_preds[0] == block_idx - 1);
+
+ /* create new loop_info */
+ ctx.loop_header.emplace(block);
+
+ /* check how many live-through variables should be spilled */
+ RegisterDemand new_demand;
+ unsigned i = block_idx;
+ while (ctx.program->blocks[i].loop_nest_depth >= block->loop_nest_depth) {
+ assert(ctx.program->blocks.size() > i);
+ new_demand.update(ctx.program->blocks[i].register_demand);
+ i++;
+ }
+ unsigned loop_end = i;
+
+ /* select live-through vgpr variables */
+ while (new_demand.vgpr - spilled_registers.vgpr > ctx.target_pressure.vgpr) {
+ unsigned distance = 0;
+ Temp to_spill;
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_end[block_idx - 1]) {
+ if (pair.first.type() == RegType::vgpr &&
+ pair.second.first >= loop_end &&
+ pair.second.second > distance &&
+ ctx.spills_entry[block_idx].find(pair.first) == ctx.spills_entry[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second.second;
+ }
+ }
+ if (distance == 0)
+ break;
+
+ uint32_t spill_id;
+ if (ctx.spills_exit[block_idx - 1].find(to_spill) == ctx.spills_exit[block_idx - 1].end()) {
+ spill_id = ctx.allocate_spill_id(to_spill.regClass());
+ } else {
+ spill_id = ctx.spills_exit[block_idx - 1][to_spill];
+ }
+
+ ctx.spills_entry[block_idx][to_spill] = spill_id;
+ spilled_registers.vgpr += to_spill.size();
+ }
+
+ /* select live-through sgpr variables */
+ while (new_demand.sgpr - spilled_registers.sgpr > ctx.target_pressure.sgpr) {
+ unsigned distance = 0;
+ Temp to_spill;
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_end[block_idx - 1]) {
+ if (pair.first.type() == RegType::sgpr &&
+ pair.second.first >= loop_end &&
+ pair.second.second > distance &&
+ ctx.spills_entry[block_idx].find(pair.first) == ctx.spills_entry[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second.second;
+ }
+ }
+ if (distance == 0)
+ break;
+
+ uint32_t spill_id;
+ if (ctx.spills_exit[block_idx - 1].find(to_spill) == ctx.spills_exit[block_idx - 1].end()) {
+ spill_id = ctx.allocate_spill_id(to_spill.regClass());
+ } else {
+ spill_id = ctx.spills_exit[block_idx - 1][to_spill];
+ }
+
+ ctx.spills_entry[block_idx][to_spill] = spill_id;
+ spilled_registers.sgpr += to_spill.size();
+ }
+
+
+
+ /* shortcut */
+ if (!RegisterDemand(new_demand - spilled_registers).exceeds(ctx.target_pressure))
+ return spilled_registers;
+
+ /* if reg pressure is too high at beginning of loop, add variables with furthest use */
+ unsigned idx = 0;
+ while (block->instructions[idx]->opcode == aco_opcode::p_phi || block->instructions[idx]->opcode == aco_opcode::p_linear_phi)
+ idx++;
+
+ assert(idx != 0 && "loop without phis: TODO");
+ idx--;
+ RegisterDemand reg_pressure = ctx.register_demand[block_idx][idx] - spilled_registers;
+ while (reg_pressure.sgpr > ctx.target_pressure.sgpr) {
+ unsigned distance = 0;
+ Temp to_spill;
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_start[block_idx]) {
+ if (pair.first.type() == RegType::sgpr &&
+ pair.second.second > distance &&
+ ctx.spills_entry[block_idx].find(pair.first) == ctx.spills_entry[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second.second;
+ }
+ }
+ assert(distance != 0);
+
+ ctx.spills_entry[block_idx][to_spill] = ctx.allocate_spill_id(to_spill.regClass());
+ spilled_registers.sgpr += to_spill.size();
+ reg_pressure.sgpr -= to_spill.size();
+ }
+ while (reg_pressure.vgpr > ctx.target_pressure.vgpr) {
+ unsigned distance = 0;
+ Temp to_spill;
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_start[block_idx]) {
+ if (pair.first.type() == RegType::vgpr &&
+ pair.second.second > distance &&
+ ctx.spills_entry[block_idx].find(pair.first) == ctx.spills_entry[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second.second;
+ }
+ }
+ assert(distance != 0);
+ ctx.spills_entry[block_idx][to_spill] = ctx.allocate_spill_id(to_spill.regClass());
+ spilled_registers.vgpr += to_spill.size();
+ reg_pressure.vgpr -= to_spill.size();
+ }
+
+ return spilled_registers;
+ }
+
+ /* branch block */
+ if (block->linear_preds.size() == 1) {
+ /* keep variables spilled if they are alive and not used in the current block */
+ unsigned pred_idx = block->linear_preds[0];
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ if (pair.first.type() == RegType::sgpr &&
+ ctx.next_use_distances_start[block_idx].find(pair.first) != ctx.next_use_distances_start[block_idx].end() &&
+ ctx.next_use_distances_start[block_idx][pair.first].second > block_idx) {
+ ctx.spills_entry[block_idx].insert(pair);
+ spilled_registers.sgpr += pair.first.size();
+ }
+ }
+ if (block->logical_preds.size() == 1) {
+ pred_idx = block->logical_preds[0];
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ if (pair.first.type() == RegType::vgpr &&
+ ctx.next_use_distances_start[block_idx].find(pair.first) != ctx.next_use_distances_start[block_idx].end() &&
+ ctx.next_use_distances_end[pred_idx][pair.first].second > block_idx) {
+ ctx.spills_entry[block_idx].insert(pair);
+ spilled_registers.vgpr += pair.first.size();
+ }
+ }
+ }
+
+ /* if register demand is still too high, we just keep all spilled live vars and process the block */
+ if (block->register_demand.sgpr - spilled_registers.sgpr > ctx.target_pressure.sgpr) {
+ pred_idx = block->linear_preds[0];
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ if (pair.first.type() == RegType::sgpr &&
+ ctx.next_use_distances_start[block_idx].find(pair.first) != ctx.next_use_distances_start[block_idx].end() &&
+ ctx.spills_entry[block_idx].insert(pair).second) {
+ spilled_registers.sgpr += pair.first.size();
+ }
+ }
+ }
+ if (block->register_demand.vgpr - spilled_registers.vgpr > ctx.target_pressure.vgpr && block->logical_preds.size() == 1) {
+ pred_idx = block->logical_preds[0];
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ if (pair.first.type() == RegType::vgpr &&
+ ctx.next_use_distances_start[block_idx].find(pair.first) != ctx.next_use_distances_start[block_idx].end() &&
+ ctx.spills_entry[block_idx].insert(pair).second) {
+ spilled_registers.vgpr += pair.first.size();
+ }
+ }
+ }
+
+ return spilled_registers;
+ }
+
+ /* else: merge block */
+ std::set<Temp> partial_spills;
+
+ /* keep variables spilled on all incoming paths */
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_start[block_idx]) {
+ std::vector<unsigned>& preds = pair.first.type() == RegType::vgpr ? block->logical_preds : block->linear_preds;
+ /* If it can be rematerialized, keep the variable spilled if all predecessors do not reload it.
+ * Otherwise, if any predecessor reloads it, ensure it's reloaded on all other predecessors.
+ * The idea is that it's better in practice to rematerialize redundantly than to create lots of phis. */
+ /* TODO: test this idea with more than Dawn of War III shaders (the current pipeline-db doesn't seem to exercise this path much) */
+ bool remat = ctx.remat.count(pair.first);
+ bool spill = !remat;
+ uint32_t spill_id = 0;
+ for (unsigned pred_idx : preds) {
+ /* variable is not even live at the predecessor: probably from a phi */
+ if (ctx.next_use_distances_end[pred_idx].find(pair.first) == ctx.next_use_distances_end[pred_idx].end()) {
+ spill = false;
+ break;
+ }
+ if (ctx.spills_exit[pred_idx].find(pair.first) == ctx.spills_exit[pred_idx].end()) {
+ if (!remat)
+ spill = false;
+ } else {
+ partial_spills.insert(pair.first);
+ /* it might be that on one incoming path, the variable has a different spill_id, but add_couple_code() will take care of that. */
+ spill_id = ctx.spills_exit[pred_idx][pair.first];
+ if (remat)
+ spill = true;
+ }
+ }
+ if (spill) {
+ ctx.spills_entry[block_idx][pair.first] = spill_id;
+ partial_spills.erase(pair.first);
+ spilled_registers += pair.first;
+ }
+ }
+
+ /* same for phis */
+ unsigned idx = 0;
+ while (block->instructions[idx]->opcode == aco_opcode::p_linear_phi ||
+ block->instructions[idx]->opcode == aco_opcode::p_phi) {
+ aco_ptr<Instruction>& phi = block->instructions[idx];
+ std::vector<unsigned>& preds = phi->opcode == aco_opcode::p_phi ? block->logical_preds : block->linear_preds;
+ bool spill = true;
+
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ if (!phi->operands[i].isTemp())
+ spill = false;
+ else if (ctx.spills_exit[preds[i]].find(phi->operands[i].getTemp()) == ctx.spills_exit[preds[i]].end())
+ spill = false;
+ else
+ partial_spills.insert(phi->definitions[0].getTemp());
+ }
+ if (spill) {
+ ctx.spills_entry[block_idx][phi->definitions[0].getTemp()] = ctx.allocate_spill_id(phi->definitions[0].regClass());
+ partial_spills.erase(phi->definitions[0].getTemp());
+ spilled_registers += phi->definitions[0].getTemp();
+ }
+
+ idx++;
+ }
+
+ /* if reg pressure at first instruction is still too high, add partially spilled variables */
+ RegisterDemand reg_pressure;
+ if (idx == 0) {
+ for (const Definition& def : block->instructions[idx]->definitions) {
+ if (def.isTemp()) {
+ reg_pressure -= def.getTemp();
+ }
+ }
+ for (const Operand& op : block->instructions[idx]->operands) {
+ if (op.isTemp() && op.isFirstKill()) {
+ reg_pressure += op.getTemp();
+ }
+ }
+ } else {
+ idx--;
+ }
+ reg_pressure += ctx.register_demand[block_idx][idx] - spilled_registers;
+
+ while (reg_pressure.sgpr > ctx.target_pressure.sgpr) {
+ assert(!partial_spills.empty());
+
+ std::set<Temp>::iterator it = partial_spills.begin();
+ Temp to_spill = *it;
+ unsigned distance = ctx.next_use_distances_start[block_idx][*it].second;
+ while (it != partial_spills.end()) {
+ assert(ctx.spills_entry[block_idx].find(*it) == ctx.spills_entry[block_idx].end());
+
+ if (it->type() == RegType::sgpr && ctx.next_use_distances_start[block_idx][*it].second > distance) {
+ distance = ctx.next_use_distances_start[block_idx][*it].second;
+ to_spill = *it;
+ }
+ ++it;
+ }
+ assert(distance != 0);
+
+ ctx.spills_entry[block_idx][to_spill] = ctx.allocate_spill_id(to_spill.regClass());
+ partial_spills.erase(to_spill);
+ spilled_registers.sgpr += to_spill.size();
+ reg_pressure.sgpr -= to_spill.size();
+ }
+
+ while (reg_pressure.vgpr > ctx.target_pressure.vgpr) {
+ assert(!partial_spills.empty());
+
+ std::set<Temp>::iterator it = partial_spills.begin();
+ Temp to_spill = *it;
+ unsigned distance = ctx.next_use_distances_start[block_idx][*it].second;
+ while (it != partial_spills.end()) {
+ assert(ctx.spills_entry[block_idx].find(*it) == ctx.spills_entry[block_idx].end());
+
+ if (it->type() == RegType::vgpr && ctx.next_use_distances_start[block_idx][*it].second > distance) {
+ distance = ctx.next_use_distances_start[block_idx][*it].second;
+ to_spill = *it;
+ }
+ ++it;
+ }
+ assert(distance != 0);
+
+ ctx.spills_entry[block_idx][to_spill] = ctx.allocate_spill_id(to_spill.regClass());
+ partial_spills.erase(to_spill);
+ spilled_registers.vgpr += to_spill.size();
+ reg_pressure.vgpr -= to_spill.size();
+ }
+
+ return spilled_registers;
+}
+
+
+void add_coupling_code(spill_ctx& ctx, Block* block, unsigned block_idx)
+{
+ /* no coupling code necessary */
+ if (block->linear_preds.size() == 0)
+ return;
+
+ std::vector<aco_ptr<Instruction>> instructions;
+ /* branch block: TODO take other branch into consideration */
+ if (block->linear_preds.size() == 1) {
+ assert(ctx.processed[block->linear_preds[0]]);
+
+ if (block->logical_preds.size() == 1) {
+ unsigned pred_idx = block->logical_preds[0];
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> live : ctx.next_use_distances_start[block_idx]) {
+ if (live.first.type() == RegType::sgpr)
+ continue;
+ /* still spilled */
+ if (ctx.spills_entry[block_idx].find(live.first) != ctx.spills_entry[block_idx].end())
+ continue;
+
+ /* in register at end of predecessor */
+ if (ctx.spills_exit[pred_idx].find(live.first) == ctx.spills_exit[pred_idx].end()) {
+ std::map<Temp, Temp>::iterator it = ctx.renames[pred_idx].find(live.first);
+ if (it != ctx.renames[pred_idx].end())
+ ctx.renames[block_idx].insert(*it);
+ continue;
+ }
+
+ /* variable is spilled at predecessor and live at current block: create reload instruction */
+ Temp new_name = {ctx.program->allocateId(), live.first.regClass()};
+ aco_ptr<Instruction> reload = do_reload(ctx, live.first, new_name, ctx.spills_exit[pred_idx][live.first]);
+ instructions.emplace_back(std::move(reload));
+ ctx.renames[block_idx][live.first] = new_name;
+ }
+ }
+
+ unsigned pred_idx = block->linear_preds[0];
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> live : ctx.next_use_distances_start[block_idx]) {
+ if (live.first.type() == RegType::vgpr)
+ continue;
+ /* still spilled */
+ if (ctx.spills_entry[block_idx].find(live.first) != ctx.spills_entry[block_idx].end())
+ continue;
+
+ /* in register at end of predecessor */
+ if (ctx.spills_exit[pred_idx].find(live.first) == ctx.spills_exit[pred_idx].end()) {
+ std::map<Temp, Temp>::iterator it = ctx.renames[pred_idx].find(live.first);
+ if (it != ctx.renames[pred_idx].end())
+ ctx.renames[block_idx].insert(*it);
+ continue;
+ }
+
+ /* variable is spilled at predecessor and live at current block: create reload instruction */
+ Temp new_name = {ctx.program->allocateId(), live.first.regClass()};
+ aco_ptr<Instruction> reload = do_reload(ctx, live.first, new_name, ctx.spills_exit[pred_idx][live.first]);
+ instructions.emplace_back(std::move(reload));
+ ctx.renames[block_idx][live.first] = new_name;
+ }
+
+ /* combine new reload instructions with original block */
+ if (!instructions.empty()) {
+ unsigned insert_idx = 0;
+ while (block->instructions[insert_idx]->opcode == aco_opcode::p_phi ||
+ block->instructions[insert_idx]->opcode == aco_opcode::p_linear_phi) {
+ insert_idx++;
+ }
+ ctx.register_demand[block->index].insert(std::next(ctx.register_demand[block->index].begin(), insert_idx),
+ instructions.size(), RegisterDemand());
+ block->instructions.insert(std::next(block->instructions.begin(), insert_idx),
+ std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(instructions.begin()),
+ std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(instructions.end()));
+ }
+ return;
+ }
+
+ /* loop header and merge blocks: check if all (linear) predecessors have been processed */
+ for (ASSERTED unsigned pred : block->linear_preds)
+ assert(ctx.processed[pred]);
+
+ /* iterate the phi nodes for which operands to spill at the predecessor */
+ for (aco_ptr<Instruction>& phi : block->instructions) {
+ if (phi->opcode != aco_opcode::p_phi &&
+ phi->opcode != aco_opcode::p_linear_phi)
+ break;
+
+ /* if the phi is not spilled, add to instructions */
+ if (ctx.spills_entry[block_idx].find(phi->definitions[0].getTemp()) == ctx.spills_entry[block_idx].end()) {
+ instructions.emplace_back(std::move(phi));
+ continue;
+ }
+
+ std::vector<unsigned>& preds = phi->opcode == aco_opcode::p_phi ? block->logical_preds : block->linear_preds;
+ uint32_t def_spill_id = ctx.spills_entry[block_idx][phi->definitions[0].getTemp()];
+
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ unsigned pred_idx = preds[i];
+
+ /* we have to spill constants to the same memory address */
+ if (phi->operands[i].isConstant()) {
+ uint32_t spill_id = ctx.allocate_spill_id(phi->definitions[0].regClass());
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ ctx.interferences[def_spill_id].second.emplace(pair.second);
+ ctx.interferences[pair.second].second.emplace(def_spill_id);
+ }
+ ctx.affinities.emplace_back(std::pair<uint32_t, uint32_t>{def_spill_id, spill_id});
+
+ aco_ptr<Pseudo_instruction> spill{create_instruction<Pseudo_instruction>(aco_opcode::p_spill, Format::PSEUDO, 2, 0)};
+ spill->operands[0] = phi->operands[i];
+ spill->operands[1] = Operand(spill_id);
+ Block& pred = ctx.program->blocks[pred_idx];
+ unsigned idx = pred.instructions.size();
+ do {
+ assert(idx != 0);
+ idx--;
+ } while (phi->opcode == aco_opcode::p_phi && pred.instructions[idx]->opcode != aco_opcode::p_logical_end);
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(pred.instructions.begin(), idx);
+ pred.instructions.insert(it, std::move(spill));
+ continue;
+ }
+ if (!phi->operands[i].isTemp())
+ continue;
+
+ /* build interferences between the phi def and all spilled variables at the predecessor blocks */
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[pred_idx]) {
+ if (phi->operands[i].getTemp() == pair.first)
+ continue;
+ ctx.interferences[def_spill_id].second.emplace(pair.second);
+ ctx.interferences[pair.second].second.emplace(def_spill_id);
+ }
+
+ /* variable is already spilled at predecessor */
+ std::map<Temp, uint32_t>::iterator spilled = ctx.spills_exit[pred_idx].find(phi->operands[i].getTemp());
+ if (spilled != ctx.spills_exit[pred_idx].end()) {
+ if (spilled->second != def_spill_id)
+ ctx.affinities.emplace_back(std::pair<uint32_t, uint32_t>{def_spill_id, spilled->second});
+ continue;
+ }
+
+ /* rename if necessary */
+ Temp var = phi->operands[i].getTemp();
+ std::map<Temp, Temp>::iterator rename_it = ctx.renames[pred_idx].find(var);
+ if (rename_it != ctx.renames[pred_idx].end()) {
+ var = rename_it->second;
+ ctx.renames[pred_idx].erase(rename_it);
+ }
+
+ uint32_t spill_id = ctx.allocate_spill_id(phi->definitions[0].regClass());
+ ctx.affinities.emplace_back(std::pair<uint32_t, uint32_t>{def_spill_id, spill_id});
+ aco_ptr<Pseudo_instruction> spill{create_instruction<Pseudo_instruction>(aco_opcode::p_spill, Format::PSEUDO, 2, 0)};
+ spill->operands[0] = Operand(var);
+ spill->operands[1] = Operand(spill_id);
+ Block& pred = ctx.program->blocks[pred_idx];
+ unsigned idx = pred.instructions.size();
+ do {
+ assert(idx != 0);
+ idx--;
+ } while (phi->opcode == aco_opcode::p_phi && pred.instructions[idx]->opcode != aco_opcode::p_logical_end);
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(pred.instructions.begin(), idx);
+ pred.instructions.insert(it, std::move(spill));
+ ctx.spills_exit[pred_idx][phi->operands[i].getTemp()] = spill_id;
+ }
+
+ /* remove phi from instructions */
+ phi.reset();
+ }
+
+ /* iterate all (other) spilled variables for which to spill at the predecessor */
+ // TODO: would be better to have them sorted: first vgprs and first with longest distance
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_entry[block_idx]) {
+ std::vector<unsigned> preds = pair.first.type() == RegType::vgpr ? block->logical_preds : block->linear_preds;
+
+ for (unsigned pred_idx : preds) {
+ /* add interferences between spilled variable and predecessors exit spills */
+ for (std::pair<Temp, uint32_t> exit_spill : ctx.spills_exit[pred_idx]) {
+ if (exit_spill.first == pair.first)
+ continue;
+ ctx.interferences[exit_spill.second].second.emplace(pair.second);
+ ctx.interferences[pair.second].second.emplace(exit_spill.second);
+ }
+
+ /* variable is already spilled at predecessor */
+ std::map<Temp, uint32_t>::iterator spilled = ctx.spills_exit[pred_idx].find(pair.first);
+ if (spilled != ctx.spills_exit[pred_idx].end()) {
+ if (spilled->second != pair.second)
+ ctx.affinities.emplace_back(std::pair<uint32_t, uint32_t>{pair.second, spilled->second});
+ continue;
+ }
+
+ /* variable is dead at predecessor, it must be from a phi: this works because of CSSA form */ // FIXME: lower_to_cssa()
+ if (ctx.next_use_distances_end[pred_idx].find(pair.first) == ctx.next_use_distances_end[pred_idx].end())
+ continue;
+
+ /* variable is in register at predecessor and has to be spilled */
+ /* rename if necessary */
+ Temp var = pair.first;
+ std::map<Temp, Temp>::iterator rename_it = ctx.renames[pred_idx].find(var);
+ if (rename_it != ctx.renames[pred_idx].end()) {
+ var = rename_it->second;
+ ctx.renames[pred_idx].erase(rename_it);
+ }
+
+ aco_ptr<Pseudo_instruction> spill{create_instruction<Pseudo_instruction>(aco_opcode::p_spill, Format::PSEUDO, 2, 0)};
+ spill->operands[0] = Operand(var);
+ spill->operands[1] = Operand(pair.second);
+ Block& pred = ctx.program->blocks[pred_idx];
+ unsigned idx = pred.instructions.size();
+ do {
+ assert(idx != 0);
+ idx--;
+ } while (pair.first.type() == RegType::vgpr && pred.instructions[idx]->opcode != aco_opcode::p_logical_end);
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(pred.instructions.begin(), idx);
+ pred.instructions.insert(it, std::move(spill));
+ ctx.spills_exit[pred.index][pair.first] = pair.second;
+ }
+ }
+
+ /* iterate phis for which operands to reload */
+ for (aco_ptr<Instruction>& phi : instructions) {
+ assert(phi->opcode == aco_opcode::p_phi || phi->opcode == aco_opcode::p_linear_phi);
+ assert(ctx.spills_entry[block_idx].find(phi->definitions[0].getTemp()) == ctx.spills_entry[block_idx].end());
+
+ std::vector<unsigned>& preds = phi->opcode == aco_opcode::p_phi ? block->logical_preds : block->linear_preds;
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ if (!phi->operands[i].isTemp())
+ continue;
+ unsigned pred_idx = preds[i];
+
+ /* rename operand */
+ if (ctx.spills_exit[pred_idx].find(phi->operands[i].getTemp()) == ctx.spills_exit[pred_idx].end()) {
+ std::map<Temp, Temp>::iterator it = ctx.renames[pred_idx].find(phi->operands[i].getTemp());
+ if (it != ctx.renames[pred_idx].end())
+ phi->operands[i].setTemp(it->second);
+ continue;
+ }
+
+ Temp tmp = phi->operands[i].getTemp();
+
+ /* reload phi operand at end of predecessor block */
+ Temp new_name = {ctx.program->allocateId(), tmp.regClass()};
+ Block& pred = ctx.program->blocks[pred_idx];
+ unsigned idx = pred.instructions.size();
+ do {
+ assert(idx != 0);
+ idx--;
+ } while (phi->opcode == aco_opcode::p_phi && pred.instructions[idx]->opcode != aco_opcode::p_logical_end);
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(pred.instructions.begin(), idx);
+
+ aco_ptr<Instruction> reload = do_reload(ctx, tmp, new_name, ctx.spills_exit[pred_idx][tmp]);
+ pred.instructions.insert(it, std::move(reload));
+
+ ctx.spills_exit[pred_idx].erase(tmp);
+ ctx.renames[pred_idx][tmp] = new_name;
+ phi->operands[i].setTemp(new_name);
+ }
+ }
+
+ /* iterate live variables for which to reload */
+ // TODO: reload at current block if variable is spilled on all predecessors
+ for (std::pair<Temp, std::pair<uint32_t, uint32_t>> pair : ctx.next_use_distances_start[block_idx]) {
+ /* skip spilled variables */
+ if (ctx.spills_entry[block_idx].find(pair.first) != ctx.spills_entry[block_idx].end())
+ continue;
+ std::vector<unsigned> preds = pair.first.type() == RegType::vgpr ? block->logical_preds : block->linear_preds;
+
+ /* variable is dead at predecessor, it must be from a phi */
+ bool is_dead = false;
+ for (unsigned pred_idx : preds) {
+ if (ctx.next_use_distances_end[pred_idx].find(pair.first) == ctx.next_use_distances_end[pred_idx].end())
+ is_dead = true;
+ }
+ if (is_dead)
+ continue;
+ for (unsigned pred_idx : preds) {
+ /* the variable is not spilled at the predecessor */
+ if (ctx.spills_exit[pred_idx].find(pair.first) == ctx.spills_exit[pred_idx].end())
+ continue;
+
+ /* variable is spilled at predecessor and has to be reloaded */
+ Temp new_name = {ctx.program->allocateId(), pair.first.regClass()};
+ Block& pred = ctx.program->blocks[pred_idx];
+ unsigned idx = pred.instructions.size();
+ do {
+ assert(idx != 0);
+ idx--;
+ } while (pair.first.type() == RegType::vgpr && pred.instructions[idx]->opcode != aco_opcode::p_logical_end);
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(pred.instructions.begin(), idx);
+
+ aco_ptr<Instruction> reload = do_reload(ctx, pair.first, new_name, ctx.spills_exit[pred.index][pair.first]);
+ pred.instructions.insert(it, std::move(reload));
+
+ ctx.spills_exit[pred.index].erase(pair.first);
+ ctx.renames[pred.index][pair.first] = new_name;
+ }
+
+ /* check if we have to create a new phi for this variable */
+ Temp rename = Temp();
+ bool is_same = true;
+ for (unsigned pred_idx : preds) {
+ if (ctx.renames[pred_idx].find(pair.first) == ctx.renames[pred_idx].end()) {
+ if (rename == Temp())
+ rename = pair.first;
+ else
+ is_same = rename == pair.first;
+ } else {
+ if (rename == Temp())
+ rename = ctx.renames[pred_idx][pair.first];
+ else
+ is_same = rename == ctx.renames[pred_idx][pair.first];
+ }
+
+ if (!is_same)
+ break;
+ }
+
+ if (!is_same) {
+ /* the variable was renamed differently in the predecessors: we have to create a phi */
+ aco_opcode opcode = pair.first.type() == RegType::vgpr ? aco_opcode::p_phi : aco_opcode::p_linear_phi;
+ aco_ptr<Pseudo_instruction> phi{create_instruction<Pseudo_instruction>(opcode, Format::PSEUDO, preds.size(), 1)};
+ rename = {ctx.program->allocateId(), pair.first.regClass()};
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ Temp tmp;
+ if (ctx.renames[preds[i]].find(pair.first) != ctx.renames[preds[i]].end())
+ tmp = ctx.renames[preds[i]][pair.first];
+ else if (preds[i] >= block_idx)
+ tmp = rename;
+ else
+ tmp = pair.first;
+ phi->operands[i] = Operand(tmp);
+ }
+ phi->definitions[0] = Definition(rename);
+ instructions.emplace_back(std::move(phi));
+ }
+
+ /* the variable was renamed: add new name to renames */
+ if (!(rename == Temp() || rename == pair.first))
+ ctx.renames[block_idx][pair.first] = rename;
+ }
+
+ /* combine phis with instructions */
+ unsigned idx = 0;
+ while (!block->instructions[idx]) {
+ idx++;
+ }
+
+ ctx.register_demand[block->index].erase(ctx.register_demand[block->index].begin(), ctx.register_demand[block->index].begin() + idx);
+ ctx.register_demand[block->index].insert(ctx.register_demand[block->index].begin(), instructions.size(), RegisterDemand());
+
+ std::vector<aco_ptr<Instruction>>::iterator start = std::next(block->instructions.begin(), idx);
+ instructions.insert(instructions.end(), std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(start),
+ std::move_iterator<std::vector<aco_ptr<Instruction>>::iterator>(block->instructions.end()));
+ block->instructions = std::move(instructions);
+}
+
+void process_block(spill_ctx& ctx, unsigned block_idx, Block* block,
+ std::map<Temp, uint32_t> &current_spills, RegisterDemand spilled_registers)
+{
+ std::vector<std::map<Temp, uint32_t>> local_next_use_distance;
+ std::vector<aco_ptr<Instruction>> instructions;
+ unsigned idx = 0;
+
+ /* phis are handled separetely */
+ while (block->instructions[idx]->opcode == aco_opcode::p_phi ||
+ block->instructions[idx]->opcode == aco_opcode::p_linear_phi) {
+ aco_ptr<Instruction>& instr = block->instructions[idx];
+ for (const Operand& op : instr->operands) {
+ /* prevent it's definining instruction from being DCE'd if it could be rematerialized */
+ if (op.isTemp() && ctx.remat.count(op.getTemp()))
+ ctx.remat_used[ctx.remat[op.getTemp()].instr] = true;
+ }
+ instructions.emplace_back(std::move(instr));
+ idx++;
+ }
+
+ if (block->register_demand.exceeds(ctx.target_pressure))
+ local_next_use_distance = local_next_uses(ctx, block);
+
+ while (idx < block->instructions.size()) {
+ aco_ptr<Instruction>& instr = block->instructions[idx];
+
+ std::map<Temp, std::pair<Temp, uint32_t>> reloads;
+ std::map<Temp, uint32_t> spills;
+ /* rename and reload operands */
+ for (Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ if (current_spills.find(op.getTemp()) == current_spills.end()) {
+ /* the Operand is in register: check if it was renamed */
+ if (ctx.renames[block_idx].find(op.getTemp()) != ctx.renames[block_idx].end())
+ op.setTemp(ctx.renames[block_idx][op.getTemp()]);
+ /* prevent it's definining instruction from being DCE'd if it could be rematerialized */
+ if (ctx.remat.count(op.getTemp()))
+ ctx.remat_used[ctx.remat[op.getTemp()].instr] = true;
+ continue;
+ }
+ /* the Operand is spilled: add it to reloads */
+ Temp new_tmp = {ctx.program->allocateId(), op.regClass()};
+ ctx.renames[block_idx][op.getTemp()] = new_tmp;
+ reloads[new_tmp] = std::make_pair(op.getTemp(), current_spills[op.getTemp()]);
+ current_spills.erase(op.getTemp());
+ op.setTemp(new_tmp);
+ spilled_registers -= new_tmp;
+ }
+
+ /* check if register demand is low enough before and after the current instruction */
+ if (block->register_demand.exceeds(ctx.target_pressure)) {
+
+ RegisterDemand new_demand = ctx.register_demand[block_idx][idx];
+ if (idx == 0) {
+ for (const Definition& def : instr->definitions) {
+ if (!def.isTemp())
+ continue;
+ new_demand += def.getTemp();
+ }
+ } else {
+ new_demand.update(ctx.register_demand[block_idx][idx - 1]);
+ }
+
+ assert(!local_next_use_distance.empty());
+
+ /* if reg pressure is too high, spill variable with furthest next use */
+ while (RegisterDemand(new_demand - spilled_registers).exceeds(ctx.target_pressure)) {
+ unsigned distance = 0;
+ Temp to_spill;
+ bool do_rematerialize = false;
+ if (new_demand.vgpr - spilled_registers.vgpr > ctx.target_pressure.vgpr) {
+ for (std::pair<Temp, uint32_t> pair : local_next_use_distance[idx]) {
+ bool can_rematerialize = ctx.remat.count(pair.first);
+ if (pair.first.type() == RegType::vgpr &&
+ ((pair.second > distance && can_rematerialize == do_rematerialize) ||
+ (can_rematerialize && !do_rematerialize && pair.second > idx)) &&
+ current_spills.find(pair.first) == current_spills.end() &&
+ ctx.spills_exit[block_idx].find(pair.first) == ctx.spills_exit[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second;
+ do_rematerialize = can_rematerialize;
+ }
+ }
+ } else {
+ for (std::pair<Temp, uint32_t> pair : local_next_use_distance[idx]) {
+ bool can_rematerialize = ctx.remat.count(pair.first);
+ if (pair.first.type() == RegType::sgpr &&
+ ((pair.second > distance && can_rematerialize == do_rematerialize) ||
+ (can_rematerialize && !do_rematerialize && pair.second > idx)) &&
+ current_spills.find(pair.first) == current_spills.end() &&
+ ctx.spills_exit[block_idx].find(pair.first) == ctx.spills_exit[block_idx].end()) {
+ to_spill = pair.first;
+ distance = pair.second;
+ do_rematerialize = can_rematerialize;
+ }
+ }
+ }
+
+ assert(distance != 0 && distance > idx);
+ uint32_t spill_id = ctx.allocate_spill_id(to_spill.regClass());
+
+ /* add interferences with currently spilled variables */
+ for (std::pair<Temp, uint32_t> pair : current_spills) {
+ ctx.interferences[spill_id].second.emplace(pair.second);
+ ctx.interferences[pair.second].second.emplace(spill_id);
+ }
+ for (std::pair<Temp, std::pair<Temp, uint32_t>> pair : reloads) {
+ ctx.interferences[spill_id].second.emplace(pair.second.second);
+ ctx.interferences[pair.second.second].second.emplace(spill_id);
+ }
+
+ current_spills[to_spill] = spill_id;
+ spilled_registers += to_spill;
+
+ /* rename if necessary */
+ if (ctx.renames[block_idx].find(to_spill) != ctx.renames[block_idx].end()) {
+ to_spill = ctx.renames[block_idx][to_spill];
+ }
+
+ /* add spill to new instructions */
+ aco_ptr<Pseudo_instruction> spill{create_instruction<Pseudo_instruction>(aco_opcode::p_spill, Format::PSEUDO, 2, 0)};
+ spill->operands[0] = Operand(to_spill);
+ spill->operands[1] = Operand(spill_id);
+ instructions.emplace_back(std::move(spill));
+ }
+ }
+
+ /* add reloads and instruction to new instructions */
+ for (std::pair<Temp, std::pair<Temp, uint32_t>> pair : reloads) {
+ aco_ptr<Instruction> reload = do_reload(ctx, pair.second.first, pair.first, pair.second.second);
+ instructions.emplace_back(std::move(reload));
+ }
+ instructions.emplace_back(std::move(instr));
+ idx++;
+ }
+
+ block->instructions = std::move(instructions);
+ ctx.spills_exit[block_idx].insert(current_spills.begin(), current_spills.end());
+}
+
+void spill_block(spill_ctx& ctx, unsigned block_idx)
+{
+ Block* block = &ctx.program->blocks[block_idx];
+ ctx.processed[block_idx] = true;
+
+ /* determine set of variables which are spilled at the beginning of the block */
+ RegisterDemand spilled_registers = init_live_in_vars(ctx, block, block_idx);
+
+ /* add interferences for spilled variables */
+ for (std::pair<Temp, uint32_t> x : ctx.spills_entry[block_idx]) {
+ for (std::pair<Temp, uint32_t> y : ctx.spills_entry[block_idx])
+ if (x.second != y.second)
+ ctx.interferences[x.second].second.emplace(y.second);
+ }
+
+ bool is_loop_header = block->loop_nest_depth && ctx.loop_header.top()->index == block_idx;
+ if (!is_loop_header) {
+ /* add spill/reload code on incoming control flow edges */
+ add_coupling_code(ctx, block, block_idx);
+ }
+
+ std::map<Temp, uint32_t> current_spills = ctx.spills_entry[block_idx];
+
+ /* check conditions to process this block */
+ bool process = RegisterDemand(block->register_demand - spilled_registers).exceeds(ctx.target_pressure) ||
+ !ctx.renames[block_idx].empty() ||
+ ctx.remat_used.size();
+
+ std::map<Temp, uint32_t>::iterator it = current_spills.begin();
+ while (!process && it != current_spills.end()) {
+ if (ctx.next_use_distances_start[block_idx][it->first].first == block_idx)
+ process = true;
+ ++it;
+ }
+
+ if (process)
+ process_block(ctx, block_idx, block, current_spills, spilled_registers);
+ else
+ ctx.spills_exit[block_idx].insert(current_spills.begin(), current_spills.end());
+
+ /* check if the next block leaves the current loop */
+ if (block->loop_nest_depth == 0 || ctx.program->blocks[block_idx + 1].loop_nest_depth >= block->loop_nest_depth)
+ return;
+
+ Block* loop_header = ctx.loop_header.top();
+
+ /* preserve original renames at end of loop header block */
+ std::map<Temp, Temp> renames = std::move(ctx.renames[loop_header->index]);
+
+ /* add coupling code to all loop header predecessors */
+ add_coupling_code(ctx, loop_header, loop_header->index);
+
+ /* update remat_used for phis added in add_coupling_code() */
+ for (aco_ptr<Instruction>& instr : loop_header->instructions) {
+ if (!is_phi(instr))
+ break;
+ for (const Operand& op : instr->operands) {
+ if (op.isTemp() && ctx.remat.count(op.getTemp()))
+ ctx.remat_used[ctx.remat[op.getTemp()].instr] = true;
+ }
+ }
+
+ /* propagate new renames through loop: i.e. repair the SSA */
+ renames.swap(ctx.renames[loop_header->index]);
+ for (std::pair<Temp, Temp> rename : renames) {
+ for (unsigned idx = loop_header->index; idx <= block_idx; idx++) {
+ Block& current = ctx.program->blocks[idx];
+ std::vector<aco_ptr<Instruction>>::iterator instr_it = current.instructions.begin();
+
+ /* first rename phis */
+ while (instr_it != current.instructions.end()) {
+ aco_ptr<Instruction>& phi = *instr_it;
+ if (phi->opcode != aco_opcode::p_phi && phi->opcode != aco_opcode::p_linear_phi)
+ break;
+ /* no need to rename the loop header phis once again. this happened in add_coupling_code() */
+ if (idx == loop_header->index) {
+ instr_it++;
+ continue;
+ }
+
+ for (Operand& op : phi->operands) {
+ if (!op.isTemp())
+ continue;
+ if (op.getTemp() == rename.first)
+ op.setTemp(rename.second);
+ }
+ instr_it++;
+ }
+
+ std::map<Temp, std::pair<uint32_t, uint32_t>>::iterator it = ctx.next_use_distances_start[idx].find(rename.first);
+
+ /* variable is not live at beginning of this block */
+ if (it == ctx.next_use_distances_start[idx].end())
+ continue;
+
+ /* if the variable is live at the block's exit, add rename */
+ if (ctx.next_use_distances_end[idx].find(rename.first) != ctx.next_use_distances_end[idx].end())
+ ctx.renames[idx].insert(rename);
+
+ /* rename all uses in this block */
+ bool renamed = false;
+ while (!renamed && instr_it != current.instructions.end()) {
+ aco_ptr<Instruction>& instr = *instr_it;
+ for (Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ if (op.getTemp() == rename.first) {
+ op.setTemp(rename.second);
+ /* we can stop with this block as soon as the variable is spilled */
+ if (instr->opcode == aco_opcode::p_spill)
+ renamed = true;
+ }
+ }
+ instr_it++;
+ }
+ }
+ }
+
+ /* remove loop header info from stack */
+ ctx.loop_header.pop();
+}
+
+void assign_spill_slots(spill_ctx& ctx, unsigned spills_to_vgpr) {
+ std::map<uint32_t, uint32_t> sgpr_slot;
+ std::map<uint32_t, uint32_t> vgpr_slot;
+ std::vector<bool> is_assigned(ctx.interferences.size());
+
+ /* first, handle affinities: just merge all interferences into both spill ids */
+ for (std::pair<uint32_t, uint32_t> pair : ctx.affinities) {
+ assert(pair.first != pair.second);
+ for (uint32_t id : ctx.interferences[pair.first].second)
+ ctx.interferences[id].second.insert(pair.second);
+ for (uint32_t id : ctx.interferences[pair.second].second)
+ ctx.interferences[id].second.insert(pair.first);
+ ctx.interferences[pair.first].second.insert(ctx.interferences[pair.second].second.begin(), ctx.interferences[pair.second].second.end());
+ ctx.interferences[pair.second].second.insert(ctx.interferences[pair.first].second.begin(), ctx.interferences[pair.first].second.end());
+
+ bool reloaded = ctx.is_reloaded[pair.first] || ctx.is_reloaded[pair.second];
+ ctx.is_reloaded[pair.first] = ctx.is_reloaded[pair.second] = reloaded;
+ }
+ for (ASSERTED uint32_t i = 0; i < ctx.interferences.size(); i++)
+ for (ASSERTED uint32_t id : ctx.interferences[i].second)
+ assert(i != id);
+
+ /* for each spill slot, assign as many spill ids as possible */
+ std::vector<std::set<uint32_t>> spill_slot_interferences;
+ unsigned slot_idx = 0;
+ bool done = false;
+
+ /* assign sgpr spill slots */
+ while (!done) {
+ done = true;
+ for (unsigned id = 0; id < ctx.interferences.size(); id++) {
+ if (is_assigned[id] || !ctx.is_reloaded[id])
+ continue;
+ if (ctx.interferences[id].first.type() != RegType::sgpr)
+ continue;
+
+ /* check interferences */
+ bool interferes = false;
+ for (unsigned i = slot_idx; i < slot_idx + ctx.interferences[id].first.size(); i++) {
+ if (i == spill_slot_interferences.size())
+ spill_slot_interferences.emplace_back(std::set<uint32_t>());
+ if (spill_slot_interferences[i].find(id) != spill_slot_interferences[i].end() || i / 64 != slot_idx / 64) {
+ interferes = true;
+ break;
+ }
+ }
+ if (interferes) {
+ done = false;
+ continue;
+ }
+
+ /* we found a spill id which can be assigned to current spill slot */
+ sgpr_slot[id] = slot_idx;
+ is_assigned[id] = true;
+ for (unsigned i = slot_idx; i < slot_idx + ctx.interferences[id].first.size(); i++)
+ spill_slot_interferences[i].insert(ctx.interferences[id].second.begin(), ctx.interferences[id].second.end());
+ }
+ slot_idx++;
+ }
+
+ slot_idx = 0;
+ done = false;
+
+ /* assign vgpr spill slots */
+ while (!done) {
+ done = true;
+ for (unsigned id = 0; id < ctx.interferences.size(); id++) {
+ if (is_assigned[id] || !ctx.is_reloaded[id])
+ continue;
+ if (ctx.interferences[id].first.type() != RegType::vgpr)
+ continue;
+
+ /* check interferences */
+ bool interferes = false;
+ for (unsigned i = slot_idx; i < slot_idx + ctx.interferences[id].first.size(); i++) {
+ if (i == spill_slot_interferences.size())
+ spill_slot_interferences.emplace_back(std::set<uint32_t>());
+ /* check for interference and ensure that vector regs are stored next to each other */
+ if (spill_slot_interferences[i].find(id) != spill_slot_interferences[i].end() || i / 64 != slot_idx / 64) {
+ interferes = true;
+ break;
+ }
+ }
+ if (interferes) {
+ done = false;
+ continue;
+ }
+
+ /* we found a spill id which can be assigned to current spill slot */
+ vgpr_slot[id] = slot_idx;
+ is_assigned[id] = true;
+ for (unsigned i = slot_idx; i < slot_idx + ctx.interferences[id].first.size(); i++)
+ spill_slot_interferences[i].insert(ctx.interferences[id].second.begin(), ctx.interferences[id].second.end());
+ }
+ slot_idx++;
+ }
+
+ for (unsigned id = 0; id < is_assigned.size(); id++)
+ assert(is_assigned[id] || !ctx.is_reloaded[id]);
+
+ for (std::pair<uint32_t, uint32_t> pair : ctx.affinities) {
+ assert(is_assigned[pair.first] == is_assigned[pair.second]);
+ if (!is_assigned[pair.first])
+ continue;
+ assert(ctx.is_reloaded[pair.first] == ctx.is_reloaded[pair.second]);
+ assert(ctx.interferences[pair.first].first.type() == ctx.interferences[pair.second].first.type());
+ if (ctx.interferences[pair.first].first.type() == RegType::sgpr)
+ assert(sgpr_slot[pair.first] == sgpr_slot[pair.second]);
+ else
+ assert(vgpr_slot[pair.first] == vgpr_slot[pair.second]);
+ }
+
+ /* hope, we didn't mess up */
+ std::vector<Temp> vgpr_spill_temps((spill_slot_interferences.size() + 63) / 64);
+ assert(vgpr_spill_temps.size() <= spills_to_vgpr);
+
+ /* replace pseudo instructions with actual hardware instructions */
+ unsigned last_top_level_block_idx = 0;
+ std::vector<bool> reload_in_loop(vgpr_spill_temps.size());
+ for (Block& block : ctx.program->blocks) {
+
+ /* after loops, we insert a user if there was a reload inside the loop */
+ if (block.loop_nest_depth == 0) {
+ int end_vgprs = 0;
+ for (unsigned i = 0; i < vgpr_spill_temps.size(); i++) {
+ if (reload_in_loop[i])
+ end_vgprs++;
+ }
+
+ if (end_vgprs > 0) {
+ aco_ptr<Instruction> destr{create_instruction<Pseudo_instruction>(aco_opcode::p_end_linear_vgpr, Format::PSEUDO, end_vgprs, 0)};
+ int k = 0;
+ for (unsigned i = 0; i < vgpr_spill_temps.size(); i++) {
+ if (reload_in_loop[i])
+ destr->operands[k++] = Operand(vgpr_spill_temps[i]);
+ reload_in_loop[i] = false;
+ }
+ /* find insertion point */
+ std::vector<aco_ptr<Instruction>>::iterator it = block.instructions.begin();
+ while ((*it)->opcode == aco_opcode::p_linear_phi || (*it)->opcode == aco_opcode::p_phi)
+ ++it;
+ block.instructions.insert(it, std::move(destr));
+ }
+ }
+
+ if (block.kind & block_kind_top_level && !block.linear_preds.empty()) {
+ last_top_level_block_idx = block.index;
+
+ /* check if any spilled variables use a created linear vgpr, otherwise destroy them */
+ for (unsigned i = 0; i < vgpr_spill_temps.size(); i++) {
+ if (vgpr_spill_temps[i] == Temp())
+ continue;
+
+ bool can_destroy = true;
+ for (std::pair<Temp, uint32_t> pair : ctx.spills_exit[block.linear_preds[0]]) {
+
+ if (sgpr_slot.find(pair.second) != sgpr_slot.end() &&
+ sgpr_slot[pair.second] / 64 == i) {
+ can_destroy = false;
+ break;
+ }
+ }
+ if (can_destroy)
+ vgpr_spill_temps[i] = Temp();
+ }
+ }
+
+ std::vector<aco_ptr<Instruction>>::iterator it;
+ std::vector<aco_ptr<Instruction>> instructions;
+ instructions.reserve(block.instructions.size());
+ for (it = block.instructions.begin(); it != block.instructions.end(); ++it) {
+
+ if ((*it)->opcode == aco_opcode::p_spill) {
+ uint32_t spill_id = (*it)->operands[1].constantValue();
+
+ if (!ctx.is_reloaded[spill_id]) {
+ /* never reloaded, so don't spill */
+ } else if (vgpr_slot.find(spill_id) != vgpr_slot.end()) {
+ /* spill vgpr */
+ ctx.program->config->spilled_vgprs += (*it)->operands[0].size();
+
+ assert(false && "vgpr spilling not yet implemented.");
+ } else if (sgpr_slot.find(spill_id) != sgpr_slot.end()) {
+ ctx.program->config->spilled_sgprs += (*it)->operands[0].size();
+
+ uint32_t spill_slot = sgpr_slot[spill_id];
+
+ /* check if the linear vgpr already exists */
+ if (vgpr_spill_temps[spill_slot / 64] == Temp()) {
+ Temp linear_vgpr = {ctx.program->allocateId(), v1.as_linear()};
+ vgpr_spill_temps[spill_slot / 64] = linear_vgpr;
+ aco_ptr<Pseudo_instruction> create{create_instruction<Pseudo_instruction>(aco_opcode::p_start_linear_vgpr, Format::PSEUDO, 0, 1)};
+ create->definitions[0] = Definition(linear_vgpr);
+ /* find the right place to insert this definition */
+ if (last_top_level_block_idx == block.index) {
+ /* insert right before the current instruction */
+ instructions.emplace_back(std::move(create));
+ } else {
+ assert(last_top_level_block_idx < block.index);
+ /* insert before the branch at last top level block */
+ std::vector<aco_ptr<Instruction>>& instructions = ctx.program->blocks[last_top_level_block_idx].instructions;
+ instructions.insert(std::next(instructions.begin(), instructions.size() - 1), std::move(create));
+ }
+ }
+
+ /* spill sgpr: just add the vgpr temp to operands */
+ Pseudo_instruction* spill = create_instruction<Pseudo_instruction>(aco_opcode::p_spill, Format::PSEUDO, 3, 0);
+ spill->operands[0] = Operand(vgpr_spill_temps[spill_slot / 64]);
+ spill->operands[1] = Operand(spill_slot % 64);
+ spill->operands[2] = (*it)->operands[0];
+ instructions.emplace_back(aco_ptr<Instruction>(spill));
+ } else {
+ unreachable("No spill slot assigned for spill id");
+ }
+
+ } else if ((*it)->opcode == aco_opcode::p_reload) {
+ uint32_t spill_id = (*it)->operands[0].constantValue();
+ assert(ctx.is_reloaded[spill_id]);
+
+ if (vgpr_slot.find(spill_id) != vgpr_slot.end()) {
+ /* reload vgpr */
+ assert(false && "vgpr spilling not yet implemented.");
+
+ } else if (sgpr_slot.find(spill_id) != sgpr_slot.end()) {
+ uint32_t spill_slot = sgpr_slot[spill_id];
+ reload_in_loop[spill_slot / 64] = block.loop_nest_depth > 0;
+
+ /* check if the linear vgpr already exists */
+ if (vgpr_spill_temps[spill_slot / 64] == Temp()) {
+ Temp linear_vgpr = {ctx.program->allocateId(), v1.as_linear()};
+ vgpr_spill_temps[spill_slot / 64] = linear_vgpr;
+ aco_ptr<Pseudo_instruction> create{create_instruction<Pseudo_instruction>(aco_opcode::p_start_linear_vgpr, Format::PSEUDO, 0, 1)};
+ create->definitions[0] = Definition(linear_vgpr);
+ /* find the right place to insert this definition */
+ if (last_top_level_block_idx == block.index) {
+ /* insert right before the current instruction */
+ instructions.emplace_back(std::move(create));
+ } else {
+ assert(last_top_level_block_idx < block.index);
+ /* insert before the branch at last top level block */
+ std::vector<aco_ptr<Instruction>>& instructions = ctx.program->blocks[last_top_level_block_idx].instructions;
+ instructions.insert(std::next(instructions.begin(), instructions.size() - 1), std::move(create));
+ }
+ }
+
+ /* reload sgpr: just add the vgpr temp to operands */
+ Pseudo_instruction* reload = create_instruction<Pseudo_instruction>(aco_opcode::p_reload, Format::PSEUDO, 2, 1);
+ reload->operands[0] = Operand(vgpr_spill_temps[spill_slot / 64]);
+ reload->operands[1] = Operand(spill_slot % 64);
+ reload->definitions[0] = (*it)->definitions[0];
+ instructions.emplace_back(aco_ptr<Instruction>(reload));
+ } else {
+ unreachable("No spill slot assigned for spill id");
+ }
+ } else if (!ctx.remat_used.count(it->get()) || ctx.remat_used[it->get()]) {
+ instructions.emplace_back(std::move(*it));
+ }
+
+ }
+ block.instructions = std::move(instructions);
+ }
+
+ /* SSA elimination inserts copies for logical phis right before p_logical_end
+ * So if a linear vgpr is used between that p_logical_end and the branch,
+ * we need to ensure logical phis don't choose a definition which aliases
+ * the linear vgpr.
+ * TODO: Moving the spills and reloads to before p_logical_end might produce
+ * slightly better code. */
+ for (Block& block : ctx.program->blocks) {
+ /* loops exits are already handled */
+ if (block.logical_preds.size() <= 1)
+ continue;
+
+ bool has_logical_phis = false;
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ if (instr->opcode == aco_opcode::p_phi) {
+ has_logical_phis = true;
+ break;
+ } else if (instr->opcode != aco_opcode::p_linear_phi) {
+ break;
+ }
+ }
+ if (!has_logical_phis)
+ continue;
+
+ std::set<Temp> vgprs;
+ for (unsigned pred_idx : block.logical_preds) {
+ Block& pred = ctx.program->blocks[pred_idx];
+ for (int i = pred.instructions.size() - 1; i >= 0; i--) {
+ aco_ptr<Instruction>& pred_instr = pred.instructions[i];
+ if (pred_instr->opcode == aco_opcode::p_logical_end) {
+ break;
+ } else if (pred_instr->opcode == aco_opcode::p_spill ||
+ pred_instr->opcode == aco_opcode::p_reload) {
+ vgprs.insert(pred_instr->operands[0].getTemp());
+ }
+ }
+ }
+ if (!vgprs.size())
+ continue;
+
+ aco_ptr<Instruction> destr{create_instruction<Pseudo_instruction>(aco_opcode::p_end_linear_vgpr, Format::PSEUDO, vgprs.size(), 0)};
+ int k = 0;
+ for (Temp tmp : vgprs) {
+ destr->operands[k++] = Operand(tmp);
+ }
+ /* find insertion point */
+ std::vector<aco_ptr<Instruction>>::iterator it = block.instructions.begin();
+ while ((*it)->opcode == aco_opcode::p_linear_phi || (*it)->opcode == aco_opcode::p_phi)
+ ++it;
+ block.instructions.insert(it, std::move(destr));
+ }
+}
+
+} /* end namespace */
+
+
+void spill(Program* program, live& live_vars, const struct radv_nir_compiler_options *options)
+{
+ program->config->spilled_vgprs = 0;
+ program->config->spilled_sgprs = 0;
+
+ /* no spilling when wave count is already high */
+ if (program->num_waves >= 6)
+ return;
+
+ /* else, we check if we can improve things a bit */
+ uint16_t total_sgpr_regs = options->chip_class >= GFX8 ? 800 : 512;
+ uint16_t max_addressible_sgpr = program->sgpr_limit;
+
+ /* calculate target register demand */
+ RegisterDemand max_reg_demand;
+ for (Block& block : program->blocks) {
+ max_reg_demand.update(block.register_demand);
+ }
+
+ RegisterDemand target_pressure = {256, int16_t(max_addressible_sgpr)};
+ unsigned num_waves = 1;
+ int spills_to_vgpr = (max_reg_demand.sgpr - max_addressible_sgpr + 63) / 64;
+
+ /* test if it possible to increase occupancy with little spilling */
+ for (unsigned num_waves_next = 2; num_waves_next <= 8; num_waves_next++) {
+ RegisterDemand target_pressure_next = {int16_t((256 / num_waves_next) & ~3),
+ int16_t(std::min<uint16_t>(((total_sgpr_regs / num_waves_next) & ~7) - 2, max_addressible_sgpr))};
+
+ /* Currently no vgpr spilling supported.
+ * Spill as many sgprs as necessary to not hinder occupancy */
+ if (max_reg_demand.vgpr > target_pressure_next.vgpr)
+ break;
+ /* check that we have enough free vgprs to spill sgprs to */
+ if (max_reg_demand.sgpr > target_pressure_next.sgpr) {
+ /* add some buffer in case graph coloring is not perfect ... */
+ const int spills_to_vgpr_next = (max_reg_demand.sgpr - target_pressure_next.sgpr + 63 + 32) / 64;
+ if (spills_to_vgpr_next + max_reg_demand.vgpr > target_pressure_next.vgpr)
+ break;
+ spills_to_vgpr = spills_to_vgpr_next;
+ }
+
+ target_pressure = target_pressure_next;
+ num_waves = num_waves_next;
+ }
+
+ assert(max_reg_demand.vgpr <= target_pressure.vgpr && "VGPR spilling not yet supported.");
+ /* nothing to do */
+ if (num_waves == program->num_waves)
+ return;
+
+ /* initialize ctx */
+ spill_ctx ctx(target_pressure, program, live_vars.register_demand);
+ compute_global_next_uses(ctx, live_vars.live_out);
+ get_rematerialize_info(ctx);
+
+ /* create spills and reloads */
+ for (unsigned i = 0; i < program->blocks.size(); i++)
+ spill_block(ctx, i);
+
+ /* assign spill slots and DCE rematerialized code */
+ assign_spill_slots(ctx, spills_to_vgpr);
+
+ /* update live variable information */
+ live_vars = live_var_analysis(program, options);
+
+ assert(program->num_waves >= num_waves);
+}
+
+}
+
diff --git a/src/amd/compiler/aco_ssa_elimination.cpp b/src/amd/compiler/aco_ssa_elimination.cpp
new file mode 100644
index 00000000000..3d76dcd8867
--- /dev/null
+++ b/src/amd/compiler/aco_ssa_elimination.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+
+#include "aco_ir.h"
+
+#include <map>
+
+namespace aco {
+namespace {
+
+/* map: block-id -> pair (dest, src) to store phi information */
+typedef std::map<uint32_t, std::vector<std::pair<Definition, Operand>>> phi_info;
+
+struct ssa_elimination_ctx {
+ phi_info logical_phi_info;
+ phi_info linear_phi_info;
+ std::vector<bool> empty_blocks;
+ Program* program;
+
+ ssa_elimination_ctx(Program* program) : empty_blocks(program->blocks.size(), true), program(program) {}
+};
+
+void collect_phi_info(ssa_elimination_ctx& ctx)
+{
+ for (Block& block : ctx.program->blocks) {
+ for (aco_ptr<Instruction>& phi : block.instructions) {
+ if (phi->opcode != aco_opcode::p_phi && phi->opcode != aco_opcode::p_linear_phi)
+ break;
+
+ for (unsigned i = 0; i < phi->operands.size(); i++) {
+ if (phi->operands[i].isUndefined())
+ continue;
+ if (phi->operands[i].isTemp() && phi->operands[i].physReg() == phi->definitions[0].physReg())
+ continue;
+
+ std::vector<unsigned>& preds = phi->opcode == aco_opcode::p_phi ? block.logical_preds : block.linear_preds;
+ phi_info& info = phi->opcode == aco_opcode::p_phi ? ctx.logical_phi_info : ctx.linear_phi_info;
+ const auto result = info.emplace(preds[i], std::vector<std::pair<Definition, Operand>>());
+ result.first->second.emplace_back(phi->definitions[0], phi->operands[i]);
+ ctx.empty_blocks[preds[i]] = false;
+ }
+ }
+ }
+}
+
+void insert_parallelcopies(ssa_elimination_ctx& ctx)
+{
+ /* insert the parallelcopies from logical phis before p_logical_end */
+ for (auto&& entry : ctx.logical_phi_info) {
+ Block& block = ctx.program->blocks[entry.first];
+ unsigned idx = block.instructions.size() - 1;
+ while (block.instructions[idx]->opcode != aco_opcode::p_logical_end) {
+ assert(idx > 0);
+ idx--;
+ }
+
+ std::vector<aco_ptr<Instruction>>::iterator it = std::next(block.instructions.begin(), idx);
+ aco_ptr<Pseudo_instruction> pc{create_instruction<Pseudo_instruction>(aco_opcode::p_parallelcopy, Format::PSEUDO, entry.second.size(), entry.second.size())};
+ unsigned i = 0;
+ for (std::pair<Definition, Operand>& pair : entry.second)
+ {
+ pc->definitions[i] = pair.first;
+ pc->operands[i] = pair.second;
+ i++;
+ }
+ /* this shouldn't be needed since we're only copying vgprs */
+ pc->tmp_in_scc = false;
+ block.instructions.insert(it, std::move(pc));
+ }
+
+ /* insert parallelcopies for the linear phis at the end of blocks just before the branch */
+ for (auto&& entry : ctx.linear_phi_info) {
+ Block& block = ctx.program->blocks[entry.first];
+ std::vector<aco_ptr<Instruction>>::iterator it = block.instructions.end();
+ --it;
+ assert((*it)->format == Format::PSEUDO_BRANCH);
+ aco_ptr<Pseudo_instruction> pc{create_instruction<Pseudo_instruction>(aco_opcode::p_parallelcopy, Format::PSEUDO, entry.second.size(), entry.second.size())};
+ unsigned i = 0;
+ for (std::pair<Definition, Operand>& pair : entry.second)
+ {
+ pc->definitions[i] = pair.first;
+ pc->operands[i] = pair.second;
+ i++;
+ }
+ pc->tmp_in_scc = block.scc_live_out;
+ pc->scratch_sgpr = block.scratch_sgpr;
+ block.instructions.insert(it, std::move(pc));
+ }
+}
+
+
+void try_remove_merge_block(ssa_elimination_ctx& ctx, Block* block)
+{
+ /* check if the successor is another merge block which restores exec */
+ // TODO: divergent loops also restore exec
+ if (block->linear_succs.size() != 1 ||
+ !(ctx.program->blocks[block->linear_succs[0]].kind & block_kind_merge))
+ return;
+
+ /* check if this block is empty and the exec mask is not needed */
+ for (aco_ptr<Instruction>& instr : block->instructions) {
+ if (instr->opcode == aco_opcode::p_parallelcopy) {
+ if (instr->definitions[0].physReg() == exec)
+ continue;
+ else
+ return;
+ }
+
+ if (instr->opcode != aco_opcode::p_linear_phi &&
+ instr->opcode != aco_opcode::p_phi &&
+ instr->opcode != aco_opcode::p_logical_start &&
+ instr->opcode != aco_opcode::p_logical_end &&
+ instr->opcode != aco_opcode::p_branch)
+ return;
+ }
+
+ /* keep the branch instruction and remove the rest */
+ aco_ptr<Instruction> branch = std::move(block->instructions.back());
+ block->instructions.clear();
+ block->instructions.emplace_back(std::move(branch));
+}
+
+void try_remove_invert_block(ssa_elimination_ctx& ctx, Block* block)
+{
+ assert(block->linear_succs.size() == 2);
+ if (block->linear_succs[0] != block->linear_succs[1])
+ return;
+
+ /* check if we can remove this block */
+ for (aco_ptr<Instruction>& instr : block->instructions) {
+ if (instr->opcode != aco_opcode::p_linear_phi &&
+ instr->opcode != aco_opcode::p_phi &&
+ instr->opcode != aco_opcode::s_andn2_b64 &&
+ instr->opcode != aco_opcode::p_branch)
+ return;
+ }
+
+ unsigned succ_idx = block->linear_succs[0];
+ assert(block->linear_preds.size() == 2);
+ for (unsigned i = 0; i < 2; i++) {
+ Block *pred = &ctx.program->blocks[block->linear_preds[i]];
+ pred->linear_succs[0] = succ_idx;
+ ctx.program->blocks[succ_idx].linear_preds[i] = pred->index;
+
+ Pseudo_branch_instruction *branch = static_cast<Pseudo_branch_instruction*>(pred->instructions.back().get());
+ assert(branch->format == Format::PSEUDO_BRANCH);
+ branch->target[0] = succ_idx;
+ branch->target[1] = succ_idx;
+ }
+
+ block->instructions.clear();
+ block->linear_preds.clear();
+ block->linear_succs.clear();
+}
+
+void try_remove_simple_block(ssa_elimination_ctx& ctx, Block* block)
+{
+ for (aco_ptr<Instruction>& instr : block->instructions) {
+ if (instr->opcode != aco_opcode::p_logical_start &&
+ instr->opcode != aco_opcode::p_logical_end &&
+ instr->opcode != aco_opcode::p_branch)
+ return;
+ }
+
+ Block& pred = ctx.program->blocks[block->linear_preds[0]];
+ Block& succ = ctx.program->blocks[block->linear_succs[0]];
+ Pseudo_branch_instruction* branch = static_cast<Pseudo_branch_instruction*>(pred.instructions.back().get());
+ if (branch->opcode == aco_opcode::p_branch) {
+ branch->target[0] = succ.index;
+ branch->target[1] = succ.index;
+ } else if (branch->target[0] == block->index) {
+ branch->target[0] = succ.index;
+ } else if (branch->target[0] == succ.index) {
+ assert(branch->target[1] == block->index);
+ branch->target[1] = succ.index;
+ branch->opcode = aco_opcode::p_branch;
+ } else if (branch->target[1] == block->index) {
+ /* check if there is a fall-through path from block to succ */
+ bool falls_through = true;
+ for (unsigned j = block->index + 1; falls_through && j < succ.index; j++) {
+ assert(ctx.program->blocks[j].index == j);
+ if (!ctx.program->blocks[j].instructions.empty())
+ falls_through = false;
+ }
+ if (falls_through) {
+ branch->target[1] = succ.index;
+ } else {
+ /* check if there is a fall-through path for the alternative target */
+ for (unsigned j = block->index + 1; j < branch->target[0]; j++) {
+ if (!ctx.program->blocks[j].instructions.empty())
+ return;
+ }
+
+ /* This is a (uniform) break or continue block. The branch condition has to be inverted. */
+ if (branch->opcode == aco_opcode::p_cbranch_z)
+ branch->opcode = aco_opcode::p_cbranch_nz;
+ else if (branch->opcode == aco_opcode::p_cbranch_nz)
+ branch->opcode = aco_opcode::p_cbranch_z;
+ else
+ assert(false);
+ /* also invert the linear successors */
+ pred.linear_succs[0] = pred.linear_succs[1];
+ pred.linear_succs[1] = succ.index;
+ branch->target[1] = branch->target[0];
+ branch->target[0] = succ.index;
+ }
+ } else {
+ assert(false);
+ }
+
+ if (branch->target[0] == branch->target[1])
+ branch->opcode = aco_opcode::p_branch;
+
+ for (unsigned i = 0; i < pred.linear_succs.size(); i++)
+ if (pred.linear_succs[i] == block->index)
+ pred.linear_succs[i] = succ.index;
+
+ for (unsigned i = 0; i < succ.linear_preds.size(); i++)
+ if (succ.linear_preds[i] == block->index)
+ succ.linear_preds[i] = pred.index;
+
+ block->instructions.clear();
+ block->linear_preds.clear();
+ block->linear_succs.clear();
+}
+
+void jump_threading(ssa_elimination_ctx& ctx)
+{
+ for (int i = ctx.program->blocks.size() - 1; i >= 0; i--) {
+ Block* block = &ctx.program->blocks[i];
+
+ if (!ctx.empty_blocks[i])
+ continue;
+
+ if (block->kind & block_kind_invert) {
+ try_remove_invert_block(ctx, block);
+ continue;
+ }
+
+ if (block->linear_succs.size() > 1)
+ continue;
+
+ if (block->kind & block_kind_merge ||
+ block->kind & block_kind_loop_exit)
+ try_remove_merge_block(ctx, block);
+
+ if (block->linear_preds.size() == 1)
+ try_remove_simple_block(ctx, block);
+ }
+}
+
+} /* end namespace */
+
+
+void ssa_elimination(Program* program)
+{
+ ssa_elimination_ctx ctx(program);
+
+ /* Collect information about every phi-instruction */
+ collect_phi_info(ctx);
+
+ /* eliminate empty blocks */
+ jump_threading(ctx);
+
+ /* insert parallelcopies from SSA elimination */
+ insert_parallelcopies(ctx);
+
+}
+}
diff --git a/src/amd/compiler/aco_util.h b/src/amd/compiler/aco_util.h
new file mode 100644
index 00000000000..ec77ba55716
--- /dev/null
+++ b/src/amd/compiler/aco_util.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright Michael Schellenberger Costa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#ifndef ACO_UTIL_H
+#define ACO_UTIL_H
+
+#include <cassert>
+#include <iterator>
+
+namespace aco {
+
+/*! \brief Definition of a span object
+*
+* \details A "span" is an "array view" type for holding a view of contiguous
+* data. The "span" object does not own the data itself.
+*/
+template <typename T>
+class span {
+public:
+ using value_type = T;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = pointer;
+ using const_iterator = const_pointer;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+
+ /*! \brief Compiler generated default constructor
+ */
+ constexpr span() = default;
+
+ /*! \brief Constructor taking a pointer and the length of the span
+ * \param[in] data Pointer to the underlying data array
+ * \param[in] length The size of the span
+ */
+ constexpr span(pointer data, const size_type length)
+ : data{ data } , length{ length } {}
+
+ /*! \brief Returns an iterator to the begin of the span
+ * \return data
+ */
+ constexpr iterator begin() noexcept {
+ return data;
+ }
+
+ /*! \brief Returns a const_iterator to the begin of the span
+ * \return data
+ */
+ constexpr const_iterator begin() const noexcept {
+ return data;
+ }
+
+ /*! \brief Returns an iterator to the end of the span
+ * \return data + length
+ */
+ constexpr iterator end() noexcept {
+ return std::next(data, length);
+ }
+
+ /*! \brief Returns a const_iterator to the end of the span
+ * \return data + length
+ */
+ constexpr const_iterator end() const noexcept {
+ return std::next(data, length);
+ }
+
+ /*! \brief Returns a const_iterator to the begin of the span
+ * \return data
+ */
+ constexpr const_iterator cbegin() const noexcept {
+ return data;
+ }
+
+ /*! \brief Returns a const_iterator to the end of the span
+ * \return data + length
+ */
+ constexpr const_iterator cend() const noexcept {
+ return std::next(data, length);
+ }
+
+ /*! \brief Returns a reverse_iterator to the end of the span
+ * \return reverse_iterator(end())
+ */
+ constexpr reverse_iterator rbegin() noexcept {
+ return reverse_iterator(end());
+ }
+
+ /*! \brief Returns a const_reverse_iterator to the end of the span
+ * \return reverse_iterator(end())
+ */
+ constexpr const_reverse_iterator rbegin() const noexcept {
+ return const_reverse_iterator(end());
+ }
+
+ /*! \brief Returns a reverse_iterator to the begin of the span
+ * \return reverse_iterator(begin())
+ */
+ constexpr reverse_iterator rend() noexcept {
+ return reverse_iterator(begin());
+ }
+
+ /*! \brief Returns a const_reverse_iterator to the begin of the span
+ * \return reverse_iterator(begin())
+ */
+ constexpr const_reverse_iterator rend() const noexcept {
+ return const_reverse_iterator(begin());
+ }
+
+ /*! \brief Returns a const_reverse_iterator to the end of the span
+ * \return rbegin()
+ */
+ constexpr const_reverse_iterator crbegin() const noexcept {
+ return const_reverse_iterator(cend());
+ }
+
+ /*! \brief Returns a const_reverse_iterator to the begin of the span
+ * \return rend()
+ */
+ constexpr const_reverse_iterator crend() const noexcept {
+ return const_reverse_iterator(cbegin());
+ }
+
+ /*! \brief Unchecked access operator
+ * \param[in] index Index of the element we want to access
+ * \return *(std::next(data, index))
+ */
+ constexpr reference operator[](const size_type index) noexcept {
+ assert(length > index);
+ return *(std::next(data, index));
+ }
+
+ /*! \brief Unchecked const access operator
+ * \param[in] index Index of the element we want to access
+ * \return *(std::next(data, index))
+ */
+ constexpr const_reference operator[](const size_type index) const noexcept {
+ assert(length > index);
+ return *(std::next(data, index));
+ }
+
+ /*! \brief Returns a reference to the last element of the span
+ * \return *(std::next(data, length - 1))
+ */
+ constexpr reference back() noexcept {
+ assert(length > 0);
+ return *(std::next(data, length - 1));
+ }
+
+ /*! \brief Returns a const_reference to the last element of the span
+ * \return *(std::next(data, length - 1))
+ */
+ constexpr const_reference back() const noexcept {
+ assert(length > 0);
+ return *(std::next(data, length - 1));
+ }
+
+ /*! \brief Returns a reference to the first element of the span
+ * \return *begin()
+ */
+ constexpr reference front() noexcept {
+ assert(length > 0);
+ return *begin();
+ }
+
+ /*! \brief Returns a const_reference to the first element of the span
+ * \return *cbegin()
+ */
+ constexpr const_reference front() const noexcept {
+ assert(length > 0);
+ return *cbegin();
+ }
+
+ /*! \brief Returns true if the span is empty
+ * \return length == 0
+ */
+ constexpr bool empty() const noexcept {
+ return length == 0;
+ }
+
+ /*! \brief Returns the size of the span
+ * \return length == 0
+ */
+ constexpr size_type size() const noexcept {
+ return length;
+ }
+
+ /*! \brief Decreases the size of the span by 1
+ */
+ constexpr void pop_back() noexcept {
+ assert(length > 0);
+ --length;
+ }
+
+ /*! \brief Clears the span
+ */
+ constexpr void clear() noexcept {
+ data = nullptr;
+ length = 0;
+ }
+
+private:
+ pointer data{ nullptr }; //!> Pointer to the underlying data array
+ size_type length{ 0 }; //!> Size of the span
+};
+
+} // namespace aco
+
+#endif // ACO_UTIL_H \ No newline at end of file
diff --git a/src/amd/compiler/aco_validate.cpp b/src/amd/compiler/aco_validate.cpp
new file mode 100644
index 00000000000..0988d66df3a
--- /dev/null
+++ b/src/amd/compiler/aco_validate.cpp
@@ -0,0 +1,460 @@
+/*
+ * Copyright © 2018 Valve Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+#include "aco_ir.h"
+
+#include <map>
+
+namespace aco {
+
+#ifndef NDEBUG
+void perfwarn(bool cond, const char *msg, Instruction *instr)
+{
+ if (cond) {
+ fprintf(stderr, "ACO performance warning: %s\n", msg);
+ if (instr) {
+ fprintf(stderr, "instruction: ");
+ aco_print_instr(instr, stderr);
+ fprintf(stderr, "\n");
+ }
+
+ if (debug_flags & DEBUG_PERFWARN)
+ exit(1);
+ }
+}
+#endif
+
+void validate(Program* program, FILE * output)
+{
+ if (!(debug_flags & DEBUG_VALIDATE))
+ return;
+
+ bool is_valid = true;
+ auto check = [&output, &is_valid](bool check, const char * msg, aco::Instruction * instr) -> void {
+ if (!check) {
+ fprintf(output, "%s: ", msg);
+ aco_print_instr(instr, output);
+ fprintf(output, "\n");
+ is_valid = false;
+ }
+ };
+
+ for (Block& block : program->blocks) {
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+
+ /* check base format */
+ Format base_format = instr->format;
+ base_format = (Format)((uint32_t)base_format & ~(uint32_t)Format::SDWA);
+ base_format = (Format)((uint32_t)base_format & ~(uint32_t)Format::DPP);
+ if ((uint32_t)base_format & (uint32_t)Format::VOP1)
+ base_format = Format::VOP1;
+ else if ((uint32_t)base_format & (uint32_t)Format::VOP2)
+ base_format = Format::VOP2;
+ else if ((uint32_t)base_format & (uint32_t)Format::VOPC)
+ base_format = Format::VOPC;
+ else if ((uint32_t)base_format & (uint32_t)Format::VINTRP)
+ base_format = Format::VINTRP;
+ check(base_format == instr_info.format[(int)instr->opcode], "Wrong base format for instruction", instr.get());
+
+ /* check VOP3 modifiers */
+ if (((uint32_t)instr->format & (uint32_t)Format::VOP3) && instr->format != Format::VOP3) {
+ check(base_format == Format::VOP2 ||
+ base_format == Format::VOP1 ||
+ base_format == Format::VOPC ||
+ base_format == Format::VINTRP,
+ "Format cannot have VOP3A/VOP3B applied", instr.get());
+ }
+
+ /* check for undefs */
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ if (instr->operands[i].isUndefined()) {
+ bool flat = instr->format == Format::FLAT || instr->format == Format::SCRATCH || instr->format == Format::GLOBAL;
+ bool can_be_undef = is_phi(instr) || instr->format == Format::EXP ||
+ instr->format == Format::PSEUDO_REDUCTION ||
+ (flat && i == 1) || (instr->format == Format::MIMG && i == 2) ||
+ ((instr->format == Format::MUBUF || instr->format == Format::MTBUF) && i == 0);
+ check(can_be_undef, "Undefs can only be used in certain operands", instr.get());
+ }
+ }
+
+ /* check num literals */
+ if (instr->isSALU() || instr->isVALU()) {
+ unsigned num_literals = 0;
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (instr->operands[i].isLiteral()) {
+ check(instr->format == Format::SOP1 ||
+ instr->format == Format::SOP2 ||
+ instr->format == Format::SOPC ||
+ instr->format == Format::VOP1 ||
+ instr->format == Format::VOP2 ||
+ instr->format == Format::VOPC,
+ "Literal applied on wrong instruction format", instr.get());
+
+ num_literals++;
+ check(!instr->isVALU() || i == 0 || i == 2, "Wrong source position for Literal argument", instr.get());
+ }
+ }
+ check(num_literals <= 1, "Only 1 Literal allowed", instr.get());
+
+ /* check num sgprs for VALU */
+ if (instr->isVALU()) {
+ check(instr->definitions[0].getTemp().type() == RegType::vgpr ||
+ (int) instr->format & (int) Format::VOPC ||
+ instr->opcode == aco_opcode::v_readfirstlane_b32 ||
+ instr->opcode == aco_opcode::v_readlane_b32,
+ "Wrong Definition type for VALU instruction", instr.get());
+ unsigned num_sgpr = 0;
+ unsigned sgpr_idx = instr->operands.size();
+ for (unsigned i = 0; i < instr->operands.size(); i++)
+ {
+ if (instr->operands[i].isTemp() && instr->operands[i].regClass().type() == RegType::sgpr) {
+ check(i != 1 || (int) instr->format & (int) Format::VOP3A, "Wrong source position for SGPR argument", instr.get());
+
+ if (sgpr_idx == instr->operands.size() || instr->operands[sgpr_idx].tempId() != instr->operands[i].tempId())
+ num_sgpr++;
+ sgpr_idx = i;
+ }
+
+ if (instr->operands[i].isConstant() && !instr->operands[i].isLiteral())
+ check(i == 0 || (int) instr->format & (int) Format::VOP3A, "Wrong source position for constant argument", instr.get());
+ }
+ check(num_sgpr + num_literals <= 1, "Only 1 Literal OR 1 SGPR allowed", instr.get());
+ }
+
+ if (instr->format == Format::SOP1 || instr->format == Format::SOP2) {
+ check(instr->definitions[0].getTemp().type() == RegType::sgpr, "Wrong Definition type for SALU instruction", instr.get());
+ for (const Operand& op : instr->operands) {
+ check(op.isConstant() || op.regClass().type() <= RegType::sgpr,
+ "Wrong Operand type for SALU instruction", instr.get());
+ }
+ }
+ }
+
+ switch (instr->format) {
+ case Format::PSEUDO: {
+ if (instr->opcode == aco_opcode::p_create_vector) {
+ unsigned size = 0;
+ for (const Operand& op : instr->operands) {
+ size += op.size();
+ }
+ check(size == instr->definitions[0].size(), "Definition size does not match operand sizes", instr.get());
+ if (instr->definitions[0].getTemp().type() == RegType::sgpr) {
+ for (const Operand& op : instr->operands) {
+ check(op.isConstant() || op.regClass().type() == RegType::sgpr,
+ "Wrong Operand type for scalar vector", instr.get());
+ }
+ }
+ } else if (instr->opcode == aco_opcode::p_extract_vector) {
+ check((instr->operands[0].isTemp()) && instr->operands[1].isConstant(), "Wrong Operand types", instr.get());
+ check(instr->operands[1].constantValue() < instr->operands[0].size(), "Index out of range", instr.get());
+ check(instr->definitions[0].getTemp().type() == RegType::vgpr || instr->operands[0].regClass().type() == RegType::sgpr,
+ "Cannot extract SGPR value from VGPR vector", instr.get());
+ } else if (instr->opcode == aco_opcode::p_parallelcopy) {
+ check(instr->definitions.size() == instr->operands.size(), "Number of Operands does not match number of Definitions", instr.get());
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ if (instr->operands[i].isTemp())
+ check((instr->definitions[i].getTemp().type() == instr->operands[i].regClass().type()) ||
+ (instr->definitions[i].getTemp().type() == RegType::vgpr && instr->operands[i].regClass().type() == RegType::sgpr),
+ "Operand and Definition types do not match", instr.get());
+ }
+ } else if (instr->opcode == aco_opcode::p_phi) {
+ check(instr->operands.size() == block.logical_preds.size(), "Number of Operands does not match number of predecessors", instr.get());
+ check(instr->definitions[0].getTemp().type() == RegType::vgpr || instr->definitions[0].getTemp().regClass() == s2, "Logical Phi Definition must be vgpr or divergent boolean", instr.get());
+ } else if (instr->opcode == aco_opcode::p_linear_phi) {
+ for (const Operand& op : instr->operands)
+ check(!op.isTemp() || op.getTemp().is_linear(), "Wrong Operand type", instr.get());
+ check(instr->operands.size() == block.linear_preds.size(), "Number of Operands does not match number of predecessors", instr.get());
+ }
+ break;
+ }
+ case Format::SMEM: {
+ if (instr->operands.size() >= 1)
+ check(instr->operands[0].isTemp() && instr->operands[0].regClass().type() == RegType::sgpr, "SMEM operands must be sgpr", instr.get());
+ if (instr->operands.size() >= 2)
+ check(instr->operands[1].isConstant() || (instr->operands[1].isTemp() && instr->operands[1].regClass().type() == RegType::sgpr),
+ "SMEM offset must be constant or sgpr", instr.get());
+ if (!instr->definitions.empty())
+ check(instr->definitions[0].getTemp().type() == RegType::sgpr, "SMEM result must be sgpr", instr.get());
+ break;
+ }
+ case Format::MTBUF:
+ case Format::MUBUF:
+ case Format::MIMG: {
+ check(instr->operands.size() > 1, "VMEM instructions must have at least one operand", instr.get());
+ check(instr->operands[0].hasRegClass() && instr->operands[0].regClass().type() == RegType::vgpr,
+ "VADDR must be in vgpr for VMEM instructions", instr.get());
+ check(instr->operands[1].isTemp() && instr->operands[1].regClass().type() == RegType::sgpr, "VMEM resource constant must be sgpr", instr.get());
+ check(instr->operands.size() < 4 || (instr->operands[3].isTemp() && instr->operands[3].regClass().type() == RegType::vgpr), "VMEM write data must be vgpr", instr.get());
+ break;
+ }
+ case Format::DS: {
+ for (const Operand& op : instr->operands) {
+ check((op.isTemp() && op.regClass().type() == RegType::vgpr) || op.physReg() == m0,
+ "Only VGPRs are valid DS instruction operands", instr.get());
+ }
+ if (!instr->definitions.empty())
+ check(instr->definitions[0].getTemp().type() == RegType::vgpr, "DS instruction must return VGPR", instr.get());
+ break;
+ }
+ case Format::EXP: {
+ for (unsigned i = 0; i < 4; i++)
+ check(instr->operands[i].hasRegClass() && instr->operands[i].regClass().type() == RegType::vgpr,
+ "Only VGPRs are valid Export arguments", instr.get());
+ break;
+ }
+ case Format::FLAT:
+ check(instr->operands[1].isUndefined(), "Flat instructions don't support SADDR", instr.get());
+ /* fallthrough */
+ case Format::GLOBAL:
+ case Format::SCRATCH: {
+ check(instr->operands[0].isTemp() && instr->operands[0].regClass().type() == RegType::vgpr, "FLAT/GLOBAL/SCRATCH address must be vgpr", instr.get());
+ check(instr->operands[1].hasRegClass() && instr->operands[1].regClass().type() == RegType::sgpr,
+ "FLAT/GLOBAL/SCRATCH sgpr address must be undefined or sgpr", instr.get());
+ if (!instr->definitions.empty())
+ check(instr->definitions[0].getTemp().type() == RegType::vgpr, "FLAT/GLOBAL/SCRATCH result must be vgpr", instr.get());
+ else
+ check(instr->operands[2].regClass().type() == RegType::vgpr, "FLAT/GLOBAL/SCRATCH data must be vgpr", instr.get());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ assert(is_valid);
+}
+
+/* RA validation */
+namespace {
+
+struct Location {
+ Location() : block(NULL), instr(NULL) {}
+
+ Block *block;
+ Instruction *instr; //NULL if it's the block's live-in
+};
+
+struct Assignment {
+ Location defloc;
+ Location firstloc;
+ PhysReg reg;
+};
+
+bool ra_fail(FILE *output, Location loc, Location loc2, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ char msg[1024];
+ vsprintf(msg, fmt, args);
+ va_end(args);
+
+ fprintf(stderr, "RA error found at instruction in BB%d:\n", loc.block->index);
+ if (loc.instr) {
+ aco_print_instr(loc.instr, stderr);
+ fprintf(stderr, "\n%s", msg);
+ } else {
+ fprintf(stderr, "%s", msg);
+ }
+ if (loc2.block) {
+ fprintf(stderr, " in BB%d:\n", loc2.block->index);
+ aco_print_instr(loc2.instr, stderr);
+ }
+ fprintf(stderr, "\n\n");
+
+ return true;
+}
+
+} /* end namespace */
+
+bool validate_ra(Program *program, const struct radv_nir_compiler_options *options, FILE *output) {
+ if (!(debug_flags & DEBUG_VALIDATE_RA))
+ return false;
+
+ bool err = false;
+ aco::live live_vars = aco::live_var_analysis(program, options);
+ std::vector<std::vector<Temp>> phi_sgpr_ops(program->blocks.size());
+
+ std::map<unsigned, Assignment> assignments;
+ for (Block& block : program->blocks) {
+ Location loc;
+ loc.block = &block;
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ if (instr->opcode == aco_opcode::p_phi) {
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ if (instr->operands[i].isTemp() &&
+ instr->operands[i].getTemp().type() == RegType::sgpr &&
+ instr->operands[i].isFirstKill())
+ phi_sgpr_ops[block.logical_preds[i]].emplace_back(instr->operands[i].getTemp());
+ }
+ }
+
+ loc.instr = instr.get();
+ for (unsigned i = 0; i < instr->operands.size(); i++) {
+ Operand& op = instr->operands[i];
+ if (!op.isTemp())
+ continue;
+ if (!op.isFixed())
+ err |= ra_fail(output, loc, Location(), "Operand %d is not assigned a register", i);
+ if (assignments.count(op.tempId()) && assignments[op.tempId()].reg != op.physReg())
+ err |= ra_fail(output, loc, assignments.at(op.tempId()).firstloc, "Operand %d has an inconsistent register assignment with instruction", i);
+ if ((op.getTemp().type() == RegType::vgpr && op.physReg() + op.size() > 256 + program->config->num_vgprs) ||
+ (op.getTemp().type() == RegType::sgpr && op.physReg() + op.size() > program->config->num_sgprs && op.physReg() < program->sgpr_limit))
+ err |= ra_fail(output, loc, assignments.at(op.tempId()).firstloc, "Operand %d has an out-of-bounds register assignment", i);
+ if (!assignments[op.tempId()].firstloc.block)
+ assignments[op.tempId()].firstloc = loc;
+ if (!assignments[op.tempId()].defloc.block)
+ assignments[op.tempId()].reg = op.physReg();
+ }
+
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Definition& def = instr->definitions[i];
+ if (!def.isTemp())
+ continue;
+ if (!def.isFixed())
+ err |= ra_fail(output, loc, Location(), "Definition %d is not assigned a register", i);
+ if (assignments[def.tempId()].defloc.block)
+ err |= ra_fail(output, loc, assignments.at(def.tempId()).defloc, "Temporary %%%d also defined by instruction", def.tempId());
+ if ((def.getTemp().type() == RegType::vgpr && def.physReg() + def.size() > 256 + program->config->num_vgprs) ||
+ (def.getTemp().type() == RegType::sgpr && def.physReg() + def.size() > program->config->num_sgprs && def.physReg() < program->sgpr_limit))
+ err |= ra_fail(output, loc, assignments.at(def.tempId()).firstloc, "Definition %d has an out-of-bounds register assignment", i);
+ if (!assignments[def.tempId()].firstloc.block)
+ assignments[def.tempId()].firstloc = loc;
+ assignments[def.tempId()].defloc = loc;
+ assignments[def.tempId()].reg = def.physReg();
+ }
+ }
+ }
+
+ for (Block& block : program->blocks) {
+ Location loc;
+ loc.block = &block;
+
+ std::array<unsigned, 512> regs;
+ regs.fill(0);
+
+ std::set<Temp> live;
+ live.insert(live_vars.live_out[block.index].begin(), live_vars.live_out[block.index].end());
+ /* remove killed p_phi sgpr operands */
+ for (Temp tmp : phi_sgpr_ops[block.index])
+ live.erase(tmp);
+
+ /* check live out */
+ for (Temp tmp : live) {
+ PhysReg reg = assignments.at(tmp.id()).reg;
+ for (unsigned i = 0; i < tmp.size(); i++) {
+ if (regs[reg + i]) {
+ err |= ra_fail(output, loc, Location(), "Assignment of element %d of %%%d already taken by %%%d in live-out", i, tmp.id(), regs[reg + i]);
+ }
+ regs[reg + i] = tmp.id();
+ }
+ }
+ regs.fill(0);
+
+ for (auto it = block.instructions.rbegin(); it != block.instructions.rend(); ++it) {
+ aco_ptr<Instruction>& instr = *it;
+
+ /* check killed p_phi sgpr operands */
+ if (instr->opcode == aco_opcode::p_logical_end) {
+ for (Temp tmp : phi_sgpr_ops[block.index]) {
+ PhysReg reg = assignments.at(tmp.id()).reg;
+ for (unsigned i = 0; i < tmp.size(); i++) {
+ if (regs[reg + i])
+ err |= ra_fail(output, loc, Location(), "Assignment of element %d of %%%d already taken by %%%d in live-out", i, tmp.id(), regs[reg + i]);
+ }
+ live.emplace(tmp);
+ }
+ }
+
+ for (const Definition& def : instr->definitions) {
+ if (!def.isTemp())
+ continue;
+ live.erase(def.getTemp());
+ }
+
+ /* don't count phi operands as live-in, since they are actually
+ * killed when they are copied at the predecessor */
+ if (instr->opcode != aco_opcode::p_phi && instr->opcode != aco_opcode::p_linear_phi) {
+ for (const Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ live.insert(op.getTemp());
+ }
+ }
+ }
+
+ for (Temp tmp : live) {
+ PhysReg reg = assignments.at(tmp.id()).reg;
+ for (unsigned i = 0; i < tmp.size(); i++)
+ regs[reg + i] = tmp.id();
+ }
+
+ for (aco_ptr<Instruction>& instr : block.instructions) {
+ loc.instr = instr.get();
+
+ /* remove killed p_phi operands from regs */
+ if (instr->opcode == aco_opcode::p_logical_end) {
+ for (Temp tmp : phi_sgpr_ops[block.index]) {
+ PhysReg reg = assignments.at(tmp.id()).reg;
+ regs[reg] = 0;
+ }
+ }
+
+ if (instr->opcode != aco_opcode::p_phi && instr->opcode != aco_opcode::p_linear_phi) {
+ for (const Operand& op : instr->operands) {
+ if (!op.isTemp())
+ continue;
+ if (op.isFirstKill()) {
+ for (unsigned j = 0; j < op.getTemp().size(); j++)
+ regs[op.physReg() + j] = 0;
+ }
+ }
+ }
+
+ for (unsigned i = 0; i < instr->definitions.size(); i++) {
+ Definition& def = instr->definitions[i];
+ if (!def.isTemp())
+ continue;
+ Temp tmp = def.getTemp();
+ PhysReg reg = assignments.at(tmp.id()).reg;
+ for (unsigned j = 0; j < tmp.size(); j++) {
+ if (regs[reg + j])
+ err |= ra_fail(output, loc, assignments.at(regs[reg + i]).defloc, "Assignment of element %d of %%%d already taken by %%%d from instruction", i, tmp.id(), regs[reg + j]);
+ regs[reg + j] = tmp.id();
+ }
+ }
+
+ for (const Definition& def : instr->definitions) {
+ if (!def.isTemp())
+ continue;
+ if (def.isKill()) {
+ for (unsigned j = 0; j < def.getTemp().size(); j++)
+ regs[def.physReg() + j] = 0;
+ }
+ }
+ }
+ }
+
+ return err;
+}
+}
diff --git a/src/amd/compiler/meson.build b/src/amd/compiler/meson.build
new file mode 100644
index 00000000000..73151cad6eb
--- /dev/null
+++ b/src/amd/compiler/meson.build
@@ -0,0 +1,103 @@
+# Copyright © 2018 Valve Corporation
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+aco_depends = files('aco_opcodes.py')
+
+aco_opcodes_h = custom_target(
+ 'aco_opcodes.h',
+ input : 'aco_opcodes_h.py',
+ output : 'aco_opcodes.h',
+ command : [prog_python, '@INPUT@'],
+ capture : true,
+ depend_files : aco_depends,
+)
+
+aco_opcodes_c = custom_target(
+ 'aco_opcodes.cpp',
+ input : 'aco_opcodes_cpp.py',
+ output : 'aco_opcodes.cpp',
+ command : [prog_python, '@INPUT@'],
+ capture : true,
+ depend_files : aco_depends,
+)
+
+aco_builder_h = custom_target(
+ 'aco_builder.h',
+ input : 'aco_builder_h.py',
+ output : 'aco_builder.h',
+ command : [prog_python, '@INPUT@'],
+ capture : true,
+ depend_files : aco_depends,
+)
+
+# Headers-only dependency
+idep_aco_headers = declare_dependency(
+ sources : [aco_opcodes_h],
+ include_directories : include_directories('.'),
+)
+
+libaco_files = files(
+ 'aco_dead_code_analysis.cpp',
+ 'aco_dominance.cpp',
+ 'aco_instruction_selection.cpp',
+ 'aco_instruction_selection_setup.cpp',
+ 'aco_interface.cpp',
+ 'aco_interface.h',
+ 'aco_ir.h',
+ 'aco_assembler.cpp',
+ 'aco_insert_exec_mask.cpp',
+ 'aco_insert_NOPs.cpp',
+ 'aco_insert_waitcnt.cpp',
+ 'aco_reduce_assign.cpp',
+ 'aco_register_allocation.cpp',
+ 'aco_live_var_analysis.cpp',
+ 'aco_lower_bool_phis.cpp',
+ 'aco_lower_to_hw_instr.cpp',
+ 'aco_optimizer.cpp',
+ 'aco_opt_value_numbering.cpp',
+ 'aco_print_asm.cpp',
+ 'aco_print_ir.cpp',
+ 'aco_scheduler.cpp',
+ 'aco_ssa_elimination.cpp',
+ 'aco_spill.cpp',
+ 'aco_util.h',
+ 'aco_validate.cpp',
+)
+
+_libaco = static_library(
+ 'aco',
+ [libaco_files, aco_opcodes_c, aco_opcodes_h, aco_builder_h],
+ include_directories : [
+ inc_common, inc_compiler, inc_mesa, inc_mapi, inc_amd, inc_amd_common,
+ ],
+ dependencies : [
+ dep_llvm, dep_thread, dep_elf, dep_libdrm_amdgpu, dep_valgrind,
+ idep_nir_headers, idep_amdgfxregs_h,
+ ],
+ c_args : [c_vis_args],
+ cpp_args : [cpp_vis_args],
+ build_by_default : true,
+)
+
+# Also link with aco
+idep_aco = declare_dependency(
+ dependencies : idep_aco_headers,
+ link_with : _libaco,
+)