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))", "VLshlrevB32" => $"(({RawSource(instruction, 1)}) << (({RawSource(instruction, 0)}) & 31u))",
"VLshrB32" => $"(({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))", "VLshrB32" => $"(({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))",
"VLshrrevB32" => $"(({RawSource(instruction, 1)}) >> (({RawSource(instruction, 0)}) & 31u))", "VLshrrevB32" => $"(({RawSource(instruction, 1)}) >> (({RawSource(instruction, 0)}) & 31u))",
"VLshrrevB64" => EmitLshrrevB64(instruction, destination),
"VAshrI32" => "VAshrI32" =>
AsUInt($"(as_type<int>({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))"), AsUInt($"(as_type<int>({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))"),
"VAshrrevI32" => "VAshrrevI32" =>
@ -471,6 +472,14 @@ public static partial class Gen5MslTranslator
return $"({AsUInt(signedLeft)} * {AsUInt(signedRight)})"; 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) private string EmitCvtPkU8F32(Gen5ShaderInstruction instruction)
{ {
var converted = Temp("uint", $"(uint)({F(instruction, 0)})"); var converted = Temp("uint", $"(uint)({F(instruction, 0)})");
@ -732,7 +741,12 @@ public static partial class Gen5MslTranslator
} }
else 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 var op = TrimCompare(opcode) switch
{ {
"Eq" => "==", "Eq" => "==",
@ -749,9 +763,15 @@ public static partial class Gen5MslTranslator
return false; return false;
} }
var left = wide ? RawSource64(instruction, 0) : RawSource(instruction, 0);
var right = wide ? RawSource64(instruction, 1) : RawSource(instruction, 1);
condition = signed condition = signed
? $"(as_type<int>({RawSource(instruction, 0)}) {op} as_type<int>({RawSource(instruction, 1)}))" ? signed16
: $"(({RawSource(instruction, 0)}) {op} ({RawSource(instruction, 1)}))"; ? $"(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 // 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"); StoreVector(instruction.Destinations[0].Value, $"{sourceActive} ? {value} : 0u");
return true; 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) out string error)
{ {
error = string.Empty; 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 texture;
string samplerName; string samplerName;
string kind; string kind;

View File

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

View File

@ -453,6 +453,30 @@ public static partial class Gen5SpirvTranslator
_uintType, _uintType,
GetRawSource(instruction, 0)); GetRawSource(instruction, 0));
break; 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": case "VFfblB32":
result = Bitcast( result = Bitcast(
_uintType, _uintType,
@ -680,6 +704,31 @@ public static partial class Gen5SpirvTranslator
SpirvOp.ShiftRightLogical, SpirvOp.ShiftRightLogical,
reverse: true); reverse: true);
break; 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": case "VLshlB32":
result = EmitIntegerBinary(instruction, SpirvOp.ShiftLeftLogical); result = EmitIntegerBinary(instruction, SpirvOp.ShiftLeftLogical);
break; break;
@ -1011,6 +1060,18 @@ public static partial class Gen5SpirvTranslator
source)); source));
break; 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": case "VCvtPkrtzF16F32":
{ {
var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0)); var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0));
@ -1791,11 +1852,33 @@ public static partial class Gen5SpirvTranslator
var right = compare64 var right = compare64
? GetRawSource64(instruction, 1) ? GetRawSource64(instruction, 1)
: GetRawSource(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) if (signed)
{ {
left = Bitcast(_intType, left); if (!signed16)
right = Bitcast(_intType, right); {
var signedType = compare64 ? _longType : _intType;
left = Bitcast(signedType, left);
right = Bitcast(signedType, right);
}
} }
var operation = opcode switch var operation = opcode switch
@ -1804,7 +1887,14 @@ public static partial class Gen5SpirvTranslator
"VCmpEqU32" or "VCmpxEqU32" => SpirvOp.IEqual, "VCmpEqU32" or "VCmpxEqU32" => SpirvOp.IEqual,
"VCmpNeI32" or "VCmpxNeI32" or "VCmpNeI32" or "VCmpxNeI32" or
"VCmpNeU32" or "VCmpxNeU32" => SpirvOp.INotEqual, "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, "VCmpLtI32" or "VCmpxLtI32" => SpirvOp.SLessThan,
"VCmpLeI32" or "VCmpxLeI32" => SpirvOp.SLessThanEqual, "VCmpLeI32" or "VCmpxLeI32" => SpirvOp.SLessThanEqual,
"VCmpGtI32" or "VCmpxGtI32" => SpirvOp.SGreaterThan, "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))); _module.AddInstruction(SpirvOp.Select, _uintType, sourceActive, value, UInt(0)));
return true; 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; _deviceAddressInstructionPc = instruction.Pc;
error = string.Empty; error = string.Empty;
if (instruction.Opcode.StartsWith("Scratch", StringComparison.Ordinal))
{
return TryEmitScratchMemory(instruction, control, out error);
}
var request = _request; var request = _request;
if (!request.Memory.TryGetIndex(instruction.Pc, 0, out var memoryIndex)) if (!request.Memory.TryGetIndex(instruction.Pc, 0, out var memoryIndex))
{ {
@ -842,6 +847,141 @@ public static partial class Gen5SpirvTranslator
return true; 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 ---- // ---- buffers ----
private bool TryResolveLayoutBuffer(uint pc, out int bindingIndex, out BufferResource resource) 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 _lds;
private uint _ldsElementPointer; private uint _ldsElementPointer;
private uint _ldsDwordMask; private uint _ldsDwordMask;
private uint _scratch;
private uint _scratchElementPointer;
private uint _scratchDwordCount;
private uint _positionOutput; private uint _positionOutput;
private uint _pointSizeOutput; private uint _pointSizeOutput;
private uint _clipDistanceOutput; private uint _clipDistanceOutput;
@ -570,6 +573,7 @@ public static partial class Gen5SpirvTranslator
{ {
DeclareLayoutBindings(); DeclareLayoutBindings();
DeclareScratch();
DeclareLds(); DeclareLds();
DeclareWave64Scratch(); DeclareWave64Scratch();
DeclareStageInterface(); 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() private void DeclareWave64Scratch()
{ {
if (!_emulateWave64 || !UsesSubgroupOperations()) if (!_emulateWave64 || !UsesSubgroupOperations())
@ -1399,6 +1423,7 @@ public static partial class Gen5SpirvTranslator
} }
if (instruction.Opcode is if (instruction.Opcode is
"SNop" or "SNop" or
"SSetregB32" or
"SWaitcnt" or "SWaitcnt" or
"SInstPrefetch" or "SInstPrefetch" or
"STtraceData" or "STtraceData" or
@ -1490,9 +1515,7 @@ public static partial class Gen5SpirvTranslator
return TryEmitGlobalDataShare(instruction, globalShare, out error); return TryEmitGlobalDataShare(instruction, globalShare, out error);
} }
if (_lds == 0 || if (instruction.Control is not Gen5DataShareControl control)
_ldsElementPointer == 0 ||
instruction.Control is not Gen5DataShareControl control)
{ {
error = "invalid LDS instruction"; error = "invalid LDS instruction";
return false; return false;
@ -1502,6 +1525,18 @@ public static partial class Gen5SpirvTranslator
{ {
case "DsSwizzleB32": case "DsSwizzleB32":
return TryEmitDataShareSwizzle(instruction, control, out error); 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 "DsAppend":
case "DsConsume": case "DsConsume":
return TryEmitDataShareWaveCounter(instruction, control, out error); return TryEmitDataShareWaveCounter(instruction, control, out error);
@ -1957,7 +1992,7 @@ public static partial class Gen5SpirvTranslator
_uintType, _uintType,
pointer, pointer,
UInt(scope), UInt(scope),
UInt(semantics)); UInt((semantics & ~0xCu) | 0x2u));
_module.AddStatement(SpirvOp.Branch, header); _module.AddStatement(SpirvOp.Branch, header);
_module.AddLabel(header); _module.AddLabel(header);
@ -2015,7 +2050,7 @@ public static partial class Gen5SpirvTranslator
_uintType, _uintType,
pointer, pointer,
UInt(scope), UInt(scope),
UInt(semantics)); UInt((semantics & ~0xCu) | 0x2u));
_module.AddStatement(SpirvOp.Branch, header); _module.AddStatement(SpirvOp.Branch, header);
_module.AddLabel(header); _module.AddLabel(header);
@ -2229,8 +2264,11 @@ public static partial class Gen5SpirvTranslator
if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal)) if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal))
{ {
var atomicSuffix = instruction.Opcode["BufferAtomic".Length..]; var atomicSuffix = instruction.Opcode["BufferAtomic".Length..];
if (atomicSuffix == "OrX2") if (atomicSuffix is "SwapX2" or "OrX2")
{ {
var wideAtomicOp = atomicSuffix == "SwapX2"
? SpirvOp.AtomicExchange
: SpirvOp.AtomicOr;
EmitExecConditional(() => EmitExecConditional(() =>
{ {
var secondAddress = IAdd(dwordAddress, UInt(1)); var secondAddress = IAdd(dwordAddress, UInt(1));
@ -2242,7 +2280,7 @@ public static partial class Gen5SpirvTranslator
EmitConditional(inRange, () => EmitConditional(inRange, () =>
{ {
var originalLow = EmitAtomic( var originalLow = EmitAtomic(
SpirvOp.AtomicOr, wideAtomicOp,
_uintType, _uintType,
BufferWordPointer(bindingIndex, dwordAddress), BufferWordPointer(bindingIndex, dwordAddress),
scope: 1, scope: 1,
@ -2250,7 +2288,7 @@ public static partial class Gen5SpirvTranslator
value: () => LoadV(control.VectorData), value: () => LoadV(control.VectorData),
comparator: () => UInt(0)); comparator: () => UInt(0));
var originalHigh = EmitAtomic( var originalHigh = EmitAtomic(
SpirvOp.AtomicOr, wideAtomicOp,
_uintType, _uintType,
BufferWordPointer(bindingIndex, secondAddress), BufferWordPointer(bindingIndex, secondAddress),
scope: 1, scope: 1,
@ -3456,6 +3494,21 @@ public static partial class Gen5SpirvTranslator
out string error) out string error)
{ {
error = string.Empty; 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; SpirvImageResource resource;
uint imageObject; uint imageObject;
uint dstSelect; uint dstSelect;

View File

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

View File

@ -158,6 +158,10 @@ public sealed class MemoryAccessTable
table.Add(FromImage(instruction, image)); table.Add(FromImage(instruction, image));
break; break;
case Gen5DataShareControl share: case Gen5DataShareControl share:
if (instruction.Opcode is "DsSwizzleB32" or "DsBpermuteB32")
{
break;
}
table.Add(new MemoryAccessInfo table.Add(new MemoryAccessInfo
{ {
Pc = instruction.Pc, Pc = instruction.Pc,
@ -241,7 +245,11 @@ public sealed class MemoryAccessTable
{ {
Pc = instruction.Pc, Pc = instruction.Pc,
Opcode = opcode, 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, Access = access,
Offset = unchecked((uint)control.OffsetBytes), Offset = unchecked((uint)control.OffsetBytes),
DataDwords = Math.Max(control.DwordCount, 1u), DataDwords = Math.Max(control.DwordCount, 1u),
@ -257,6 +265,22 @@ public sealed class MemoryAccessTable
private static MemoryAccessInfo FromImage(Gen5ShaderInstruction instruction, Gen5ImageControl control) private static MemoryAccessInfo FromImage(Gen5ShaderInstruction instruction, Gen5ImageControl control)
{ {
var opcode = instruction.Opcode; 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 atomic = opcode.StartsWith("ImageAtomic", StringComparison.Ordinal);
var store = opcode.StartsWith("ImageStore", StringComparison.Ordinal); var store = opcode.StartsWith("ImageStore", StringComparison.Ordinal);
var sampled = opcode.StartsWith("ImageSample", 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, evaluateTable: true, out var values, out var table, out var activeSources,
additionalTableWords: checked(plan.WrittenRangeCount * ShaderResourcePlan.WrittenRangeDwordCount))) additionalTableWords: checked(plan.WrittenRangeCount * ShaderResourcePlan.WrittenRangeDwordCount)))
{ {
SpecializationFailed(DiagnoseSnapshotEvaluationFailure(plan, inputs, activeSources));
return false; return false;
} }
@ -214,6 +215,126 @@ public static class ResourceMaterializer
return true; 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) => private static bool NullImageDescriptor(ReadOnlySpan<uint> descriptor) =>
descriptor[0] == 0 && (descriptor[1] & 0xFF) == 0; descriptor[0] == 0 && (descriptor[1] & 0xFF) == 0;

View File

@ -485,7 +485,9 @@ public sealed partial class ResourceTracker
{ {
if (memory.Kind == MemoryResourceKind.Scratch) 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) if (access.Handle is null || access.Handle.Kind != ScalarValueKind.AddressHandle)

View File

@ -73,6 +73,19 @@ public sealed class Gen5ShaderAtomicDecodeTests
instruction.Destinations); 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] [Fact]
public void ImageAtomicAdd_KeepsDataRegisterAsDestination() public void ImageAtomicAdd_KeepsDataRegisterAsDestination()
{ {
@ -199,6 +212,48 @@ public sealed class Gen5ShaderAtomicDecodeTests
Assert.Empty(instruction.Destinations); 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) private static Gen5ShaderInstruction DecodeSingle(params uint[] words)
{ {
var memory = new FakeCpuMemory(ShaderAddress, 0x1000); var memory = new FakeCpuMemory(ShaderAddress, 0x1000);

View File

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

View File

@ -106,6 +106,35 @@ public sealed class Gen5ShaderTranslatorTests
Assert.Equal(Gen5OperandKind.EncodedConstant, instruction.Sources[0].Kind); 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] [Fact]
public void FusedProgramContinuesAfterSetProgramCounter() public void FusedProgramContinuesAfterSetProgramCounter()
{ {