// 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(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 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 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 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 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 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) { 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 bytes = stackalloc byte[sizeof(uint)]; BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); Assert.True(memory.TryWrite(address, bytes)); } }