shader: implement RDNA2 subvector loop mask and branch semantics

This commit is contained in:
nmzik
2026-09-09 19:04:03 +04:00
parent 3902b42096
commit c913951f7b
11 changed files with 167 additions and 1 deletions
@@ -96,7 +96,9 @@ void EmitReturn(ValueEmitContext& ctx) {
}
uint32_t BranchCondition(ValueEmitContext& ctx, const IR::BlockInfo& info) {
// Scalar-instruction conditions already test the full wave's raw register values.
if (ctx.other_half == nullptr ||
info.terminator.condition == CFG::BranchCondition::ScalarInstruction ||
info.terminator.condition == CFG::BranchCondition::GotoVariable) {
return ctx.Def(info.condition);
}
@@ -61,6 +61,8 @@ BranchCondition ConditionForOpcode(Opcode opcode) {
case Opcode::S_CBRANCH_VCCNZ: return BranchCondition::VccNonZero;
case Opcode::S_CBRANCH_EXECZ: return BranchCondition::ExecZero;
case Opcode::S_CBRANCH_EXECNZ: return BranchCondition::ExecNonZero;
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END: return BranchCondition::ScalarInstruction;
default: return BranchCondition::Unknown;
}
}
@@ -2224,6 +2226,7 @@ std::string BranchConditionToString(BranchCondition condition) {
case BranchCondition::VccNonZero: return "vccnz";
case BranchCondition::ExecZero: return "execz";
case BranchCondition::ExecNonZero: return "execnz";
case BranchCondition::ScalarInstruction: return "scalar_instruction";
case BranchCondition::GotoVariable: return "goto_variable";
default: return "unknown";
}
@@ -17,6 +17,7 @@ enum class BranchCondition {
VccNonZero,
ExecZero,
ExecNonZero,
ScalarInstruction,
GotoVariable,
Unknown
};
@@ -83,6 +83,7 @@ constexpr OpcodeMap SOPK_OPCODE_LIST[] = {
{0x0eu, Opcode::S_CMP_LE_U32}, {0x0fu, Opcode::S_ADD_I32}, {0x10u, Opcode::S_MULK_I32},
{0x13u, Opcode::S_SETREG_B32}, {0x17u, Opcode::S_WAITCNT}, {0x18u, Opcode::S_WAITCNT},
{0x19u, Opcode::S_WAITCNT}, {0x1au, Opcode::S_WAITCNT},
{0x1bu, Opcode::S_SUBVECTOR_LOOP_BEGIN}, {0x1cu, Opcode::S_SUBVECTOR_LOOP_END},
};
constexpr OpcodeMap SOPP_OPCODE_LIST[] = {
@@ -209,6 +210,11 @@ void DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
switch (inst.opcode) {
case Opcode::S_MOVK_I32: DecodeScalarDestination(sdst, pc, inst.dst); return;
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END:
DecodeScalarDestination(sdst, pc, inst.dst);
inst.branch_target = pc + 4u + static_cast<uint32_t>(imm * 4);
return;
case Opcode::S_WAITCNT: {
const uint32_t waitcnt = word & 0xffffu;
inst.dst.kind = OperandKind::Null;
@@ -192,7 +192,9 @@ bool IsConditionalBranch(Opcode opcode) {
case Opcode::S_CBRANCH_VCCZ:
case Opcode::S_CBRANCH_VCCNZ:
case Opcode::S_CBRANCH_EXECZ:
case Opcode::S_CBRANCH_EXECNZ: return true;
case Opcode::S_CBRANCH_EXECNZ:
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END: return true;
default: return false;
}
}
@@ -535,6 +537,11 @@ std::string InstructionToString(const Instruction& inst) {
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} 0x{:08x}", inst.pc,
magic_enum::enum_name(inst.opcode),
inst.branch_target));
case Opcode::S_SUBVECTOR_LOOP_BEGIN:
case Opcode::S_SUBVECTOR_LOOP_END:
return WithUnsupportedReason(inst, fmt::format(
"0x{:08x}: {} {}, 0x{:08x}", inst.pc, magic_enum::enum_name(inst.opcode),
OperandToString(inst.dst), inst.branch_target));
case Opcode::EXP: return WithUnsupportedReason(inst, FormatExp(inst));
case Opcode::IMAGE_SAMPLE:
case Opcode::IMAGE_STORE:
@@ -53,6 +53,8 @@ enum class Opcode {
S_QUADMASK_B64,
S_GETPC_B64,
S_SETPC_B64,
S_SUBVECTOR_LOOP_BEGIN,
S_SUBVECTOR_LOOP_END,
S_AND_SAVEEXEC_B32,
S_ORN2_SAVEEXEC_B32,
S_ANDN1_SAVEEXEC_B32,
@@ -22,6 +22,33 @@ Decoder::Operand ConditionOperand(Decoder::OperandKind kind) {
} // namespace
void Translator::S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin) {
const auto zero = IR::U32(IR::Value(0u));
const auto lo = ir.GetExecLo();
const auto hi = ir.GetExecHi();
const auto saved = ReadU32(inst.dst);
if (begin) {
const auto low_active = ir.INotEqual(lo, zero);
instruction_branch_condition = ir.IEqual(ir.BitwiseOr(lo, hi), zero);
WriteRawU32(inst.dst, ir.Select(instruction_branch_condition, saved,
ir.Select(low_active, hi, lo)));
// Keep the ISA assignment order: SDST may itself name an EXEC half.
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecHi),
ir.Select(low_active, zero, ir.GetExecHi()));
} else {
const auto high_active = ir.INotEqual(hi, zero);
instruction_branch_condition =
ir.LogicalAnd(ir.LogicalNot(high_active), ir.INotEqual(saved, zero));
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecHi),
ir.Select(instruction_branch_condition, saved, hi));
WriteRawU32(inst.dst,
ir.Select(instruction_branch_condition, lo, ReadU32(inst.dst)));
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo),
ir.Select(high_active, saved,
ir.Select(instruction_branch_condition, zero, ir.GetExecLo())));
}
}
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
bool negate_exec, bool negate_source, bool write_64) {
if (!write_64) {
@@ -11,6 +11,8 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_WQM_B64: S_WQM_B64(inst); return true;
case O::S_GETPC_B64: S_GETPC_B64(inst); return true;
case O::S_SETPC_B64: return true;
case O::S_SUBVECTOR_LOOP_BEGIN: S_SUBVECTOR_LOOP(inst, true); return true;
case O::S_SUBVECTOR_LOOP_END: S_SUBVECTOR_LOOP(inst, false); return true;
case O::S_CSELECT_B32: S_CSELECT_B32(inst); return true;
case O::S_CSELECT_B64: ScalarSelect64(inst, inst.src1); return true;
case O::S_CMOV_B64: ScalarSelect64(inst, inst.dst); return true;
@@ -758,6 +758,10 @@ void Translator::AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo
case CFG::BranchCondition::VccNonZero: condition = ir.GetVcc(); break;
case CFG::BranchCondition::ExecZero: condition = ir.LogicalNot(ir.GetExec()); break;
case CFG::BranchCondition::ExecNonZero: condition = ir.GetExec(); break;
case CFG::BranchCondition::ScalarInstruction:
EXIT_IF(instruction_branch_condition.IsEmpty());
condition = instruction_branch_condition;
break;
case CFG::BranchCondition::GotoVariable:
if (source.terminator.goto_variable == UINT32_MAX) {
EXIT("block %u reads an invalid goto variable", source.id);
@@ -212,6 +212,7 @@ private:
bool V_CNDMASK_B32(const Decoder::Instruction& inst);
bool PackB16(const Decoder::Instruction& inst, bool high0, bool high1);
void S_SUBVECTOR_LOOP(const Decoder::Instruction& inst, bool begin);
void S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation, bool negate_exec,
bool negate_source, bool write_64);
void ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in);
@@ -252,6 +253,7 @@ private:
IR::Program& program;
IR::IREmitter ir;
IR::U1 instruction_branch_condition;
Decoder::Opcode current_opcode = Decoder::Opcode::UNKNOWN;
uint32_t current_pc = 0;
uint32_t current_vector_limit = 1;