[Spirvtranslator] Added many instruction set for SPIR-V translator (#894)
This commit is contained in:
parent
6fe8555f8f
commit
6d6a5a1a0d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) ||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue