From bcb0ebd991733aa253f1efdcd9c336925df6b548 Mon Sep 17 00:00:00 2001 From: Peter Bonanni Date: Fri, 17 Jul 2026 19:41:07 -0400 Subject: [PATCH] [ShaderCompiler] Fix VReadlane scalar destination field (#344) V_READLANE uses the gfx10 VOP3A vdst byte even though its result is scalar. Decode bits 0-7 and cover the public LLVM s5 and s101 encodings so the VOP3B sdst field cannot be confused with this opcode again. --- .../Gen5ShaderTranslator.cs | 5 +- .../Gen5ShaderTranslatorTests.cs | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs index 4b49a267..a6347b2b 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs @@ -1872,8 +1872,9 @@ public static class Gen5ShaderTranslator destinations = [Gen5Operand.Vector(word & 0xFF)]; if (opcode == "VReadlaneB32") { - // The scalar destination lives in the low vdst byte (bits 0-7); - // bits 8-14 are the VOP3B carry-out sdst, which readlane lacks. + // V_READLANE uses the VOP3A vdst byte even though the + // destination register is scalar. Bits 8-14 are the + // distinct sdst field used by VOP3B encodings. destinations = [Gen5Operand.Scalar(word & 0xFF)]; } var isVop3B = IsVop3BOpcode((word >> 16) & 0x3FF); diff --git a/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs new file mode 100644 index 00000000..2e449e9c --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs @@ -0,0 +1,52 @@ +// 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; + + [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); + } +}