[GPU] Map half-wave shuffle lanes into the host subgroup

This commit is contained in:
kuba 2026-10-04 11:16:52 +02:00
parent 5c9b409e18
commit 5cac7c7812
3 changed files with 29 additions and 18 deletions

View File

@ -3955,7 +3955,7 @@ public static partial class Gen5SpirvTranslator
{
targetLane = BitwiseAnd(targetLane, UInt(31));
}
var shuffled = ShuffleLane(value, targetLane);
var shuffled = ShuffleHalfWaveLane(value, targetLane);
if (control.FetchInactive)
{
return shuffled;
@ -3968,7 +3968,7 @@ public static partial class Gen5SpirvTranslator
UInt(1),
UInt(0));
var sourceActive = IsNotZero(
ShuffleLane(activeWord, targetLane));
ShuffleHalfWaveLane(activeWord, targetLane));
return _module.AddInstruction(
SpirvOp.Select,
_uintType,
@ -3991,7 +3991,7 @@ public static partial class Gen5SpirvTranslator
// operations are limited to a single half-wave for some encodings, so we
// must not clamp wave64 lanes to 31; use the full lane mask instead.
safeTarget = BitwiseAnd(safeTarget, UInt(_waveLaneCount == 64 ? 63u : 31u));
var shuffled = ShuffleLane(value, safeTarget);
var shuffled = ShuffleHalfWaveLane(value, safeTarget);
var sourceAvailable = inRange;
if (!control.FetchInactive)
@ -4002,7 +4002,7 @@ public static partial class Gen5SpirvTranslator
Load(_boolType, _exec),
UInt(1),
UInt(0));
var shuffledActive = ShuffleLane(activeWord, safeTarget);
var shuffledActive = ShuffleHalfWaveLane(activeWord, safeTarget);
sourceAvailable = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
@ -5124,11 +5124,9 @@ public static partial class Gen5SpirvTranslator
}
var targetLane = IAdd(rowBase, selector);
// Mask to guest wave size — on Radeon hardware DPP is limited to a
// single half-wave for some encodings, but we must not clamp wave64
// lanes to 31; use the full lane mask instead.
// Select a guest lane first; ShuffleHalfWaveLane maps it to the host subgroup.
targetLane = BitwiseAnd(targetLane, UInt(_waveLaneCount == 64 ? 63u : 31u));
var shuffled = ShuffleLane(value, targetLane);
var shuffled = ShuffleHalfWaveLane(value, targetLane);
var fetchInactive = (control.OperandSelect & 1) != 0;
if (fetchInactive)
{
@ -5142,7 +5140,7 @@ public static partial class Gen5SpirvTranslator
UInt(1),
UInt(0));
var sourceActive = IsNotZero(
ShuffleLane(activeWord, targetLane));
ShuffleHalfWaveLane(activeWord, targetLane));
return _module.AddInstruction(
SpirvOp.Select,
_uintType,

View File

@ -8303,6 +8303,15 @@ public static partial class Gen5SpirvTranslator
? value
: _module.AddInstruction(SpirvOp.GroupNonUniformShuffle, _uintType, UInt(3), value, lane);
// DPP row/quad operations and PERMLANE16 never cross a 32-lane half-wave.
// Guest lanes 32..63 belong to the second host subgroup on 32-lane devices;
// using those guest indices directly in OpGroupNonUniformShuffle is undefined.
// Preserve the current physical half on devices whose subgroup is wider than 32.
private uint ShuffleHalfWaveLane(uint value, uint guestLane) =>
_subgroupInvocationIdInput == 0 ? value : ShuffleLane(value,
BitwiseOr(BitwiseAnd(Load(_uintType, _subgroupInvocationIdInput), UInt(0xFFFF_FFE0)),
BitwiseAnd(guestLane, UInt(31))));
private uint CurrentLaneBit()
{
if (_subgroupInvocationIdInput == 0)

View File

@ -17,12 +17,16 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO
: IClassFixture<HeadlessVulkanFixture>
{
[Theory]
[InlineData("Dpp8")]
[InlineData("Dpp16")]
[InlineData("Permlane16")]
[InlineData("Permlanex16")]
[InlineData("Reduction")]
public void Wave64Shuffle_PreservesUpperHalf(string operation)
[InlineData("Dpp8", 32)]
[InlineData("Dpp8", 64)]
[InlineData("Dpp16", 32)]
[InlineData("Dpp16", 64)]
[InlineData("Permlane16", 32)]
[InlineData("Permlane16", 64)]
[InlineData("Permlanex16", 32)]
[InlineData("Permlanex16", 64)]
[InlineData("Reduction", 64)]
public void WaveShuffle_PreservesEachHalf(string operation, uint waveSize)
{
var vulkan = fixture.Vulkan;
if (!GatePrerequisites.Ready(vulkan, shaderInt64: true)) return;
@ -79,7 +83,7 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO
var (plan, resources, layout) = Prepare(Program([.. instructions]));
var request = new ShaderCompileRequest(plan, resources, layout)
{
LocalSizeX = 8, LocalSizeY = 8, ThreadCountX = 8, ThreadCountY = 8, WaveSize = 64,
LocalSizeX = 8, LocalSizeY = waveSize / 8, ThreadCountX = 8, ThreadCountY = waveSize / 8, WaveSize = waveSize,
};
Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error);
using var harness = new ImageTestHarness(vulkan);
@ -90,7 +94,7 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO
harness.Run(() => runner.Dispatch(registers,
new Dictionary<DescriptorBindingKind, GpuBuffer[]> { [DescriptorBindingKind.Buffers] = [result] }, 1));
var actual = runner.ReadBack(result, 0, 256);
for (uint lane = 0; lane < 64; lane++)
for (uint lane = 0; lane < waveSize; lane++)
{
var expected = operation switch
{
@ -104,6 +108,6 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO
Assert.Equal(expected, BinaryPrimitives.ReadUInt32LittleEndian(actual.AsSpan((int)lane * 4)));
}
harness.AssertNoValidationMessages();
output.WriteLine($"Verified {operation} on {vulkan.DeviceName}.");
output.WriteLine($"Verified wave{waveSize} {operation} on {vulkan.DeviceName}.");
}
}