shader: read lane 0 in V_READFIRSTLANE when EXEC is zero instead of an undefined lane

This commit is contained in:
Nawid3333
2026-10-22 01:17:31 +02:00
committed by nmzik
parent a52d1ed4e2
commit 919fac13d0
2 changed files with 53 additions and 22 deletions
@@ -493,29 +493,24 @@ uint32_t ValueEmitContext::Ballot(IR::Value predicate) {
}
uint32_t ValueEmitContext::FirstLane(uint32_t ballot) {
if (other_half == nullptr) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpGroupNonUniformBallotFindLSB, TypeU32(state), result,
ConstantU32(state, spv::ScopeSubgroup), ballot);
return result;
// An empty ballot (EXEC zero) gives lane 0, which V_READFIRSTLANE reads on hardware. FindILsb
// returns -1 for zero, unlike BallotFindLSB, whose result is then undefined.
const auto first = [&](uint32_t word) {
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), bits, ballot, word);
return std::pair {bits, EmitGlsl<GLSLstd450FindILsb, IR::Type::U32>(state, bits)};
};
const auto [low, low_first] = first(0);
if (state.program.wave_size == 32u) {
return EmitGlsl<GLSLstd450SMax, IR::Type::U32>(state, low_first, ConstantU32(state, 0));
}
const auto low = state.builder.AllocateId();
const auto high = state.builder.AllocateId();
const auto low_first = state.builder.AllocateId();
const auto high_first = state.builder.AllocateId();
const auto low_active = state.builder.AllocateId();
const auto result = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), low, ballot, 0);
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), high, ballot, 1);
state.builder.AddFunction(spv::OpExtInst, TypeU32(state), low_first, GlslStd450(state),
GLSLstd450FindILsb, low);
state.builder.AddFunction(spv::OpExtInst, TypeU32(state), high_first, GlslStd450(state),
GLSLstd450FindILsb, high);
state.builder.AddFunction(spv::OpINotEqual, TypeBool(state), low_active, low,
ConstantU32(state, 0));
state.builder.AddFunction(spv::OpSelect, TypeU32(state), result, low_active, low_first,
EmitAddU32(state, high_first, ConstantU32(state, 32)));
return result;
const auto high_first = first(1).second;
const auto high_lane = EmitGlsl<GLSLstd450SMax, IR::Type::U32>(
state, EmitBinaryU32(state, spv::OpBitwiseOr, high_first, ConstantU32(state, 32)),
ConstantU32(state, 0));
return Select(state, TypeU32(state),
Binary(state, spv::OpINotEqual, TypeBool(state), low, ConstantU32(state, 0)),
low_first, high_lane);
}
uint32_t ValueEmitContext::Shuffle(const IR::Inst& inst, size_t index, uint32_t lane) {
+36
View File
@@ -23006,6 +23006,38 @@ TestCase ScalarZeroBitOps() {
return test;
}
// V_READFIRSTLANE reads the first EXEC lane, or lane 0 when EXEC is zero; v1 = 100 + lane.
TestCase VectorReadFirstLaneExecMasks(u32 wave_size) {
using O = ShaderOpcode;
TestCase test;
test.name = wave_size == 32 ? "VectorReadFirstLaneExecMasksWave32"
: "VectorReadFirstLaneExecMasksWave64";
auto &code = test.code;
AppendVMovU32(&code, 1, 100);
code.push_back(EncodeVop2(0x25, 1, Vgpr(0), 1));
code.push_back(EncodeSop1(0x04, 10, 126)); // s_mov_b64 s[10:11], exec
const uint64_t masks[] = {0, 1ull << 5, 0x80000000ull, 1ull << 32, 1ull << 63, 0xffff000000000000ull};
for (const uint64_t mask : masks) {
if (wave_size == 32 && mask > UINT32_MAX) continue;
AppendSMovLiteral(&code, 126, u32(mask));
if (wave_size == 64) AppendSMovLiteral(&code, 127, u32(mask >> 32));
code.push_back(EncodeVop1(0x02, 20, Vgpr(1)));
code.push_back(EncodeSop1(0x04, 126, 10));
AppendStoreSgpr(&code, 20, u32(test.expected.size()));
test.expected.push_back(100 + (mask == 0 ? 0 : std::countr_zero(mask)));
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32, O::S_MOV_B64,
O::V_READFIRSTLANE_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] = test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase ScalarSextMasks(u32 bits, u32 wave_size) {
using O = ShaderOpcode;
TestCase test;
@@ -43094,6 +43126,8 @@ std::vector<TestCase> MakeCases() {
cases.push_back(VectorDppRowShare(64));
cases.push_back(VectorDppMirrorAndRotate(32));
cases.push_back(VectorDppMirrorAndRotate(64));
cases.push_back(VectorReadFirstLaneExecMasks(32));
cases.push_back(VectorReadFirstLaneExecMasks(64));
cases.push_back(ComputeWave64Minimum(false));
cases.push_back(ComputeWave64Minimum(true));
AddCase(VectorDppBankMaskPreservesDestination);
@@ -49186,6 +49220,8 @@ int main(int argc, char **argv) {
RunCase(&vulkan, VectorDppRowShare(64));
RunCase(&vulkan, VectorDppMirrorAndRotate(32));
RunCase(&vulkan, VectorDppMirrorAndRotate(64));
RunCase(&vulkan, VectorReadFirstLaneExecMasks(32));
RunCase(&vulkan, VectorReadFirstLaneExecMasks(64));
RunCase(&vulkan, VectorDppBankMaskPreservesDestination());
RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination());
return 0;