shader: disable DPP16 writes from EXEC-disabled sources without BOUND_CTRL

This commit is contained in:
nmzik
2026-10-25 01:29:02 +02:00
parent 67c89e0915
commit 9a20c6f738
2 changed files with 69 additions and 17 deletions
@@ -147,9 +147,24 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
// Only row shifts (0x101-0x10f, 0x111-0x11f) can vacate a lane; other controls always read one.
const bool row_shift = flags.control > 0x100u && flags.control < 0x120u &&
flags.control != 0x110u;
if (!flags.bound_control && row_shift) {
if (flags.bound_control || (flags.fetch_inactive && !row_shift)) {
return writable;
}
// Without BOUND_CTRL, a vacated source or, without FI, an EXEC-disabled one disables the write.
const auto target = EmitDppTargetLane(state, flags);
if (row_shift) {
writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable, target.valid);
}
if (!flags.fetch_inactive) {
// Vacated lanes target outside their row; wrapping keeps the ballot index in range.
auto source = target.lane;
if (row_shift) {
lane = lane != 0 ? lane : EmitSubgroupLocalInvocationId(state);
source = EmitNative<spv::OpBitFieldInsert, IR::Type::U32>(
state, lane, source, ConstantU32(state, 0), ConstantU32(state, 4));
}
writable = Binary(state, spv::OpLogicalAnd, TypeBool(state), writable,
EmitDppTargetLane(state, flags).valid);
EmitSourceLaneActive(ctx, exec, source));
}
return writable;
}
+52 -15
View File
@@ -28298,25 +28298,56 @@ TestCase VectorDppBankMaskPreservesDestination() {
return test;
}
TestCase VectorDppBoundsControlZeroPreservesDestination() {
TestCase VectorDppInactiveSource(bool row_shift, bool bound_ctrl, u32 wave_size) {
using O = ShaderOpcode;
constexpr u32 sentinel = 0xaaaaaaaau;
// Each half mixes active, EXEC-disabled and (for row_shr:1) vacated sources.
const u32 masks[] = {0x5f5ff5f5u, 0xf5f55f5fu};
std::vector<u32> code;
AppendVMovLiteral(&code, 2, 0xaaaaaaaau);
AppendVMovU32(&code, 1, 100);
code.push_back(EncodeVop2(0x25, 2, 250, 1));
code.push_back(EncodeVop2Dpp(0, 0x111));
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 0));
AppendBufferStoreDword(&code, 2, 3);
code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1));
AppendVMovU32(&code, 7, 1000);
code.push_back(EncodeVop2(0x25, 7, Vgpr(0), 7));
AppendVMovLiteral(&code, 2, sentinel);
AppendSMovLiteral(&code, 126, masks[0]);
if (wave_size == 64) AppendSMovLiteral(&code, 127, masks[1]);
// v_add_nc_u32_dpp v2, v1, v7 with row_shr:1 or quad_perm:[3,2,1,0]; bit 19 is BOUND_CTRL.
code.push_back(EncodeVop2(0x25, 2, 250, 7));
code.push_back(EncodeVop2Dpp(1, row_shift ? 0x111u : 0x01bu) |
(bound_ctrl ? 1u << 19 : 0u));
AppendSMovLiteral(&code, 126, 0xffffffffu);
if (wave_size == 64) AppendSMovLiteral(&code, 127, 0xffffffffu);
AppendStoreVgprAtLaneDwordOffset(&code, 2, 0, 0);
AppendEnd(&code);
TestCase test;
test.name = "VectorDppBoundsControlZeroPreservesDestination";
test.code = code;
test.expected = {0xaaaaaaaau, 100, 101, 102, 103, 104, 105, 106};
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_LSHLREV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 8;
const char *names[2][2][2] = {
{{"VectorDppQuadPermInactiveSourceKeepsWave32",
"VectorDppQuadPermInactiveSourceKeepsWave64"},
{"VectorDppQuadPermInactiveSourceZeroWave32",
"VectorDppQuadPermInactiveSourceZeroWave64"}},
{{"VectorDppRowShrInactiveSourceKeepsWave32",
"VectorDppRowShrInactiveSourceKeepsWave64"},
{"VectorDppRowShrInactiveSourceZeroWave32",
"VectorDppRowShrInactiveSourceZeroWave64"}}};
test.name = names[row_shift][bound_ctrl][wave_size == 64];
test.code = std::move(code);
test.expected.resize(wave_size, sentinel);
for (u32 lane = 0; lane < wave_size; ++lane) {
const auto mask = masks[lane / 32u];
if ((mask & (1u << (lane % 32u))) == 0) continue;
const bool vacated = row_shift && lane % 16u == 0;
const u32 source = row_shift ? lane - 1u : (lane & ~3u) | (3u - (lane & 3u));
if (!vacated && (mask & (1u << (source % 32u))) != 0) {
test.expected[lane] = 1100u + source + lane;
} else if (bound_ctrl) {
test.expected[lane] = 1000u + lane;
}
}
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32,
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
@@ -44177,7 +44208,10 @@ std::vector<TestCase> MakeCases() {
cases.push_back(ComputeWave64Minimum(false));
cases.push_back(ComputeWave64Minimum(true));
AddCase(VectorDppBankMaskPreservesDestination);
AddCase(VectorDppBoundsControlZeroPreservesDestination);
for (const bool row_shift : {false, true})
for (const bool bound_ctrl : {false, true})
for (const u32 wave_size : {32u, 64u})
cases.push_back(VectorDppInactiveSource(row_shift, bound_ctrl, wave_size));
AddCase(Vop3FmacF32NegatedSourceAccumulates);
AddCase(VectorMadF32RoundsProduct);
AddCase(VectorMadF32FlushesDenorms);
@@ -50614,7 +50648,10 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorReadFirstLaneExecMasks(32));
RunCase(&vulkan, VectorReadFirstLaneExecMasks(64));
RunCase(&vulkan, VectorDppBankMaskPreservesDestination());
RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination());
for (const bool row_shift : {false, true})
for (const bool bound_ctrl : {false, true})
for (const u32 wave_size : {32u, 64u})
RunCase(&vulkan, VectorDppInactiveSource(row_shift, bound_ctrl, wave_size));
for (const u32 op_sel : {0u, 1u, 2u})
for (const u32 wave_size : {32u, 64u})
RunCase(&vulkan, VectorPermlane16InactiveSource(op_sel, wave_size));