shader: implement S_ORN2_SAVEEXEC_B32

This commit is contained in:
nmzik
2026-09-09 13:11:48 +04:00
parent e04007a7c6
commit 32ea0869e5
7 changed files with 105 additions and 13 deletions
@@ -62,6 +62,7 @@ constexpr OpcodeMap SOP1_OPCODE_LIST[] = {
{0x37u, Opcode::S_ANDN1_SAVEEXEC_B64},
{0x3bu, Opcode::S_BITREPLICATE_B64_B32},
{0x3cu, Opcode::S_AND_SAVEEXEC_B32},
{0x40u, Opcode::S_ORN2_SAVEEXEC_B32},
{0x44u, Opcode::S_ANDN1_SAVEEXEC_B32},
};
@@ -485,6 +485,7 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::S_WQM_B64:
case Opcode::S_QUADMASK_B64:
case Opcode::S_AND_SAVEEXEC_B32:
case Opcode::S_ORN2_SAVEEXEC_B32:
case Opcode::S_ANDN1_SAVEEXEC_B32:
case Opcode::S_AND_SAVEEXEC_B64:
case Opcode::S_ORN2_SAVEEXEC_B64:
@@ -54,6 +54,7 @@ enum class Opcode {
S_GETPC_B64,
S_SETPC_B64,
S_AND_SAVEEXEC_B32,
S_ORN2_SAVEEXEC_B32,
S_ANDN1_SAVEEXEC_B32,
S_AND_SAVEEXEC_B64,
S_ORN2_SAVEEXEC_B64,
@@ -24,27 +24,34 @@ Decoder::Operand ConditionOperand(Decoder::OperandKind kind) {
void Translator::S_SAVEEXEC(const Decoder::Instruction& inst, IR::ValueOpcode operation,
bool negate_exec, bool negate_source, bool write_64) {
if (!write_64) {
// Read the encoded scalar word and preserve EXEC_HI, including in wave32.
const auto old = ir.GetExecLo();
const auto src = ReadU32(inst.src0);
const auto lhs = negate_exec ? ir.BitwiseNot(old) : old;
const auto rhs = negate_source ? ir.BitwiseNot(src) : src;
IR::U32 result;
switch (operation) {
case IR::ValueOpcode::LogicalAnd: result = ir.BitwiseAnd(lhs, rhs); break;
case IR::ValueOpcode::LogicalOr: result = ir.BitwiseOr(lhs, rhs); break;
default: EXIT("unsupported SAVEEXEC operation");
}
WriteRawU32(inst.dst, old);
WriteRawU32(ConditionOperand(Decoder::OperandKind::ExecLo), result);
ir.SetScc(ir.INotEqual(result, IR::U32(IR::Value(0u))));
return;
}
const auto old = ir.GetExec();
const auto src = ReadMask(inst.src0);
const auto lhs = negate_exec ? ir.LogicalNot(old) : old;
const auto rhs = negate_source ? ir.LogicalNot(src) : src;
auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
if (write_64) {
WriteMask(inst.dst, old, true);
} else {
WriteRawU32(inst.dst, ir.GetExecLo());
if (program.wave_size == 64u) {
const auto low_half =
ir.ULessThan(IR::U32(ir.Emit(IR::ValueOpcode::LaneId)), IR::U32(IR::Value(32u)));
result = IR::U1(ir.Emit(IR::ValueOpcode::SelectU1, {low_half, result, old}));
}
}
const auto result = IR::U1(ir.Emit(operation, {lhs, rhs}));
WriteMask(inst.dst, old, true);
const auto mask = BallotMask(result);
ir.SetExec(result);
ir.SetExecLo(mask[0]);
ir.SetExecHi(mask[1]);
ir.SetScc(
ir.INotEqual(write_64 ? ir.BitwiseOr(mask[0], mask[1]) : mask[0], IR::U32(IR::Value(0u))));
ir.SetScc(ir.INotEqual(ir.BitwiseOr(mask[0], mask[1]), IR::U32(IR::Value(0u))));
}
void Translator::ADD_U32(const Decoder::Instruction& inst, bool vector, bool use_carry_in) {
@@ -23,6 +23,9 @@ bool Translator::EmitScalar(const Decoder::Instruction& inst) {
case O::S_ANDN1_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, true, false);
return true;
case O::S_ORN2_SAVEEXEC_B32:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalOr, true, false, false);
return true;
case O::S_AND_SAVEEXEC_B64:
S_SAVEEXEC(inst, IR::ValueOpcode::LogicalAnd, false, false, true);
return true;
+69
View File
@@ -14968,6 +14968,69 @@ TestCase ScalarOrn2SaveexecUsesSourceOrNotExec() {
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarOrn2SaveexecB32(u32 wave_size, u32 threads) {
using O = ShaderOpcode;
constexpr u32 exec_hi = 0xa5a55a5au;
constexpr u32 vcc_hi = 0x13579bdfu;
struct Case {
u32 exec_lo, source, destination, result;
};
constexpr Case cases[] = {
{0x8000000cu, 126, 106, 0xffffffffu}, // Captured vcc_lo, exec_lo.
{0x0000000fu, 6, 6, 0xfffffff3u}, // Aliased SGPR source/destination.
{0xffffffffu, 128, 8, 0}, // SCC ignores the nonzero high half.
{0xffffffffu, 107, 8, vcc_hi},
{0xffffffffu, 127, 8, exec_hi},
{0xffffffffu, 253, 8, 1}, // SCC is a scalar value, not a mask.
};
TestCase test;
test.name = wave_size == 64 ? "ScalarOrn2SaveexecB32Wave64"
: threads == 32 ? "ScalarOrn2SaveexecB32Wave32"
: "ScalarOrn2SaveexecB32PartialWave";
auto &code = test.code;
for (const auto &item : cases) {
AppendVMovU32(&code, 2, 0);
AppendSMovLiteral(&code, 126, item.exec_lo);
AppendSMovLiteral(&code, 127, exec_hi);
AppendSMovLiteral(&code, 107, vcc_hi);
AppendSMovLiteral(&code, 6, 3);
code.push_back(EncodeSopc(0x06, InlineU32(0), InlineU32(0)));
code.push_back(item.source == 126 ? 0xbeea407eu
: EncodeSop1(0x40, item.destination, item.source));
const u32 snapshots[] = {item.destination, 126, 127, 107, 253};
for (u32 i = 0; i < std::size(snapshots); ++i) {
code.push_back(EncodeSMovB32(20 + i, snapshots[i]));
}
AppendVMovU32(&code, 2, 1);
code.push_back(EncodeSop1(0x04, 126, 193u)); // Restore EXEC for readback.
const u32 expected[] = {item.exec_lo, item.result, exec_hi, vcc_hi,
item.result != 0 ? 1u : 0u};
for (u32 i = 0; i < std::size(expected); ++i) {
AppendStoreSgprAtLaneDwordOffset(&code, 20 + i, 0,
static_cast<u32>(test.expected.size()));
test.expected.insert(test.expected.end(), threads, expected[i]);
}
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0,
static_cast<u32>(test.expected.size()));
for (u32 lane = 0; lane < threads; ++lane) {
const u32 mask = lane < 32 ? item.result : exec_hi;
test.expected.push_back((mask >> (lane % 32)) & 1u);
}
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::S_CMP_EQ_U32,
O::S_ORN2_SAVEEXEC_B32, O::V_MOV_B32, O::V_LSHLREV_B32,
O::V_ADD_NC_U32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"S_ORN2_SAVEEXEC_B32 vcc_lo, exec_lo", 1}};
test.compute_info.threads_num[0] = threads;
test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase ScalarGetpcWritesNextInstructionPc() {
using O = ShaderOpcode;
@@ -23993,6 +24056,9 @@ std::vector<TestCase> MakeCases() {
AddCase(ScalarFlbitI32B32Gpu);
AddCase(ScalarSaveExecOps);
AddCase(ScalarOrn2SaveexecUsesSourceOrNotExec);
cases.push_back(ScalarOrn2SaveexecB32(32, 32));
cases.push_back(ScalarOrn2SaveexecB32(64, 64));
cases.push_back(ScalarOrn2SaveexecB32(32, 4));
AddCase(ScalarGetpcWritesNextInstructionPc);
AddCase(ScalarBitfieldPack);
AddCase(ScalarBrevB32PreservesScc);
@@ -28448,6 +28514,9 @@ int main(int argc, char **argv) {
RunCase(&vulkan, DispatcherIrreducibleControlFlow());
RunCase(&vulkan, ScalarSaveExecOps());
RunCase(&vulkan, ScalarOrn2SaveexecUsesSourceOrNotExec());
RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 32));
RunCase(&vulkan, ScalarOrn2SaveexecB32(64, 64));
RunCase(&vulkan, ScalarOrn2SaveexecB32(32, 4));
RunCase(&vulkan, ScalarNotB64UpdatesScc());
RunCase(&vulkan, ScalarSelectB64PreservesMaskProvenance());
RunCase(&vulkan, ScalarConditionalMoveB64());
+10
View File
@@ -4033,6 +4033,16 @@ void TestNewShaderDecoderArchitecture() {
Check(GetInstructionFamily(EncodeDs0(0x36)) == Family::DS,
"decoder did not classify DS directly");
const uint32_t orn2_saveexec_code[] = {0xbeea407eu};
Instruction orn2_saveexec;
ShaderRecompiler::Decoder::DecodeInstruction(orn2_saveexec_code, 0u, orn2_saveexec);
Check(orn2_saveexec.family == Family::SOP1 &&
orn2_saveexec.opcode == Opcode::S_ORN2_SAVEEXEC_B32 &&
orn2_saveexec.word_count == 1u && orn2_saveexec.src_count == 1u &&
orn2_saveexec.dst.kind == OperandKind::VccLo &&
orn2_saveexec.src0.kind == OperandKind::ExecLo,
"decoder rejected or misdecoded captured S_ORN2_SAVEEXEC_B32");
const uint32_t offset_code[] = {0u, EncodeVop1(0x01, 2, 3)};
Instruction direct;
ShaderRecompiler::Decoder::DecodeInstruction(offset_code, 1u, direct);