[AGC] Correct VReadlaneB32 decode and emission (#237)

VReadlaneB32 (VOP3 0x360) had two bugs:

1. Decode: VOP3 decode always set destinations = Vector(word & 0xFF).
   For VReadlaneB32, bits 0-7 are unused — the scalar destination is
   in bits 8-14. Now decodes as Scalar((word >> 8) & 0x7F).

2. Emission: VReadlaneB32 was in the VMovB32 fall-through group,
   just returning GetRawSource(instruction, 0) — reading the current
   lane's value. By ISA, sdst = vsrc0[lane(src1)], which requires
   reading a different lane's value. Now uses SPIR-V
   GroupNonUniformBroadcast(scope=Subgroup, value=src0, lane=src1)
   when subgroup operations are available, with a fallback to the
   current-lane simplification when not.

3. Routing: TryEmitVectorAlu called TryGetVectorDestination first,
   which checks for VectorRegister kind. With the scalar destination
   fix, VReadlaneB32 now routes to a new TryEmitReadlane handler
   before the vector destination check.

4. Subgroup capability: UsesSubgroupShuffle now includes VReadlaneB32
   so GroupNonUniform capability is enabled when needed.

Verified: dotnet build 0 errors/0 warnings, 26/26 tests pass,
ShaderDump all programs behaved as expected.
This commit is contained in:
kostyaff
2026-07-16 00:27:26 +03:00
committed by GitHub
parent 83303e953e
commit b5930465f2
3 changed files with 48 additions and 2 deletions
@@ -24,6 +24,11 @@ public static partial class Gen5SpirvTranslator
return TryEmitVectorCompare(instruction, out error);
}
if (instruction.Opcode == "VReadlaneB32")
{
return TryEmitReadlane(instruction, out error);
}
if (!TryGetVectorDestination(instruction, out var destination))
{
error = "missing vector destination";
@@ -34,7 +39,6 @@ public static partial class Gen5SpirvTranslator
switch (instruction.Opcode)
{
case "VMovB32":
case "VReadlaneB32":
case "VReadfirstlaneB32":
result = GetRawSource(instruction, 0);
break;
@@ -2365,6 +2369,42 @@ public static partial class Gen5SpirvTranslator
StoreWaveMask(106, carry);
}
private bool TryEmitReadlane(
Gen5ShaderInstruction instruction,
out string error)
{
error = string.Empty;
if (instruction.Destinations.Count == 0 ||
instruction.Destinations[0].Kind != Gen5OperandKind.ScalarRegister)
{
error = "VReadlaneB32 expects scalar destination";
return false;
}
var destination = instruction.Destinations[0].Value;
var src0 = GetRawSource(instruction, 0);
if (_subgroupInvocationIdInput != 0)
{
// sdst = vsrc0[lane(src1)] — broadcast from the specified lane.
var laneSelect = GetRawSource(instruction, 1);
var broadcast = _module.AddInstruction(
SpirvOp.GroupNonUniformBroadcast,
_uintType,
UInt(3), // Subgroup scope
src0,
laneSelect);
StoreS(destination, broadcast);
}
else
{
// Fallback: no subgroup ops, read current lane's value.
StoreS(destination, src0);
}
return true;
}
private uint EmitPermlane16(
Gen5ShaderInstruction instruction,
bool exchangeRows)
@@ -2507,7 +2507,7 @@ public static partial class Gen5SpirvTranslator
private bool UsesSubgroupShuffle() =>
_state.Program.Instructions.Any(instruction =>
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32" or "VReadlaneB32");
private bool UsesWaveControl() =>
_state.Program.Instructions.Any(instruction =>
@@ -1467,6 +1467,12 @@ public static class Gen5ShaderTranslator
Gen5Operand.Source((extra >> 18) & 0x1FF, literal),
];
destinations = [Gen5Operand.Vector(word & 0xFF)];
if (opcode == "VReadlaneB32")
{
// VReadlaneB32 writes to scalar destination (bits 8-14), not vector.
// Bits 0-7 are unused for this opcode.
destinations = [Gen5Operand.Scalar((word >> 8) & 0x7F)];
}
var isVop3B = IsVop3BOpcode((word >> 16) & 0x3FF);
control = new Gen5Vop3Control(
isVop3B ? 0 : (word >> 8) & 0x7,