shader: preserve wave-wide vector-store completion before publication

This commit is contained in:
nmzik
2026-10-04 00:17:28 +04:00
parent 42a315f0e4
commit f58014ff0e
16 changed files with 144 additions and 32 deletions
+40 -4
View File
@@ -29003,11 +29003,14 @@ TestCase Wave64AppendConsumeHighHalf() {
return test;
}
TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false) {
TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false,
bool store_completion = false) {
using O = ShaderOpcode;
constexpr u32 groups = 31;
TestCase test;
test.name = dlc_only ? "BufferWorkgroupPublicationDlc"
test.name = store_completion ? (wave_size == 64 ? "Wave64StoreCompletionPublication"
: "Wave32StoreCompletionPublication")
: dlc_only ? "BufferWorkgroupPublicationDlc"
: wave_size == 64 ? "BufferWorkgroupPublicationWave64"
: "BufferWorkgroupPublicationWave32";
auto &code = test.code;
@@ -29022,15 +29025,21 @@ TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false) {
AppendVMovU32(&code, 2, 0);
code.push_back(EncodeSMovB32(21, InlineU32(0)));
AppendSMovLiteral(&code, 22, 1000000);
if (store_completion) AppendSMovLiteral(&code, 26, groups * 4);
code.push_back(EncodeSopc(0x06, 20, InlineU32(0)));
const auto first_ticket = code.size();
code.push_back(0);
const auto poll = code.size();
if (store_completion) {
code.push_back(EncodeVop2(0x25, 4, 26, 20));
AppendVMovU32(&code, 3, 0);
AppendBufferStoreOpcode(&code, 0x32, 3, 4, true);
}
code.push_back(EncodeMubuf0(0x0c, 0, false, true, !dlc_only) | (1u << 15u));
code.push_back(EncodeMubuf1(2, 12, 20));
code.push_back(EncodeSopp(0x0c, 0)); // S_WAITCNT vmcnt(0).
code.push_back(EncodeVop1(0x02, 23, Vgpr(2)));
code.push_back(EncodeVop1(0x02, 23, Vgpr(store_completion ? 3 : 2)));
code.push_back(EncodeSopc(0x07, 23, InlineU32(0)));
const auto ready = code.size();
code.push_back(0);
@@ -29045,15 +29054,33 @@ TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false) {
code.push_back(EncodeVop2(0x25, 2, Vgpr(1), 2));
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 2));
code.push_back(EncodeVop2(0x25, 20, InlineU32(4), 20));
if (store_completion) {
// The last lane writes the payload; lane zero publishes readiness after the
// native wave-wide store completion, including both emulated wave64 halves.
code.push_back(EncodeVop1(0x02, 24, Vgpr(2)));
code.push_back(EncodeVop1(0x02, 25, Vgpr(20)));
code.push_back(EncodeSop1(0x04, 126, 193u));
code.push_back(EncodeVop1(0x01, 2, 24));
code.push_back(EncodeVop1(0x01, 20, 25));
code.push_back(EncodeSop2(0x1f, 126, InlineU32(1), InlineU32(wave_size - 1)));
}
// RDNA2 stores publish to L2 even without GLC/DLC on the producer.
AppendBufferStoreDword(&code, 2, 20);
if (store_completion) {
code.push_back(0xbbfd0000u); // Captured S_WAITCNT_VSCNT null, 0 at 5b10:0x2464.
code.push_back(EncodeSop1(0x04, 126, InlineU32(1)));
code.push_back(EncodeVop2(0x25, 4, 26, 20));
AppendVMovU32(&code, 3, 1);
AppendBufferStoreOpcode(&code, 0x32, 3, 4, true);
}
AppendEnd(&code);
test.initial.assign(groups + 1, 0);
test.initial.assign(groups * (store_completion ? 2 : 1) + 1, 0);
test.expected = {groups};
for (u32 ticket = 0; ticket < groups; ++ticket) {
test.expected.push_back((ticket + 1) * (ticket + 2) / 2);
}
if (store_completion) test.expected.resize(groups * 2 + 1, 1);
test.opcodes = {O::S_MOV_B64, O::S_MOV_B32, O::V_MOV_B32,
O::BUFFER_ATOMIC_ADD, O::V_READFIRSTLANE_B32,
O::V_LSHLREV_B32, O::S_CMP_EQ_U32, O::S_CMP_LG_U32,
@@ -29061,6 +29088,11 @@ TestCase BufferWorkgroupPublication(u32 wave_size, bool dlc_only = false) {
O::S_WAITCNT, O::BUFFER_LOAD_DWORD, O::V_ADD_NC_U32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"Coherent", "Volatile", "OpAtomicIAdd", "OpLoopMerge"};
if (store_completion) {
test.opcodes.push_back(O::S_WAITCNT_VSCNT);
test.opcodes.push_back(O::S_LSHL_B64);
test.required_spirv.push_back("OpControlBarrier %uint_3 %uint_1 %uint_2116");
}
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
@@ -32085,6 +32117,8 @@ std::vector<TestCase> MakeCases() {
cases.push_back(BufferWorkgroupPublication(32));
cases.push_back(BufferWorkgroupPublication(64));
cases.push_back(BufferWorkgroupPublication(32, true));
cases.push_back(BufferWorkgroupPublication(32, false, true));
cases.push_back(BufferWorkgroupPublication(64, false, true));
AddCase(IntegerAddSubMul);
AddCase(BitwiseOps);
@@ -37540,6 +37574,8 @@ int main(int argc, char **argv) {
RunCase(&vulkan, BufferWorkgroupPublication(32));
RunCase(&vulkan, BufferWorkgroupPublication(64));
RunCase(&vulkan, BufferWorkgroupPublication(32, true));
RunCase(&vulkan, BufferWorkgroupPublication(32, false, true));
RunCase(&vulkan, BufferWorkgroupPublication(64, false, true));
RunCase(&vulkan, BufferAtomicVariants());
return 0;
}
+62 -18
View File
@@ -1817,28 +1817,71 @@ void TestNggVertexEntryState() {
}
}
void TestNewShaderRecompilerSopkWaitcntMarkers() {
const uint32_t shader[] = {
EncodeSopk(0x17, 125, 0xffff), // s_waitcnt_vscnt null, 0xffff
EncodeSopk(0x18, 125, 0), // s_waitcnt_vmcnt null, 0
EncodeSopk(0x19, 125, 0), // s_waitcnt_expcnt null, 0
EncodeSopk(0x1a, 125, 0), // s_waitcnt_lgkmcnt null, 0
EncodeSopp(0x01, 0), // s_endpgm
};
void TestNativeStoreCompletion() {
using namespace ShaderRecompiler;
const std::array register_wait = {EncodeSopk(0x17, 4, static_cast<int16_t>(0x9234)), EncodeSopp(0x01)};
Decoder::Program decoded;
Decoder::DecodeProgram(register_wait, decoded);
const auto &wait = decoded.instructions.front();
Check(wait.opcode == Decoder::Opcode::S_WAITCNT_VSCNT &&
wait.src0.kind == Decoder::OperandKind::Sgpr && wait.src0.reg == 4 &&
wait.src1.value == 0x9234u,
"store completion lost its counter kind, source, or unsigned immediate");
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
ShaderComputeInputInfo compute{};
compute.threads_num[1] = compute.threads_num[2] = 1;
options.input_info.compute = &compute;
for (const uint32_t wave_size : {32u, 64u}) {
compute.threads_num[0] = compute.wave_size = options.wave_size = wave_size;
const std::array barrier_only = {0xbbfd0000u, EncodeSopp(0x01)};
const auto minimal = RecompileForTest(barrier_only, options);
CheckSpirvBinaryValidates(minimal.spirv);
Check(SpirvContainsCapability(minimal.spirv, 61),
"standalone store completion lacks GroupNonUniform capability");
auto result = RecompileForTest(shader, options);
Check((result.decoded_dump.find("s_waitcnt 0") != std::string::npos),
"new decoder did not decode SOPK waitcnt marker");
Check((result.decoded_dump.find("s_waitcnt 65535") != std::string::npos),
"SOPK waitcnt marker immediate was not kept unsigned");
Check((result.ir_dump.find("Waitcnt null, 0x00000000") != std::string::npos),
"SOPK waitcnt did not lower to an IR marker");
Check((result.ir_dump.find("Waitcnt null, 0x0000ffff") != std::string::npos),
"SOPK waitcnt marker immediate was not translated as 16-bit unsigned");
CheckSpirvBinaryValidates(result.spirv);
// Captured 5b10:0x2464 drains node stores before the next parent atomic.
const std::array publication = {
EncodeVop1(0x01, 1, 129),
EncodeMubuf0(0x1c, 0, false), EncodeMubuf1(1, 0, 0),
0xbbfd0000u,
EncodeMubuf0(0x32, 4, false, true), EncodeMubuf1(1, 0, 0),
EncodeSopp(0x01),
};
const auto result = RecompileForTest(publication, options);
CheckSpirvBinaryValidates(result.spirv);
const auto store = result.ir_dump.find("StoreBufferU32");
const auto completion = result.ir_dump.find("StoreCompletion");
const auto atomic = result.ir_dump.find("BufferAtomicIAdd32");
Check(store < completion && completion < atomic && atomic != std::string::npos,
"store completion moved outside its native publication order");
const auto source = DisassembleSpirvBinary(result.spirv);
const auto barrier = source.find("OpControlBarrier %uint_3 %uint_1 %uint_2116");
Check(barrier != std::string::npos &&
source.find("OpStore") < barrier &&
barrier < source.find("OpAtomicIAdd") &&
source.find("OpControlBarrier", barrier + 1) == std::string::npos,
"store completion must synchronize the subgroup once after both wave halves");
}
const std::array no_store_wait = {
EncodeSopk(0x17, 125, 0xffff), // Effective VSCNT 63 waits for nothing.
EncodeSopk(0x18, 125, 0), EncodeSopk(0x19, 125, 0),
EncodeSopk(0x1a, 125, 0), EncodeSopp(0x01),
};
const auto no_wait = RecompileForTest(no_store_wait, options);
const auto source = DisassembleSpirvBinary(no_wait.spirv);
Check(source.find("OpControlBarrier") == std::string::npos &&
source.find("OpMemoryBarrier") == std::string::npos,
"other wait counters or VSCNT 63 became a store completion barrier");
#ifndef _WIN32
for (const uint32_t word : {EncodeSopk(0x17, 125, 1), register_wait[0]}) {
const std::array unsupported = {word, EncodeSopp(0x01)};
ExpectFatal([&] { (void)RecompileForTest(unsupported, options); },
"unsupported partial or register-based store wait was dropped");
}
#endif
}
void TestSopkCompareImmediateExtension() {
@@ -14444,6 +14487,7 @@ int main() {
TestNewShaderDecoderArchitecture();
TestImageAddressOperands();
TestSopkCompareImmediateExtension();
TestNativeStoreCompletion();
TestNewShaderRecompilerCapturedVopcSdwaCmpxClass();
TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16();
TestNewShaderRecompilerIrLookupMissFailsExplicitly();