VMovrelsB32 opcode fix (#825)

This commit is contained in:
Astell
2026-08-17 23:00:28 +02:00
committed by GitHub
parent 7521295ee1
commit ad115ddcbc
4 changed files with 319 additions and 0 deletions
@@ -101,6 +101,12 @@ public static partial class Gen5SpirvTranslator
return false;
}
if (instruction.Opcode is "VMovrelsB32" or "VMovreldB32" or
"VMovrelsdB32" or "VMovrelsd2B32")
{
return TryEmitMoveRelative(instruction, destination, out error);
}
uint result;
switch (instruction.Opcode)
{
@@ -1013,6 +1019,76 @@ public static partial class Gen5SpirvTranslator
return true;
}
// V_MOVREL*_B32: register-relative moves. M0 is added at run time to the
// source and/or destination register number encoded in the instruction,
// which is how shader compilers implement a dynamically indexed array
// that stayed in registers instead of being spilled to memory. Astro Bot
// ships pixel shaders that index a small register-resident table this
// way; without this the whole shader fails to translate.
//
// V_MOVRELS_B32 vdst = vgpr[src0 + M0]
// V_MOVRELD_B32 vgpr[vdst + M0] = src0
// V_MOVRELSD_B32 vgpr[vdst + M0] = vgpr[src0 + M0]
// V_MOVRELSD_2_B32 vgpr[vdst + M0[25:16]] = vgpr[src0 + M0[9:0]]
//
// The relative forms address the VGPR file relative to the wave's own
// allocation base, which is exactly what the private register array
// models, so the encoded number and M0 simply add.
private bool TryEmitMoveRelative(
Gen5ShaderInstruction instruction,
uint destination,
out string error)
{
error = string.Empty;
if (instruction.Sources.Count == 0)
{
error = $"missing source for {instruction.Opcode}";
return false;
}
var m0 = LoadS(M0ScalarRegister);
uint sourceOffset;
uint destinationOffset;
if (instruction.Opcode == "VMovrelsd2B32")
{
sourceOffset = BitwiseAnd(m0, UInt(0x3FF));
destinationOffset = BitwiseAnd(ShiftRightLogical(m0, UInt(16)), UInt(0x3FF));
}
else
{
sourceOffset = m0;
destinationOffset = m0;
}
uint value;
if (instruction.Opcode == "VMovreldB32")
{
// Only the destination is relative here; src0 is an ordinary
// operand and may be an SGPR or an inline/literal constant.
value = GetRawSource(instruction, 0);
}
else
{
var source = instruction.Sources[0];
if (source.Kind != Gen5OperandKind.VectorRegister)
{
error = $"{instruction.Opcode} source must be a vector register";
return false;
}
value = LoadVDynamic(IAdd(UInt(source.Value), sourceOffset));
}
if (instruction.Opcode == "VMovrelsB32")
{
StoreV(destination, value);
return true;
}
StoreVDynamic(IAdd(UInt(destination), destinationOffset), value);
return true;
}
// Packed f16 (VOP3P) arithmetic. Each source register holds two f16 values,
// one per result lane. Every f16<->f32 conversion is done with the explicit
// integer sequences below (EmitHalfToFloat / EmitFloatToHalf) instead of
@@ -176,6 +176,11 @@ public static partial class Gen5SpirvTranslator
private const uint ImageDescriptorDwords = 8;
private const uint SamplerDescriptorDwords = 4;
private const int ScalarRegisterCount = 128;
// M0. Used as the runtime index added to the register numbers encoded in
// the V_MOVREL* instructions, and as the LDS/GDS base elsewhere.
private const uint M0ScalarRegister = 124;
private const long InitialScalarDefinition = -1;
private const long ConflictingScalarDefinition = -2;
private const long UnreachableScalarDefinition = -3;
@@ -5090,6 +5095,33 @@ public static partial class Gen5SpirvTranslator
_vectorRegisters,
UInt(register));
// The V_MOVREL* opcodes address the VGPR file with a register number that
// is only known at run time (encoded number + M0), so the access chain
// takes a computed index instead of a constant. The index is masked to
// the array bounds: SPIR-V leaves an out-of-range Private access chain
// undefined, and a mask costs nothing next to the surrounding load.
private uint DynamicVectorPointer(uint registerIndex) =>
_module.AddInstruction(
SpirvOp.AccessChain,
_privateUintPointer,
_vectorRegisters,
BitwiseAnd(registerIndex, UInt(VectorRegisterCount - 1)));
private uint LoadVDynamic(uint registerIndex) =>
Load(_uintType, DynamicVectorPointer(registerIndex));
private void StoreVDynamic(uint registerIndex, uint value)
{
var pointer = DynamicVectorPointer(registerIndex);
value = _module.AddInstruction(
SpirvOp.Select,
_uintType,
Load(_boolType, _exec),
value,
Load(_uintType, pointer));
Store(pointer, value);
}
private uint PackedHalfPointer(uint register) =>
_module.AddInstruction(
SpirvOp.AccessChain,
@@ -947,6 +947,7 @@ public static class Gen5ShaderTranslator
0x42 => "VMovreldB32",
0x43 => "VMovrelsB32",
0x44 => "VMovrelsdB32",
0x48 => "VMovrelsd2B32",
_ => string.Empty,
};