[ShaderCompiler] Decode fused programs whose halves are far apart
The fused decoder joined the continuation at its address distance from the entry and refused halves more than 4 GiB apart or in the wrong order, which Poppy Playtime now hits for a vertex shader (invalid-fused-layout). Such a continuation is placed right after the entry instead, and each of its instructions keeps its real offset from the program address in AddressOffset; S_GETPC_B64 in the scalar graph, the SPIR-V and the MSL translators uses that offset, and the persisted scalar analysis key includes it.
This commit is contained in:
parent
521360f04b
commit
1b3c380137
|
|
@ -1116,7 +1116,7 @@ public static partial class Gen5MslTranslator
|
|||
{
|
||||
// The shader base is pushed per draw; the program offset is added to it.
|
||||
var (baseLow, baseHigh) = ShaderBaseWords();
|
||||
var offset = instruction.Pc + (ulong)(instruction.Words.Count * sizeof(uint));
|
||||
var offset = unchecked(instruction.ProgramOffset + (ulong)(instruction.Words.Count * sizeof(uint)));
|
||||
var address = Temp("ulong", $"((ulong){baseLow} | ((ulong){baseHigh} << 32)) + {offset}ul");
|
||||
StoreScalar(destination, $"(uint){address}");
|
||||
StoreScalar(destination + 1, $"(uint)({address} >> 32)");
|
||||
|
|
|
|||
|
|
@ -2616,7 +2616,7 @@ public static partial class Gen5SpirvTranslator
|
|||
var (baseLow, baseHigh) = LoadShaderBase();
|
||||
var next = IAdd64(
|
||||
Pair64(baseLow, baseHigh),
|
||||
ULong(instruction.Pc + (ulong)(instruction.Words.Count * sizeof(uint))));
|
||||
ULong(unchecked(instruction.ProgramOffset + (ulong)(instruction.Words.Count * sizeof(uint)))));
|
||||
StoreS(destination, Narrow(next));
|
||||
StoreS(destination + 1, Narrow(ShiftRightLogical64(next, ULong(32))));
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -336,7 +336,12 @@ public sealed record Gen5ShaderInstruction(
|
|||
IReadOnlyList<uint> Words,
|
||||
IReadOnlyList<Gen5Operand> Sources,
|
||||
IReadOnlyList<Gen5Operand> Destinations,
|
||||
Gen5InstructionControl? Control);
|
||||
Gen5InstructionControl? Control)
|
||||
{
|
||||
public ulong? AddressOffset { get; init; }
|
||||
|
||||
public ulong ProgramOffset => AddressOffset ?? Pc;
|
||||
}
|
||||
|
||||
public sealed record Gen5ShaderProgram(
|
||||
ulong Address,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public static partial class Gen5ShaderTranslator
|
|||
// reaching S_ENDPGM. Keep the malformed-shader guard, but size it to the
|
||||
// largest declared shader object accepted by the decoder (1 MiB).
|
||||
private const int MaxInstructions = (int)(MaximumDeclaredShaderSizeBytes / sizeof(uint));
|
||||
private const ulong FusedContinuationAlignment = 0x100;
|
||||
private const ulong ShaderSizeOffset = 0x44;
|
||||
private const uint MaximumDeclaredShaderSizeBytes = 1024 * 1024;
|
||||
private static readonly ConditionalWeakTable<object, FusedProgramRegistry> _fusedProgramsByMemory = new();
|
||||
|
|
@ -159,9 +160,8 @@ public static partial class Gen5ShaderTranslator
|
|||
return false;
|
||||
}
|
||||
|
||||
if (parts.ContinuationAddress <= entryAddress ||
|
||||
parts.ContinuationAddress - entryAddress > uint.MaxValue ||
|
||||
((parts.ContinuationAddress - entryAddress) & (sizeof(uint) - 1)) != 0)
|
||||
var continuationDistance = unchecked(parts.ContinuationAddress - entryAddress);
|
||||
if (parts.ContinuationAddress == entryAddress || (continuationDistance & (sizeof(uint) - 1)) != 0)
|
||||
{
|
||||
error = $"invalid-fused-layout entry=0x{entryAddress:X} " +
|
||||
$"continuation=0x{parts.ContinuationAddress:X}";
|
||||
|
|
@ -201,7 +201,13 @@ public static partial class Gen5ShaderTranslator
|
|||
return false;
|
||||
}
|
||||
|
||||
var continuationPc = checked((uint)(parts.ContinuationAddress - entryAddress));
|
||||
var lastEntryInstruction = entryProgram.Instructions[^1];
|
||||
var entryEnd = (ulong)lastEntryInstruction.Pc + (ulong)lastEntryInstruction.Words.Count * sizeof(uint);
|
||||
var adjacent = parts.ContinuationAddress > entryAddress && continuationDistance <= uint.MaxValue &&
|
||||
continuationDistance >= entryEnd;
|
||||
var continuationPc = adjacent
|
||||
? (uint)continuationDistance
|
||||
: (uint)((entryEnd + FusedContinuationAlignment - 1) & ~(FusedContinuationAlignment - 1));
|
||||
var instructions = new List<Gen5ShaderInstruction>(
|
||||
entryProgram.Instructions.Count + continuationProgram.Instructions.Count);
|
||||
instructions.AddRange(entryProgram.Instructions.Take(entryProgram.Instructions.Count - 1));
|
||||
|
|
@ -227,7 +233,9 @@ public static partial class Gen5ShaderTranslator
|
|||
return false;
|
||||
}
|
||||
|
||||
instructions.Add(instruction with { Pc = (uint)rebasedPc });
|
||||
instructions.Add(adjacent
|
||||
? instruction with { Pc = (uint)rebasedPc }
|
||||
: instruction with { Pc = (uint)rebasedPc, AddressOffset = unchecked(continuationDistance + instruction.ProgramOffset) });
|
||||
}
|
||||
|
||||
program = new Gen5ShaderProgram(entryAddress, instructions);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ internal static class ScalarGraphDiskCache
|
|||
writer.Write(program.Instructions.Count);
|
||||
foreach (var instruction in program.Instructions)
|
||||
{
|
||||
writer.Write(instruction.Pc); writer.Write((int)instruction.Encoding); writer.Write(instruction.Opcode);
|
||||
writer.Write(instruction.Pc); writer.Write(instruction.ProgramOffset); writer.Write((int)instruction.Encoding); writer.Write(instruction.Opcode);
|
||||
writer.Write(instruction.Words.Count);
|
||||
foreach (var word in instruction.Words) writer.Write(word);
|
||||
writer.Write(instruction.Sources.Count);
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ public sealed partial class ScalarValueGraph
|
|||
case "SGetpcB64":
|
||||
{
|
||||
var address = _graph.Operation(ScalarOperation.IAdd64, ScalarValueType.U64, _graph.ShaderBase(),
|
||||
_graph.Constant((ulong)instruction.Pc + (ulong)(instruction.Words.Count * sizeof(uint))));
|
||||
_graph.Constant(unchecked(instruction.ProgramOffset + (ulong)(instruction.Words.Count * sizeof(uint)))));
|
||||
state.WritePair(destinationRegister, Extract(address, 0), Extract(address, 1));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,6 +270,42 @@ public sealed class Gen5ShaderTranslatorTests
|
|||
Assert.Equal(
|
||||
[0u, 4u, 0x100u, 0x104u],
|
||||
program.Instructions.Select(static instruction => instruction.Pc));
|
||||
Assert.All(program.Instructions, static instruction => Assert.Null(instruction.AddressOffset));
|
||||
}
|
||||
|
||||
private sealed class TwoRegionMemory(FakeCpuMemory first, FakeCpuMemory second) : ICpuMemory
|
||||
{
|
||||
public bool TryRead(ulong virtualAddress, Span<byte> destination) =>
|
||||
first.TryRead(virtualAddress, destination) || second.TryRead(virtualAddress, destination);
|
||||
|
||||
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source) =>
|
||||
first.TryWrite(virtualAddress, source) || second.TryWrite(virtualAddress, source);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x1_8000_0000L)]
|
||||
[InlineData(-0x1000L)]
|
||||
public void FusedProgramKeepsAFarContinuationsAddressForGetPc(long distance)
|
||||
{
|
||||
var continuationAddress = unchecked(ProgramAddress + (ulong)distance);
|
||||
const ulong entryHeaderAddress = ProgramAddress + 0x400;
|
||||
var continuationHeaderAddress = continuationAddress + 0x400;
|
||||
var entry = new FakeCpuMemory(ProgramAddress, 0x800);
|
||||
var continuation = new FakeCpuMemory(continuationAddress, 0x800);
|
||||
WriteWords(entry, ProgramAddress, 0xBF800000u, 0xBE802000u);
|
||||
WriteWords(continuation, continuationAddress, 0xBE801F00u, 0xBF810000u);
|
||||
WriteUInt32(entry, entryHeaderAddress + 0x44, 2 * sizeof(uint));
|
||||
WriteUInt32(continuation, continuationHeaderAddress + 0x44, 2 * sizeof(uint));
|
||||
|
||||
var context = new CpuContext(new TwoRegionMemory(entry, continuation), Generation.Gen5);
|
||||
Gen5ShaderTranslator.RegisterFusedProgram(context, ProgramAddress, entryHeaderAddress, continuationAddress, continuationHeaderAddress);
|
||||
|
||||
Assert.True(Gen5ShaderTranslator.TryDecodeProgram(context, ProgramAddress, out var program, out var error), error);
|
||||
Assert.Equal(["SNop", "SNop", "SGetpcB64", "SEndpgm"], program.Instructions.Select(static instruction => instruction.Opcode));
|
||||
Assert.Equal([0u, 4u, 0x100u, 0x104u], program.Instructions.Select(static instruction => instruction.Pc));
|
||||
Assert.Equal((ulong)distance, program.Instructions[2].ProgramOffset);
|
||||
Assert.Equal(unchecked((ulong)distance + 4), program.Instructions[3].ProgramOffset);
|
||||
Assert.Equal(continuationAddress, unchecked(program.Address + program.Instructions[2].ProgramOffset));
|
||||
}
|
||||
|
||||
private static void WriteWords(FakeCpuMemory memory, ulong address, params uint[] words)
|
||||
|
|
|
|||
Loading…
Reference in New Issue