diff --git a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs index 4eb07ac..3effbfe 100644 --- a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs @@ -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)"); diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs index edfc136..f7597e8 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs @@ -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; diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderIr.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderIr.cs index ebf5d7e..146274a 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderIr.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderIr.cs @@ -336,7 +336,12 @@ public sealed record Gen5ShaderInstruction( IReadOnlyList Words, IReadOnlyList Sources, IReadOnlyList Destinations, - Gen5InstructionControl? Control); + Gen5InstructionControl? Control) +{ + public ulong? AddressOffset { get; init; } + + public ulong ProgramOffset => AddressOffset ?? Pc; +} public sealed record Gen5ShaderProgram( ulong Address, diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs index 9213a68..1639150 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs @@ -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 _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( 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); diff --git a/src/SharpEmu.ShaderCompiler/Resources/ScalarGraphDiskCache.cs b/src/SharpEmu.ShaderCompiler/Resources/ScalarGraphDiskCache.cs index 550ff99..4f8e10f 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ScalarGraphDiskCache.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ScalarGraphDiskCache.cs @@ -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); diff --git a/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs b/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs index 2e3a488..eaaf4b9 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ScalarValueGraph.Builder.cs @@ -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; } diff --git a/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs index 0ab0652..5854c7e 100644 --- a/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs +++ b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs @@ -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 destination) => + first.TryRead(virtualAddress, destination) || second.TryRead(virtualAddress, destination); + + public bool TryWrite(ulong virtualAddress, ReadOnlySpan 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)