Compare commits

...

1 Commits

Author SHA1 Message Date
ParantezTech 867960b106 [Spirvtranslator] Added many instruction set for SPIR-V translator 2026-09-21 15:50:45 +03:00
15 changed files with 700 additions and 23 deletions

View File

@ -336,6 +336,7 @@ public static partial class Gen5MslTranslator
"VLshlrevB32" => $"(({RawSource(instruction, 1)}) << (({RawSource(instruction, 0)}) & 31u))",
"VLshrB32" => $"(({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))",
"VLshrrevB32" => $"(({RawSource(instruction, 1)}) >> (({RawSource(instruction, 0)}) & 31u))",
"VLshrrevB64" => EmitLshrrevB64(instruction, destination),
"VAshrI32" =>
AsUInt($"(as_type<int>({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))"),
"VAshrrevI32" =>
@ -471,6 +472,14 @@ public static partial class Gen5MslTranslator
return $"({AsUInt(signedLeft)} * {AsUInt(signedRight)})";
}
private string EmitLshrrevB64(Gen5ShaderInstruction instruction, uint destination)
{
var shift = Temp("uint", $"({RawSource(instruction, 0)}) & 63u");
var shifted = Temp("ulong", $"({RawSource64(instruction, 1)}) >> {shift}");
StoreVector(destination + 1, $"(uint)({shifted} >> 32)");
return $"(uint){shifted}";
}
private string EmitCvtPkU8F32(Gen5ShaderInstruction instruction)
{
var converted = Temp("uint", $"(uint)({F(instruction, 0)})");
@ -732,7 +741,12 @@ public static partial class Gen5MslTranslator
}
else
{
var signed = opcode.EndsWith("I32", StringComparison.Ordinal);
var signed16 = opcode.EndsWith("I16", StringComparison.Ordinal);
var signed = signed16 ||
opcode.EndsWith("I32", StringComparison.Ordinal) ||
opcode.EndsWith("I64", StringComparison.Ordinal);
var wide = opcode.EndsWith("I64", StringComparison.Ordinal) ||
opcode.EndsWith("U64", StringComparison.Ordinal);
var op = TrimCompare(opcode) switch
{
"Eq" => "==",
@ -749,9 +763,15 @@ public static partial class Gen5MslTranslator
return false;
}
var left = wide ? RawSource64(instruction, 0) : RawSource(instruction, 0);
var right = wide ? RawSource64(instruction, 1) : RawSource(instruction, 1);
condition = signed
? $"(as_type<int>({RawSource(instruction, 0)}) {op} as_type<int>({RawSource(instruction, 1)}))"
: $"(({RawSource(instruction, 0)}) {op} ({RawSource(instruction, 1)}))";
? signed16
? $"(int(short(({left}) & 0xFFFFu)) {op} int(short(({right}) & 0xFFFFu)))"
: wide
? $"(as_type<long>({left}) {op} as_type<long>({right}))"
: $"(as_type<int>({left}) {op} as_type<int>({right}))"
: $"(({left}) {op} ({right}))";
}
// Only EXEC-enabled lanes can pass; balloting the raw condition

View File

@ -45,5 +45,32 @@ public static partial class Gen5MslTranslator
StoreVector(instruction.Destinations[0].Value, $"{sourceActive} ? {value} : 0u");
return true;
}
private bool TryEmitDataShareBpermute(Gen5ShaderInstruction instruction,
Gen5DataShareControl control, out string error)
{
error = string.Empty;
if (instruction.Sources.Count != 2 || instruction.Destinations.Count != 1)
{
error = "invalid data-share bpermute operands";
return false;
}
if (IsSingleLaneStage)
{
error = "data-share bpermute requires subgroup lane access";
return false;
}
var lane = Temp("uint", "sharpemu_lane");
var address = Temp("uint", $"{RawSource(instruction, 0)} + {control.SingleOffsetBytes}u");
var index = Temp("uint", $"({address} >> 2u) & 31u");
var sourceLane = Temp("uint", $"({lane} & ~31u) | {index}");
var activeLanes = Temp("uint", "sharpemu_ballot(exec)");
var sourceActive = Temp("bool", $"(({activeLanes} >> ({sourceLane} & 31u)) & 1u) != 0u");
var safeSourceLane = Temp("uint", $"{sourceActive} ? {sourceLane} : {lane}");
var value = Temp("uint", $"simd_shuffle({RawSource(instruction, 1)}, (ushort){safeSourceLane})");
StoreVector(instruction.Destinations[0].Value, $"{sourceActive} ? {value} : 0u");
return true;
}
}
}

View File

@ -23,6 +23,19 @@ public static partial class Gen5MslTranslator
out string error)
{
error = string.Empty;
if (instruction.Opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay")
{
// Metal has no bound representation of GFX10's raw BVH
// descriptor in this backend. Preserve the hardware miss
// fallback used by the Vulkan path and keep the shader valid.
for (uint component = 0; component < 4; component++)
{
StoreVector(image.VectorData + component, "0u");
}
return true;
}
string texture;
string samplerName;
string kind;

View File

@ -134,7 +134,8 @@ public static partial class Gen5MslTranslator
foreach (var instruction in _request.Program.Instructions)
{
_usesLds |= instruction.Control is Gen5DataShareControl { Gds: false };
_usesLds |= instruction.Control is Gen5DataShareControl { Gds: false } &&
instruction.Opcode is not ("DsSwizzleB32" or "DsBpermuteB32");
_usesFormatLoads |= IsFormatBufferLoad(instruction.Opcode) ||
instruction.Opcode.StartsWith("TBufferStoreFormat", StringComparison.Ordinal);
if (instruction.Control is Gen5InterpolationControl interpolationControl)
@ -907,6 +908,7 @@ public static partial class Gen5MslTranslator
case "STrap":
return true;
case "SNop":
case "SSetregB32":
case "SWaitcnt":
case "SInstPrefetch":
case "STtraceData":
@ -1332,6 +1334,8 @@ public static partial class Gen5MslTranslator
{
case "DsSwizzleB32":
return TryEmitDataShareSwizzle(instruction, control, out error);
case "DsBpermuteB32":
return TryEmitDataShareBpermute(instruction, control, out error);
case "DsAppend":
case "DsConsume":
{

View File

@ -453,6 +453,30 @@ public static partial class Gen5SpirvTranslator
_uintType,
GetRawSource(instruction, 0));
break;
case "VFfbhU32":
{
var source = GetRawSource(instruction, 0);
var msb = Bitcast(
_uintType,
Ext(75, _intType, source));
var position = _module.AddInstruction(
SpirvOp.ISub,
_uintType,
UInt(31),
msb);
var nonzero = _module.AddInstruction(
SpirvOp.INotEqual,
_boolType,
source,
UInt(0));
result = _module.AddInstruction(
SpirvOp.Select,
_uintType,
nonzero,
position,
UInt(uint.MaxValue));
break;
}
case "VFfblB32":
result = Bitcast(
_uintType,
@ -680,6 +704,31 @@ public static partial class Gen5SpirvTranslator
SpirvOp.ShiftRightLogical,
reverse: true);
break;
case "VLshrrevB64":
{
// V_LSHRREV_B64 writes a VGPR pair. Source 0 supplies the
// shift count; source 1 is the 64-bit value to shift.
var shift = _module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
BitwiseAnd(GetRawSource(instruction, 0), UInt(63)));
var shifted = ShiftRightLogical64(
GetRawSource64(instruction, 1),
shift);
result = _module.AddInstruction(
SpirvOp.UConvert,
_uintType,
shifted);
StoreV(
destination + 1,
_module.AddInstruction(
SpirvOp.UConvert,
_uintType,
ShiftRightLogical64(
shifted,
_module.Constant64(_ulongType, 32))));
break;
}
case "VLshlB32":
result = EmitIntegerBinary(instruction, SpirvOp.ShiftLeftLogical);
break;
@ -1011,6 +1060,18 @@ public static partial class Gen5SpirvTranslator
source));
break;
}
case "VAlignbitB32":
{
var high = GetRawSource(instruction, 0);
var low = GetRawSource(instruction, 1);
var shift = BitwiseAnd(GetRawSource(instruction, 2), UInt(31));
var lowPart = ShiftRightLogical(low, shift);
var inverse = BitwiseAnd(ISubU(UInt(32), shift), UInt(31));
var highPartRaw = ShiftLeftLogical(high, inverse);
var highPart = SelectU(IsNotZero(shift), highPartRaw, UInt(0));
result = BitwiseOr(lowPart, highPart);
break;
}
case "VCvtPkrtzF16F32":
{
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
@ -1791,11 +1852,33 @@ public static partial class Gen5SpirvTranslator
var right = compare64
? GetRawSource64(instruction, 1)
: GetRawSource(instruction, 1);
var signed = opcode.EndsWith("I32", StringComparison.Ordinal);
var signed16 = opcode.EndsWith("I16", StringComparison.Ordinal);
var signed = signed16 ||
opcode.EndsWith("I32", StringComparison.Ordinal) ||
opcode.EndsWith("I64", StringComparison.Ordinal);
if (signed16)
{
left = _module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
Bitcast(_intType, left),
UInt(0),
UInt(16));
right = _module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
Bitcast(_intType, right),
UInt(0),
UInt(16));
}
if (signed)
{
left = Bitcast(_intType, left);
right = Bitcast(_intType, right);
if (!signed16)
{
var signedType = compare64 ? _longType : _intType;
left = Bitcast(signedType, left);
right = Bitcast(signedType, right);
}
}
var operation = opcode switch
@ -1804,7 +1887,14 @@ public static partial class Gen5SpirvTranslator
"VCmpEqU32" or "VCmpxEqU32" => SpirvOp.IEqual,
"VCmpNeI32" or "VCmpxNeI32" or
"VCmpNeU32" or "VCmpxNeU32" => SpirvOp.INotEqual,
"VCmpxNeU64" => SpirvOp.INotEqual,
"VCmpEqI16" or "VCmpxEqI16" => SpirvOp.IEqual,
"VCmpNeI16" or "VCmpxNeI16" => SpirvOp.INotEqual,
"VCmpNeU64" or "VCmpxNeU64" or
"VCmpNeI64" or "VCmpxNeI64" => SpirvOp.INotEqual,
"VCmpLtI16" or "VCmpxLtI16" => SpirvOp.SLessThan,
"VCmpLeI16" or "VCmpxLeI16" => SpirvOp.SLessThanEqual,
"VCmpGtI16" or "VCmpxGtI16" => SpirvOp.SGreaterThan,
"VCmpGeI16" or "VCmpxGeI16" => SpirvOp.SGreaterThanEqual,
"VCmpLtI32" or "VCmpxLtI32" => SpirvOp.SLessThan,
"VCmpLeI32" or "VCmpxLeI32" => SpirvOp.SLessThanEqual,
"VCmpGtI32" or "VCmpxGtI32" => SpirvOp.SGreaterThan,

View File

@ -61,5 +61,41 @@ public static partial class Gen5SpirvTranslator
_module.AddInstruction(SpirvOp.Select, _uintType, sourceActive, value, UInt(0)));
return true;
}
private bool TryEmitDataShareBpermute(Gen5ShaderInstruction instruction,
Gen5DataShareControl control, out string error)
{
error = string.Empty;
if (instruction.Sources.Count != 2 || instruction.Destinations.Count != 1)
{
error = "invalid data-share bpermute operands";
return false;
}
if (_subgroupInvocationIdInput == 0)
{
error = "data-share bpermute requires subgroup lane access";
return false;
}
var lane = Load(_uintType, _subgroupInvocationIdInput);
var address = IAdd(GetRawSource(instruction, 0), UInt(control.SingleOffsetBytes));
var index = BitwiseAnd(ShiftRightLogical(address, UInt(2)), UInt(31));
var sourceLane = BitwiseOr(BitwiseAnd(lane, UInt(~31u)), index);
// DS_BPERMUTE reads only from active lanes in the current 32-lane half-wave.
var ballot = _module.AddInstruction(SpirvOp.GroupNonUniformBallot, _uvec4Type,
UInt(3), Load(_boolType, _exec));
var sourceWord = _module.AddInstruction(SpirvOp.VectorExtractDynamic, _uintType,
ballot, ShiftRightLogical(sourceLane, UInt(5)));
var sourceActive = IsNotZero(BitwiseAnd(ShiftRightLogical(sourceWord,
BitwiseAnd(sourceLane, UInt(31))), UInt(1)));
var safeSourceLane = _module.AddInstruction(SpirvOp.Select, _uintType,
sourceActive, sourceLane, lane);
var value = _module.AddInstruction(SpirvOp.GroupNonUniformShuffle, _uintType,
UInt(3), GetRawSource(instruction, 1), safeSourceLane);
StoreV(instruction.Destinations[0].Value,
_module.AddInstruction(SpirvOp.Select, _uintType, sourceActive, value, UInt(0)));
return true;
}
}
}

View File

@ -741,6 +741,11 @@ public static partial class Gen5SpirvTranslator
{
_deviceAddressInstructionPc = instruction.Pc;
error = string.Empty;
if (instruction.Opcode.StartsWith("Scratch", StringComparison.Ordinal))
{
return TryEmitScratchMemory(instruction, control, out error);
}
var request = _request;
if (!request.Memory.TryGetIndex(instruction.Pc, 0, out var memoryIndex))
{
@ -842,6 +847,141 @@ public static partial class Gen5SpirvTranslator
return true;
}
private bool TryEmitScratchMemory(
Gen5ShaderInstruction instruction,
Gen5GlobalMemoryControl control,
out string error)
{
error = string.Empty;
if (_scratch == 0 || _scratchDwordCount == 0)
{
error = "scratch storage was not declared";
return false;
}
var address = control.ScalarAddress < 125
? LoadS(control.ScalarAddress)
: LoadV(control.VectorAddress);
if (control.OffsetBytes != 0)
{
address = IAdd(address, UInt(unchecked((uint)control.OffsetBytes)));
}
if (instruction.Opcode.StartsWith("ScratchStore", StringComparison.Ordinal))
{
EmitExecConditional(() =>
{
if (TryGetSubdwordStoreInfo(instruction.Opcode, out var byteCount, out var sourceShift))
{
StoreScratchBytes(address, LoadV(control.SourceVectorRegister), byteCount, sourceShift);
return;
}
for (uint index = 0; index < control.DwordCount; index++)
{
Store(
ScratchPointer(address, index * sizeof(uint)),
LoadV(control.SourceVectorRegister + index));
}
});
return true;
}
if (!instruction.Opcode.StartsWith("ScratchLoad", StringComparison.Ordinal))
{
error = $"unsupported scratch opcode {instruction.Opcode}";
return false;
}
EmitExecConditional(() =>
{
if (TryGetSubdwordLoadInfo(instruction.Opcode, out var byteCount, out var signExtend, out var d16, out var d16High))
{
var destination = control.DestinationVectorRegister;
var value = LoadScratchBytes(address, byteCount, signExtend);
if (d16)
{
value = d16High
? BitwiseOr(BitwiseAnd(LoadV(destination), UInt(0x0000_FFFF)), ShiftLeftLogical(BitwiseAnd(value, UInt(0xFFFF)), UInt(16)))
: BitwiseOr(BitwiseAnd(LoadV(destination), UInt(0xFFFF_0000)), BitwiseAnd(value, UInt(0xFFFF)));
}
StoreV(destination, value);
return;
}
for (uint index = 0; index < control.DwordCount; index++)
{
StoreV(
control.DestinationVectorRegister + index,
Load(_uintType, ScratchPointer(address, index * sizeof(uint))));
}
});
return true;
}
private uint ScratchPointer(uint byteAddress, uint offsetBytes = 0)
{
var address = offsetBytes == 0
? byteAddress
: IAdd(byteAddress, UInt(offsetBytes));
var dwordIndex = ShiftRightLogical(address, UInt(2));
if (_scratchDwordCount > 1)
{
dwordIndex = _module.AddInstruction(
SpirvOp.UMod,
_uintType,
dwordIndex,
UInt(_scratchDwordCount));
}
return _module.AddInstruction(
SpirvOp.AccessChain,
_scratchElementPointer,
_scratch,
dwordIndex);
}
private uint LoadScratchBytes(uint byteAddress, uint byteCount, bool signExtend)
{
var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3));
var value = BitwiseAnd(
ShiftRightLogical(Load(_uintType, ScratchPointer(byteAddress)), shift),
UInt(byteCount == 1 ? 0xFFu : 0xFFFFu));
if (signExtend)
{
value = Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
Bitcast(_intType, value),
UInt(0),
UInt(byteCount * 8)));
}
return value;
}
private void StoreScratchBytes(uint byteAddress, uint value, uint byteCount, uint sourceShift)
{
if (sourceShift != 0)
{
value = ShiftRightLogical(value, UInt(sourceShift));
}
var pointer = ScratchPointer(byteAddress);
var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3));
var elementMask = UInt(byteCount == 1 ? 0xFFu : 0xFFFFu);
var shiftedMask = ShiftLeftLogical(elementMask, shift);
var merged = BitwiseOr(
BitwiseAnd(
Load(_uintType, pointer),
_module.AddInstruction(SpirvOp.Not, _uintType, shiftedMask)),
ShiftLeftLogical(BitwiseAnd(value, elementMask), shift));
Store(pointer, merged);
}
// ---- buffers ----
private bool TryResolveLayoutBuffer(uint pc, out int bindingIndex, out BufferResource resource)

View File

@ -123,6 +123,9 @@ public static partial class Gen5SpirvTranslator
private uint _lds;
private uint _ldsElementPointer;
private uint _ldsDwordMask;
private uint _scratch;
private uint _scratchElementPointer;
private uint _scratchDwordCount;
private uint _positionOutput;
private uint _pointSizeOutput;
private uint _clipDistanceOutput;
@ -570,6 +573,7 @@ public static partial class Gen5SpirvTranslator
{
DeclareLayoutBindings();
DeclareScratch();
DeclareLds();
DeclareWave64Scratch();
DeclareStageInterface();
@ -577,6 +581,26 @@ public static partial class Gen5SpirvTranslator
}
}
private void DeclareScratch()
{
if (!_request.Program.Instructions.Any(static instruction =>
instruction.Opcode.StartsWith("Scratch", StringComparison.Ordinal)))
{
return;
}
_scratchDwordCount = Math.Max(_request.ScratchDwords, 1u);
var arrayType = _module.TypeArray(_uintType, _scratchDwordCount);
var arrayPointer = _module.TypePointer(SpirvStorageClass.Private, arrayType);
_scratchElementPointer = _module.TypePointer(SpirvStorageClass.Private, _uintType);
_scratch = _module.AddGlobalVariable(
arrayPointer,
SpirvStorageClass.Private,
_module.ConstantNull(arrayType));
_interfaces.Add(_scratch);
_module.AddName(_scratch, "scratch");
}
private void DeclareWave64Scratch()
{
if (!_emulateWave64 || !UsesSubgroupOperations())
@ -1399,6 +1423,7 @@ public static partial class Gen5SpirvTranslator
}
if (instruction.Opcode is
"SNop" or
"SSetregB32" or
"SWaitcnt" or
"SInstPrefetch" or
"STtraceData" or
@ -1490,9 +1515,7 @@ public static partial class Gen5SpirvTranslator
return TryEmitGlobalDataShare(instruction, globalShare, out error);
}
if (_lds == 0 ||
_ldsElementPointer == 0 ||
instruction.Control is not Gen5DataShareControl control)
if (instruction.Control is not Gen5DataShareControl control)
{
error = "invalid LDS instruction";
return false;
@ -1502,6 +1525,18 @@ public static partial class Gen5SpirvTranslator
{
case "DsSwizzleB32":
return TryEmitDataShareSwizzle(instruction, control, out error);
case "DsBpermuteB32":
return TryEmitDataShareBpermute(instruction, control, out error);
}
if (_lds == 0 || _ldsElementPointer == 0)
{
error = "invalid LDS instruction";
return false;
}
switch (instruction.Opcode)
{
case "DsAppend":
case "DsConsume":
return TryEmitDataShareWaveCounter(instruction, control, out error);
@ -1957,7 +1992,7 @@ public static partial class Gen5SpirvTranslator
_uintType,
pointer,
UInt(scope),
UInt(semantics));
UInt((semantics & ~0xCu) | 0x2u));
_module.AddStatement(SpirvOp.Branch, header);
_module.AddLabel(header);
@ -2015,7 +2050,7 @@ public static partial class Gen5SpirvTranslator
_uintType,
pointer,
UInt(scope),
UInt(semantics));
UInt((semantics & ~0xCu) | 0x2u));
_module.AddStatement(SpirvOp.Branch, header);
_module.AddLabel(header);
@ -2229,8 +2264,11 @@ public static partial class Gen5SpirvTranslator
if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal))
{
var atomicSuffix = instruction.Opcode["BufferAtomic".Length..];
if (atomicSuffix == "OrX2")
if (atomicSuffix is "SwapX2" or "OrX2")
{
var wideAtomicOp = atomicSuffix == "SwapX2"
? SpirvOp.AtomicExchange
: SpirvOp.AtomicOr;
EmitExecConditional(() =>
{
var secondAddress = IAdd(dwordAddress, UInt(1));
@ -2242,7 +2280,7 @@ public static partial class Gen5SpirvTranslator
EmitConditional(inRange, () =>
{
var originalLow = EmitAtomic(
SpirvOp.AtomicOr,
wideAtomicOp,
_uintType,
BufferWordPointer(bindingIndex, dwordAddress),
scope: 1,
@ -2250,7 +2288,7 @@ public static partial class Gen5SpirvTranslator
value: () => LoadV(control.VectorData),
comparator: () => UInt(0));
var originalHigh = EmitAtomic(
SpirvOp.AtomicOr,
wideAtomicOp,
_uintType,
BufferWordPointer(bindingIndex, secondAddress),
scope: 1,
@ -3456,6 +3494,21 @@ public static partial class Gen5SpirvTranslator
out string error)
{
error = string.Empty;
if (instruction.Opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay")
{
// The host path does not expose Vulkan ray-query or an
// acceleration-structure descriptor for GFX10's raw BVH
// texture. Return a deterministic miss instead of rejecting
// the complete compute shader. The instruction always writes
// four DWORD result registers.
for (uint component = 0; component < 4; component++)
{
StoreV(image.VectorData + component, UInt(0));
}
return true;
}
SpirvImageResource resource;
uint imageObject;
uint dstSelect;

View File

@ -776,6 +776,7 @@ public static partial class Gen5ShaderTranslator
0x0E => "SCmpkLeU32",
0x0F => "SAddkI32",
0x10 => "SMulkI32",
0x13 => "SSetregB32",
// RDNA2 uses four SOPK forms to wait for one counter.
// The selected counter does not change the translated operation.
0x17 or 0x18 or 0x19 or 0x1A => "SWaitcnt",
@ -824,6 +825,7 @@ public static partial class Gen5ShaderTranslator
0x36 => "VCosF32",
0x37 => "VNotB32",
0x38 => "VBfrevB32",
0x39 => "VFfbhU32",
0x3A => "VFfblB32",
0x42 => "VMovreldB32",
0x43 => "VMovrelsB32",
@ -962,6 +964,13 @@ public static partial class Gen5ShaderTranslator
0x86 => "VCmpGeI32",
0x87 => "VCmpTI32",
0x88 => "VCmpClassF32",
0x89 => "VCmpLtI16",
0x8A => "VCmpEqI16",
0x8B => "VCmpLeI16",
0x8C => "VCmpGtI16",
0x8D => "VCmpNeI16",
0x8E => "VCmpGeI16",
0x98 => "VCmpxClassF32",
0x90 => "VCmpxFI32",
0x91 => "VCmpxLtI32",
0x92 => "VCmpxEqI32",
@ -970,6 +979,12 @@ public static partial class Gen5ShaderTranslator
0x95 => "VCmpxNeI32",
0x96 => "VCmpxGeI32",
0x97 => "VCmpxTI32",
0x99 => "VCmpxLtI16",
0x9A => "VCmpxEqI16",
0x9B => "VCmpxLeI16",
0x9C => "VCmpxGtI16",
0x9D => "VCmpxNeI16",
0x9E => "VCmpxGeI16",
0xC0 => "VCmpFU32",
0xC1 => "VCmpLtU32",
0xC2 => "VCmpEqU32",
@ -1002,6 +1017,7 @@ public static partial class Gen5ShaderTranslator
0xDD => "VCmpxLgF16",
0xDE => "VCmpxGeF16",
0xDF => "VCmpxOF16",
0xE5 => "VCmpNeU64",
0xE8 => "VCmpUF16",
0xE9 => "VCmpNgeF16",
0xEA => "VCmpNlgF16",
@ -1011,6 +1027,7 @@ public static partial class Gen5ShaderTranslator
0xEE => "VCmpNltF16",
0xEF => "VCmpTruF16",
0xF5 => "VCmpxNeU64",
0xB5 => "VCmpxNeI64",
0xF8 => "VCmpxUF16",
0xF9 => "VCmpxNgeF16",
0xFA => "VCmpxNlgF16",
@ -1051,6 +1068,7 @@ public static partial class Gen5ShaderTranslator
}
: opcode switch
{
0x0B5 => "VCmpxNeI64",
0x101 => "VCndmaskB32",
0x103 => "VAddF32",
0x104 => "VSubF32",
@ -1068,6 +1086,7 @@ public static partial class Gen5ShaderTranslator
0x146 => "VCubetcF32",
0x147 => "VCubemaF32",
0x14A => "VBfiB32",
0x14E => "VAlignbitB32",
0x14B => "VFmaF32",
0x151 => "VMin3F32",
0x152 => "VMin3I32",
@ -1109,6 +1128,7 @@ public static partial class Gen5ShaderTranslator
0x36D => "VAdd3U32",
0x36F => "VLshlOrU32",
0x178 => "VXor3B32",
0x300 => "VLshrrevB64",
0x371 => "VAndOrB32",
0x372 => "VOr3U32",
0x377 => "VPermlane16B32",
@ -1226,6 +1246,7 @@ public static partial class Gen5ShaderTranslator
0x77 => "DsRead2B64",
0xB0 => "DsWriteAddtidB32",
0xB1 => "DsReadAddtidB32",
0xB3 => "DsBpermuteB32",
0xDE => "DsWriteB96",
0xDF => "DsWriteB128",
0xFE => "DsReadB96",
@ -1345,6 +1366,7 @@ public static partial class Gen5ShaderTranslator
0x3D => "BufferAtomicDec",
0x3F => "BufferAtomicFmin",
0x40 => "BufferAtomicFmax",
0x50 => "BufferAtomicSwapX2",
0x5A => "BufferAtomicOrX2",
_ => $"MubufRaw{opcode:X2}",
};
@ -1366,6 +1388,7 @@ public static partial class Gen5ShaderTranslator
var prefix = segment switch
{
0x0 => "Flat",
0x1 => "Scratch",
0x2 => "Global",
_ => string.Empty,
};
@ -1457,7 +1480,9 @@ public static partial class Gen5ShaderTranslator
private static bool DecodeMimg(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 18) & 0x7F;
// RDNA2 MIMG OP[7] is bit 0 while OP[6:0] occupies bits 24:18.
// Ignoring bit 0 aliases 0xE6/0xE7 BVH operations to 0x66/0x67.
var opcode = ((word >> 18) & 0x7F) | ((word & 1) << 7);
sizeDwords = 2 + ((word >> 1) & 0x3);
error = string.Empty;
name = opcode switch
@ -1545,6 +1570,8 @@ public static partial class Gen5ShaderTranslator
0xB9 => "ImageSampleCClAO",
0xBD => "ImageSampleCBAO",
0xBE => "ImageSampleCBClAO",
0xE6 => "ImageBvhIntersectRay",
0xE7 => "ImageBvh64IntersectRay",
_ => string.Empty,
};
@ -1833,6 +1860,14 @@ public static partial class Gen5ShaderTranslator
sources = [.. sources, Gen5Operand.Scalar(scalarSource)];
}
}
else if (opcode == "SSetregB32")
{
sources =
[
Gen5Operand.Scalar((word >> 16) & 0x7F),
new Gen5Operand(Gen5OperandKind.EncodedConstant, word & 0xFFFF),
];
}
else
{
destinations = [Gen5Operand.Scalar((word >> 16) & 0x7F)];
@ -2160,6 +2195,10 @@ public static partial class Gen5ShaderTranslator
Gen5Operand.Vector(vectorData1),
],
"DsSwizzleB32" => [Gen5Operand.Vector(vectorData0)],
"DsBpermuteB32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
],
// DS_CMPST operand order is reversed vs buffer/image cmpswap:
// DATA0 holds the comparator, DATA1 holds the new value.
"DsCmpstB32" or "DsCmpstRtnB32" => [
@ -2185,7 +2224,8 @@ public static partial class Gen5ShaderTranslator
"DsAppend" or "DsConsume" => [
Gen5Operand.Vector(vectorDestination),
],
"DsReadB32" or "DsReadI8" or "DsReadAddtidB32" or "DsSwizzleB32" => [
"DsReadB32" or "DsReadI8" or "DsReadAddtidB32" or
"DsSwizzleB32" or "DsBpermuteB32" => [
Gen5Operand.Vector(vectorDestination),
],
"DsReadB64" or "DsRead2B32" or "DsRead2St64B32" => [
@ -2228,9 +2268,14 @@ public static partial class Gen5ShaderTranslator
var usesFlatAddress = opcode.StartsWith(
"Flat",
StringComparison.Ordinal);
var usesScratchAddress = opcode.StartsWith(
"Scratch",
StringComparison.Ordinal);
var memoryOpcode = usesFlatAddress
? "Global" + opcode["Flat".Length..]
: opcode;
: usesScratchAddress
? "Global" + opcode["Scratch".Length..]
: opcode;
var dwordCount = memoryOpcode switch
{
"GlobalLoadUbyte" or
@ -2265,6 +2310,10 @@ public static partial class Gen5ShaderTranslator
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorAddress + 1),
]
: usesScratchAddress && scalarAddress < 125
? [Gen5Operand.Scalar(scalarAddress)]
: usesScratchAddress
? [Gen5Operand.Vector(vectorAddress)]
:
[
Gen5Operand.Vector(vectorAddress),
@ -2343,7 +2392,7 @@ public static partial class Gen5ShaderTranslator
"BufferStoreDwordx2" => 2u,
"BufferStoreDwordx3" => 3u,
"BufferStoreDwordx4" => 4u,
"BufferAtomicCmpswap" or "BufferAtomicOrX2" => 2u,
"BufferAtomicCmpswap" or "BufferAtomicSwapX2" or "BufferAtomicOrX2" => 2u,
_ when opcode.StartsWith("BufferAtomic", StringComparison.Ordinal) => 1u,
_ => 0u,
};
@ -2442,7 +2491,11 @@ public static partial class Gen5ShaderTranslator
sources = imageSources;
destinations = opcode.StartsWith("ImageStore", StringComparison.Ordinal)
? []
: [Gen5Operand.Vector(vectorData)];
: opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay"
? Enumerable.Range((int)vectorData, 4)
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray()
: [Gen5Operand.Vector(vectorData)];
var dimension = (word >> 3) & 0x7;
control = new Gen5ImageControl(
(word >> 8) & 0xF,

View File

@ -158,6 +158,10 @@ public sealed class MemoryAccessTable
table.Add(FromImage(instruction, image));
break;
case Gen5DataShareControl share:
if (instruction.Opcode is "DsSwizzleB32" or "DsBpermuteB32")
{
break;
}
table.Add(new MemoryAccessInfo
{
Pc = instruction.Pc,
@ -241,7 +245,11 @@ public sealed class MemoryAccessTable
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = control.UsesFlatAddress ? MemoryResourceKind.Flat : MemoryResourceKind.Global,
Kind = opcode.StartsWith("Scratch", StringComparison.Ordinal)
? MemoryResourceKind.Scratch
: control.UsesFlatAddress
? MemoryResourceKind.Flat
: MemoryResourceKind.Global,
Access = access,
Offset = unchecked((uint)control.OffsetBytes),
DataDwords = Math.Max(control.DwordCount, 1u),
@ -257,6 +265,22 @@ public sealed class MemoryAccessTable
private static MemoryAccessInfo FromImage(Gen5ShaderInstruction instruction, Gen5ImageControl control)
{
var opcode = instruction.Opcode;
if (opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay")
{
// The current backends return a deterministic miss for raw GFX10
// BVH traversal. Do not materialize its descriptor as a texture;
// BVH descriptors do not use the regular image descriptor layout.
return new MemoryAccessInfo
{
Pc = instruction.Pc,
Opcode = opcode,
Kind = MemoryResourceKind.None,
Access = MemoryAccess.None,
DataDwords = 4,
ComponentCount = 4,
};
}
var atomic = opcode.StartsWith("ImageAtomic", StringComparison.Ordinal);
var store = opcode.StartsWith("ImageStore", StringComparison.Ordinal);
var sampled = opcode.StartsWith("ImageSample", StringComparison.Ordinal) ||

View File

@ -117,6 +117,7 @@ public static class ResourceMaterializer
evaluateTable: true, out var values, out var table, out var activeSources,
additionalTableWords: checked(plan.WrittenRangeCount * ShaderResourcePlan.WrittenRangeDwordCount)))
{
SpecializationFailed(DiagnoseSnapshotEvaluationFailure(plan, inputs, activeSources));
return false;
}
@ -214,6 +215,126 @@ public static class ResourceMaterializer
return true;
}
private static string DiagnoseSnapshotEvaluationFailure(
ShaderResourcePlan plan,
ResourceRuntimeInputs inputs,
IReadOnlyList<bool> activeSources)
{
var cleanEvaluator = new RuntimeValueEvaluator(
plan,
inputs.WithReader(inputs.ReadCleanMemory));
var evaluator = new RuntimeValueEvaluator(
plan,
inputs,
plan.CleanFlatSlots,
cleanEvaluator);
foreach (var sourceIndex in plan.MaterializationSources)
{
if (sourceIndex >= plan.DescriptorSources.Count)
{
return $"descriptor source {sourceIndex} is outside the source table";
}
if (activeSources.Count != 0 && !activeSources[(int)sourceIndex])
{
continue;
}
var source = plan.DescriptorSources[(int)sourceIndex];
for (var dword = 0; dword < source.Dwords.Length; dword++)
{
if (!evaluator.Evaluate(source.Dwords[dword], out _))
{
return $"descriptor source {sourceIndex} dword {dword} cannot be evaluated: " +
DescribeEvaluationValue(plan, inputs, evaluator, source.Dwords[dword]);
}
}
}
foreach (var read in plan.TableReads)
{
var clean = read.FlatOffset < plan.CleanFlatSlots.Count &&
plan.CleanFlatSlots[(int)read.FlatOffset] != 0;
var selected = clean ? cleanEvaluator : evaluator;
if (read.FlatOffset >= plan.TableReads.Count || !selected.Evaluate(read.Value, out _))
{
return $"resource table read {read.FlatOffset} cannot be evaluated: " +
DescribeEvaluationValue(plan, inputs, selected, read.Value);
}
}
return "descriptor snapshot evaluation failed without an isolated source";
}
private static string DescribeEvaluationValue(
ShaderResourcePlan plan,
ResourceRuntimeInputs inputs,
RuntimeValueEvaluator evaluator,
ScalarValue value,
int depth = 0)
{
if (depth >= 6)
{
return value.ToString();
}
if (value.Kind == ScalarValueKind.ResourceTableWord)
{
var slot = (int)value.Payload;
if ((uint)slot < plan.TableReads.Count)
{
var read = plan.TableReads[slot];
return $"table-slot={slot} flat={read.FlatOffset} value=" +
DescribeEvaluationValue(plan, inputs, evaluator, read.Value, depth + 1);
}
return $"table-slot={slot} (outside table)";
}
if (value.Kind is ScalarValueKind.ScalarAddressWord or ScalarValueKind.ScalarBufferWord &&
value.MemoryIndex >= 0 && value.MemoryIndex < plan.Memory.Count)
{
var memory = plan.Memory[value.MemoryIndex];
var detail = $"{value.Kind} memory={value.MemoryIndex} pc=0x{memory.Pc:X8} " +
$"opcode={memory.Opcode} offset={memory.Offset} operands=[{string.Join(", ", value.Operands.Select(operand =>
DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]";
if (value.Operands.Length >= 2 && value.Operands[0].Operands.Length >= 2 &&
evaluator.EvaluateWide(value.Operands[0].Operands[0], out var low) &&
evaluator.EvaluateWide(value.Operands[0].Operands[1], out var high) &&
evaluator.EvaluateWide(value.Operands[1], out var dynamicOffset))
{
var baseAddress = ((high << 32) | (uint)low) & AddressMask;
var relative = (long)(int)memory.Offset + (uint)dynamicOffset;
if (relative >= 0 && baseAddress <= AddressMask - (ulong)relative)
{
var address = (baseAddress + (ulong)relative) & ~3ul;
var regular = inputs.ReadMemory is not null && inputs.ReadMemory(address, out _);
var clean = inputs.ReadCleanMemory is not null && inputs.ReadCleanMemory(address, out _);
detail += $" base=0x{baseAddress:X16} dynamic=0x{dynamicOffset:X} " +
$"address=0x{address:X16} readable={regular} clean={clean}";
}
}
return detail;
}
if (value.Kind is ScalarValueKind.AddressHandle or ScalarValueKind.BufferHandle or
ScalarValueKind.ImageHandle or ScalarValueKind.SamplerHandle)
{
return $"{value.Kind}[{string.Join(", ", value.Operands.Select(operand =>
DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]";
}
if (value.Kind is ScalarValueKind.Phi or ScalarValueKind.Select or ScalarValueKind.Operation)
{
return $"{value} operands=[{string.Join(", ", value.Operands.Select(operand =>
DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]";
}
return value.ToString();
}
private static bool NullImageDescriptor(ReadOnlySpan<uint> descriptor) =>
descriptor[0] == 0 && (descriptor[1] & 0xFF) == 0;

View File

@ -485,7 +485,9 @@ public sealed partial class ResourceTracker
{
if (memory.Kind == MemoryResourceKind.Scratch)
{
throw Failure(memory.Pc, "scratch operations are not supported");
// Scratch is invocation-private shader storage. It has no host
// resource handle or descriptor-table binding to materialize.
return;
}
if (access.Handle is null || access.Handle.Kind != ScalarValueKind.AddressHandle)

View File

@ -73,6 +73,19 @@ public sealed class Gen5ShaderAtomicDecodeTests
instruction.Destinations);
}
[Fact]
public void BufferAtomicSwapX2_UsesTwoDataRegisters()
{
// BUFFER_ATOMIC_SWAP_X2 v[1:2], off, s[0:3], 128 glc
var instruction = DecodeSingle(0xE1404000, 0x80000100);
Assert.Equal("BufferAtomicSwapX2", instruction.Opcode);
var control = Assert.IsType<Gen5BufferMemoryControl>(instruction.Control);
Assert.Equal(2u, control.DwordCount);
Assert.Equal(1u, control.VectorData);
Assert.Equal(new[] { Gen5Operand.Vector(1), Gen5Operand.Vector(2) }, instruction.Destinations);
}
[Fact]
public void ImageAtomicAdd_KeepsDataRegisterAsDestination()
{
@ -199,6 +212,48 @@ public sealed class Gen5ShaderAtomicDecodeTests
Assert.Empty(instruction.Destinations);
}
[Fact]
public void VCmpNeU64_DecodesVectorRegisterPairs()
{
// V_CMP_NE_U64 v[0:1], v[3:4].
var instruction = DecodeSingle(0x7DCA0700);
Assert.Equal("VCmpNeU64", instruction.Opcode);
Assert.Equal(
new[] { Gen5Operand.Vector(0), Gen5Operand.Vector(3) },
instruction.Sources);
Assert.Empty(instruction.Destinations);
}
[Fact]
public void VFfbhU32_DecodesVop1Opcode39()
{
// V_FFBH_U32 v1, v2.
var instruction = DecodeSingle(0x7E027302);
Assert.Equal("VFfbhU32", instruction.Opcode);
Assert.Equal(new[] { Gen5Operand.Vector(2) }, instruction.Sources);
Assert.Equal(new[] { Gen5Operand.Vector(1) }, instruction.Destinations);
}
[Fact]
public void VCmpxClassF32_DecodesVopcOpcode98()
{
var instruction = DecodeSingle(0x7D300702);
Assert.Equal("VCmpxClassF32", instruction.Opcode);
}
[Fact]
public void VAlignbitB32_DecodesVop3Opcode14E()
{
var instruction = DecodeSingle(0xD14E0001, 0x040E0502);
Assert.Equal("VAlignbitB32", instruction.Opcode);
Assert.Equal(new[] { Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Vector(3) }, instruction.Sources);
Assert.Equal(new[] { Gen5Operand.Vector(1) }, instruction.Destinations);
}
private static Gen5ShaderInstruction DecodeSingle(params uint[] words)
{
var memory = new FakeCpuMemory(ShaderAddress, 0x1000);

View File

@ -59,6 +59,16 @@ public sealed class Gen5SpirvAtomicTranslationTests
Assert.Contains((ushort)SpirvOp.AtomicOr, opcodes);
}
[Fact]
public void BufferAtomicSwapX2_EmitsAtomicExchange()
{
var opcodes = CompileComputeOpcodes(
[0xE1404000, 0x80000100],
BufferDescriptorRegisters());
Assert.Contains((ushort)SpirvOp.AtomicExchange, opcodes);
}
[Fact]
public void DataShareAtomics_EmitAtomicOpcodes()
{

View File

@ -106,6 +106,35 @@ public sealed class Gen5ShaderTranslatorTests
Assert.Equal(Gen5OperandKind.EncodedConstant, instruction.Sources[0].Kind);
}
[Fact]
public void SopkSetregB32DecodesScalarSourceWithoutDestination()
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
Span<byte> code = stackalloc byte[2 * sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(code, 0xB9851234u);
BinaryPrimitives.WriteUInt32LittleEndian(code[sizeof(uint)..], 0xBF810000u);
Assert.True(memory.TryWrite(ProgramAddress, code));
var context = new CpuContext(memory, Generation.Gen5);
Assert.True(
Gen5ShaderTranslator.TryDecodeProgram(
context,
ProgramAddress,
out var program,
out var error),
error);
var instruction = program.Instructions[0];
Assert.Equal(Gen5ShaderEncoding.Sopk, instruction.Encoding);
Assert.Equal("SSetregB32", instruction.Opcode);
Assert.Empty(instruction.Destinations);
Assert.Equal(2, instruction.Sources.Count);
Assert.Equal(Gen5OperandKind.ScalarRegister, instruction.Sources[0].Kind);
Assert.Equal(5u, instruction.Sources[0].Value);
Assert.Equal(Gen5OperandKind.EncodedConstant, instruction.Sources[1].Kind);
Assert.Equal(0x1234u, instruction.Sources[1].Value);
}
[Fact]
public void FusedProgramContinuesAfterSetProgramCounter()
{