sharpemu/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs

331 lines
14 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using SharpEmu.HLE;
using SharpEmu.ShaderCompiler;
using Xunit;
namespace SharpEmu.Libs.Tests.ShaderCompiler;
public sealed class Gen5ShaderTranslatorTests
{
private const ulong ProgramAddress = 0x1_0000_0000;
[Fact]
public void Vop2OpcodeZeroDecodesAsNoop()
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
WriteWords(memory, ProgramAddress, 0x00000000u, 0xBF810000u);
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.Vop2, instruction.Encoding);
Assert.Equal("VNop", instruction.Opcode);
Assert.Equal(0u, instruction.Pc);
}
[Fact]
public void Vop2FmamkF16ConsumesLiteralDword()
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
WriteWords(memory, ProgramAddress, 0x6F104E10u, 0x3C003C00u, 0xBF810000u);
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.Vop2, instruction.Encoding);
Assert.Equal("VFmaMkF16", instruction.Opcode);
Assert.Equal(2, instruction.Words.Count);
Assert.Equal(3, instruction.Sources.Count);
Assert.Equal(Gen5OperandKind.LiteralConstant, instruction.Sources[1].Kind);
Assert.Equal(0x3C003C00u, instruction.Sources[1].Value);
}
[Fact]
public void Sop2ScalarFloatSubtractDecodes()
{
const uint instructionWord =
(2u << 30) | (0x41u << 23) | (3u << 16) | (2u << 8) | 1u;
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
WriteWords(memory, ProgramAddress, instructionWord, 0xBF810000u);
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.Sop2, instruction.Encoding);
Assert.Equal("SSubF32", instruction.Opcode);
Assert.Equal(3u, instruction.Destinations[0].Value);
}
[Fact]
public void ScalarScratchStoreDwordx2DecodesAsStore()
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
WriteWords(memory, ProgramAddress, 0xC59B2040u, 0xBF810000u);
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.Smrd, instruction.Encoding);
Assert.Equal("ScratchStoreDwordx2", instruction.Opcode);
Assert.Empty(instruction.Destinations);
Assert.Equal(4, instruction.Sources.Count);
Assert.Equal(32u, instruction.Sources[0].Value);
Assert.Equal(54u, instruction.Sources[1].Value);
Assert.Equal(55u, instruction.Sources[2].Value);
Assert.Equal(64u, instruction.Sources[3].Value);
var control = Assert.IsType<Gen5GlobalMemoryControl>(instruction.Control);
Assert.Equal(2u, control.DwordCount);
Assert.Equal(32u, control.ScalarAddress);
Assert.Equal(54u, control.SourceVectorRegister);
Assert.Equal(64u, control.DynamicOffsetRegister);
Assert.True(control.SourceIsScalar);
}
[Theory]
[InlineData(0xD7600005u, 5u)]
[InlineData(0xD7600065u, 101u)]
public void VReadlaneB32DecodesScalarDestinationFromVdstByte(
uint instructionWord,
uint expectedDestination)
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
Span<byte> code = stackalloc byte[3 * sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(code, instructionWord);
BinaryPrimitives.WriteUInt32LittleEndian(code[sizeof(uint)..], 0x02000501u);
BinaryPrimitives.WriteUInt32LittleEndian(code[(2 * 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 = Assert.Single(
program.Instructions,
static item => item.Opcode == "VReadlaneB32");
Assert.Equal(Gen5ShaderEncoding.Vop3, instruction.Encoding);
var destination = Assert.Single(instruction.Destinations);
Assert.Equal(Gen5OperandKind.ScalarRegister, destination.Kind);
Assert.Equal(expectedDestination, destination.Value);
Assert.Equal(Gen5OperandKind.VectorRegister, instruction.Sources[0].Kind);
Assert.Equal(1u, instruction.Sources[0].Value);
Assert.Equal(Gen5OperandKind.ScalarRegister, instruction.Sources[1].Kind);
Assert.Equal(2u, instruction.Sources[1].Value);
}
[Theory]
[InlineData(0xBF970001u, "SCbranchCdbgsys")]
[InlineData(0xBF980001u, "SCbranchCdbguser")]
[InlineData(0xBF990001u, "SCbranchCdbgsysOrUser")]
[InlineData(0xBF9A0001u, "SCbranchCdbgsysAndUser")]
public void DebugConditionBranchesDecode(uint instructionWord, string expectedOpcode)
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
Span<byte> code = stackalloc byte[3 * sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(code, instructionWord);
BinaryPrimitives.WriteUInt32LittleEndian(code[sizeof(uint)..], 0xBF800000u);
BinaryPrimitives.WriteUInt32LittleEndian(code[(2 * 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);
Assert.Equal(expectedOpcode, program.Instructions[0].Opcode);
}
[Theory]
[InlineData(0xBBFD0000u)]
[InlineData(0xBC7D0000u)]
[InlineData(0xBCFD0000u)]
[InlineData(0xBD7D0000u)]
public void SopkWaitCounterFormsDecodeWithoutScalarDestination(uint instructionWord)
{
var memory = new FakeCpuMemory(ProgramAddress, 0x100);
Span<byte> code = stackalloc byte[2 * sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(code, instructionWord);
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("SWaitcnt", instruction.Opcode);
Assert.Empty(instruction.Destinations);
Assert.Single(instruction.Sources);
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()
{
const ulong continuationAddress = ProgramAddress + 0x100;
const ulong entryHeaderAddress = ProgramAddress + 0x400;
const ulong continuationHeaderAddress = ProgramAddress + 0x500;
var memory = new FakeCpuMemory(ProgramAddress, 0x1000);
WriteWords(memory, ProgramAddress, 0xBF800000u, 0xBE802000u);
WriteWords(memory, continuationAddress, 0xBF800000u, 0xBF810000u);
WriteUInt32(memory, entryHeaderAddress + 0x44, 2 * sizeof(uint));
WriteUInt32(memory, continuationHeaderAddress + 0x44, 2 * sizeof(uint));
var context = new CpuContext(memory, 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", "SNop", "SEndpgm"],
program.Instructions.Select(static instruction => instruction.Opcode));
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)
{
var bytes = new byte[words.Length * sizeof(uint)];
for (var index = 0; index < words.Length; index++)
{
BinaryPrimitives.WriteUInt32LittleEndian(
bytes.AsSpan(index * sizeof(uint), sizeof(uint)),
words[index]);
}
Assert.True(memory.TryWrite(address, bytes));
}
private static void WriteUInt32(FakeCpuMemory memory, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
Assert.True(memory.TryWrite(address, bytes));
}
}