shader: support partial SDWA multiply and byte-source moves

This commit is contained in:
shadowbeat070
2026-09-19 15:15:40 +02:00
committed by nmzik
parent 7881c69a54
commit 9f25e3c23e
2 changed files with 159 additions and 2 deletions
+157
View File
@@ -22094,6 +22094,80 @@ TestCase Vop1SdwaMovByteDestinations() {
return test;
}
TestCase Vop1SdwaMovByteSources(u32 wave_size) {
using O = ShaderOpcode;
// Select 0x80 from each byte position, with nonzero neighboring bytes.
constexpr std::array<u32, 4> sources{
0x12345680u, 0x12348056u, 0x12803456u, 0x80341256u};
// RDNA2 table 88 / PS5 SDWA-related options: PAD, SEXT, PRESERVE.
constexpr std::array<u32, 18> partial_expected{
0x00000080u, 0xffffff80u, 0xa1b2c380u,
0x00008000u, 0xffff8000u, 0xa1b280d4u,
0x00800000u, 0xff800000u, 0xa180c3d4u,
0x80000000u, 0x80000000u, 0x80b2c3d4u,
0x00000080u, 0x00000080u, 0xa1b20080u,
0x00800000u, 0x00800000u, 0x0080c3d4u};
constexpr std::array<u32, 6> sext_word_expected{
0x0000ff80u, 0xffffff80u, 0xa1b2ff80u,
0xff800000u, 0xff800000u, 0xff80c3d4u};
TestCase test;
test.name = wave_size == 64 ? "Vop1SdwaMovByteSourcesWave64"
: "Vop1SdwaMovByteSourcesWave32";
test.initial.assign(sources.begin(), sources.end());
test.expected = test.initial;
auto &code = test.code;
const auto store = [&](u32 expected) {
AppendStoreVgpr(&code, 1, static_cast<u32>(test.expected.size()));
test.expected.push_back(expected);
};
for (u32 src_sel = 0; src_sel < sources.size(); ++src_sel) {
AppendVMovU32(&code, 30, src_sel * sizeof(u32));
AppendBufferLoadDword(&code, 2, 30);
for (u32 sext = 0; sext < 2; ++sext) {
for (u32 dst_sel = 0; dst_sel < 6; ++dst_sel) {
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 1, 0xa1b2c3d4u);
code.push_back(EncodeVop1(0x01, 1, 249));
code.push_back(EncodeVop1Sdwa(2, dst_sel, unused, src_sel, sext));
store(sext && dst_sel >= 4 ? sext_word_expected[(dst_sel - 4u) * 3u + unused]
: partial_expected[dst_sel * 3u + unused]);
}
}
}
}
// Aliasing must read the source byte before replacing the destination byte.
AppendVMovU32(&code, 30, 0);
AppendBufferLoadDword(&code, 1, 30);
code.push_back(EncodeVop1(0x01, 1, 249));
code.push_back(EncodeVop1Sdwa(1, 3, 2, 1));
store(0x56345680u);
// Scalar and inline sources use the same independent selectors.
AppendSMovLiteral(&code, 18, 0x12348056u);
code.push_back(EncodeVop1(0x01, 1, 249));
code.push_back(EncodeVop1Sdwa(18, 5, 0, 1, 1, 0, 0, 1));
store(0xff800000u);
AppendVMovLiteral(&code, 1, 0xa1b2c3d4u);
code.push_back(EncodeVop1(0x01, 1, 249));
code.push_back(EncodeVop1Sdwa(193, 0, 2, 3, 0, 0, 0, 1)); // Inline -1.
store(0xa1b2c3ffu);
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 1, 0xa1b2c3d4u);
code.push_back(EncodeSMovB32(126, InlineU32(0)));
code.push_back(EncodeVop1(0x01, 1, 249));
code.push_back(EncodeVop1Sdwa(2, 4, unused, 0));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
store(0xa1b2c3d4u);
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpBitFieldUExtract", "OpBitFieldSExtract", "OpBitFieldInsert"};
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop2SdwaSubNcExactByte2Destination() {
using O = ShaderOpcode;
@@ -22266,6 +22340,74 @@ TestCase Vop2SdwaMulI24Destinations(u32 wave_size) {
return test;
}
TestCase Vop2SdwaMulU24Destinations(u32 wave_size) {
using O = ShaderOpcode;
// The high source bytes are discarded: 3 * 0xe080 = 0x2a180.
constexpr std::array<u32, 18> partial_expected{
0x00000080u, 0xffffff80u, 0xa1b2c380u,
0x00008000u, 0xffff8000u, 0xa1b280d4u,
0x00800000u, 0xff800000u, 0xa180c3d4u,
0x80000000u, 0x80000000u, 0x80b2c3d4u,
0x0000a180u, 0xffffa180u, 0xa1b2a180u,
0xa1800000u, 0xa1800000u, 0xa180c3d4u};
TestCase test;
test.name = wave_size == 64 ? "Vop2SdwaMulU24DestinationsWave64"
: "Vop2SdwaMulU24DestinationsWave32";
test.initial = {0xab000003u, 0xcd00e080u, 0xffffffffu};
test.expected = test.initial;
auto &code = test.code;
const auto store = [&](u32 reg, u32 expected) {
AppendStoreVgpr(&code, reg, static_cast<u32>(test.expected.size()));
test.expected.push_back(expected);
};
for (u32 i = 0; i < test.initial.size(); ++i) {
AppendVMovU32(&code, 30, i * sizeof(u32));
AppendBufferLoadDword(&code, 12u + i, 30);
}
for (u32 selector = 0; selector < 6; ++selector) {
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, 0xa1b2c3d4u);
code.push_back(EncodeVop2(0x0b, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, selector, unused));
store(26, partial_expected[selector * 3u + unused]);
}
}
// Full result still truncates the 48-bit unsigned product to 32 bits.
code.push_back(EncodeVop2(0x0b, 26, 249, 14));
code.push_back(EncodeVop2Sdwa(14));
store(26, 0xfe000001u);
// Either multiplicand may alias the partial destination.
code.push_back(EncodeVop2(0x0b, 12, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 5, 2));
store(12, 0xa1800003u);
code.push_back(EncodeVop2(0x0b, 13, 249, 13));
code.push_back(EncodeVop2Sdwa(InlineU32(3), 2, 2, 6, 4,
0, 0, 0, 0, 0, 0, 1));
store(13, 0xcd80e080u);
// Source-byte sign extension happens before the unsigned 24-bit multiply.
AppendSMovLiteral(&code, 18, 0x12348056u);
code.push_back(EncodeVop2(0x0b, 26, 249, InlineU32(3)));
code.push_back(EncodeVop2Sdwa(18, 5, 0, 1, 6, 1, 0, 0, 0, 0, 0, 1, 1));
store(26, 0xfe800000u);
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, 0xa1b2c3d4u);
code.push_back(EncodeSMovB32(126, InlineU32(0)));
code.push_back(EncodeVop2(0x0b, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 4, unused));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
store(26, 0xa1b2c3d4u);
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_MUL_U32_U24, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_MUL_U32_U24", 25u}};
test.required_spirv = {"OpIMul", "OpBitFieldUExtract", "OpBitFieldSExtract"};
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop2SdwaLshrrevCapturedByte1Source() {
using O = ShaderOpcode;
@@ -35937,6 +36079,8 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop1SdwaNotPreservesHighWordDestination);
AddCase(Vop1SdwaNotPartialSourcesAndDestinations);
AddCase(Vop1SdwaMovByteDestinations);
cases.push_back(Vop1SdwaMovByteSources(32));
cases.push_back(Vop1SdwaMovByteSources(64));
AddCase(Vop2SdwaSubNcExactByte2Destination);
AddCase(Vop2SdwaAddNcCapturedHighWordDestination);
AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends);
@@ -35944,6 +36088,8 @@ std::vector<TestCase> MakeCases() {
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64));
cases.push_back(Vop2SdwaMulI24Destinations(32));
cases.push_back(Vop2SdwaMulI24Destinations(64));
cases.push_back(Vop2SdwaMulU24Destinations(32));
cases.push_back(Vop2SdwaMulU24Destinations(64));
AddCase(Vop2SdwaLshrrevCapturedByte1Source);
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
AddCase(Vop3CvtPkI16I32Captured);
@@ -41935,6 +42081,8 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--sdwa-mov-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop1SdwaMovByteDestinations());
RunCase(&vulkan, Vop1SdwaMovByteSources(32));
RunCase(&vulkan, Vop1SdwaMovByteSources(64));
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-ffbh-only") == 0) {
@@ -41984,6 +42132,15 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorIntegerOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-mul-u24-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop2SdwaMulU24Destinations(32));
RunCase(&vulkan, Vop2SdwaMulU24Destinations(64));
RunCase(&vulkan, Vop2SdwaMulI24Destinations(32));
RunCase(&vulkan, Vop2SdwaMulI24Destinations(64));
RunCase(&vulkan, VectorIntegerOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop3CvtPkI16I32Captured());